这种使用不安全的琐碎安全吗?

Posted

技术标签:

【中文标题】这种使用不安全的琐碎安全吗?【英文标题】:Is this use of unsafe trivially safe? 【发布时间】:2021-12-09 07:03:53 【问题描述】:

我遇到了一个 Rust 借用检查器错误,我认为这是 non-lexical lifetimes 当前实现的限制。我想写的代码是这样的:

struct Thing 
    value: i32


impl Thing 
    fn value(&self) -> &i32 
        &self.value
    
    fn increment(&mut self) 
        self.value += 1;
    


/// Increments the value of `thing` if it is odd, and returns a reference to the value.
fn increment_if_odd(thing: &mut Thing) -> &i32 
    let ref_to_value = thing.value();
    if (*ref_to_value % 2) == 0 
        return ref_to_value;
    
    thing.increment();  // fails to compile because the immutable borrow `ref_to_value` is still alive
    thing.value()

Rust Playground.

第一个问题:我是否认为这段代码是 100% 安全的,而借用检查器过于保守?返回ref_to_value 的分支不会改变thing,因此保证引用是有效的,而另一个分支根本不使用ref_to_value。 (我知道如果我用return thing.value(); 替换return ref_to_value; 它将编译,但在我的实际代码中value 方法很昂贵。)

看来我可以通过指针“清洗”引用来解决这个问题:

if (*ref_to_value % 2) == 0 
    return unsafe 
        &*(ref_to_value as *const i32)
    

第二个问题:这很安全吗?我以前从来没有用过 unsafe 所以我很紧张。

我猜第三个问题:有没有办法用安全的 Rust 重写它?约束是 value 只能在非变异路径上调用一次。

【问题讨论】:

我想说编译器在这里有问题。因为通过强制执行额外的 escope,应该删除引用。但我不是。 play.rust-lang.org/… 即使显式删除也不起作用:play.rust-lang.org/… 看看the RFC for non-lexical lifetimes。第三个例子和你的差不多,第四个的变通方法可以修改。 这不是编译器错误,而是已知限制。我知道我们有这个问题的多个重复项,但我现在无法找到一个。我会继续挖掘。 我想用 Polonius 补充一下,有问题的代码已经编译了 - play.rust-lang.org/…。 【参考方案1】:

编译器不允许您的代码的原因是因为 ref_to_value 的生命周期必须至少与 increment_if_odd 的生命周期一样长才能返回。

如果您在省略的生命周期中添加回来,则 ref_to_value 必须具有生命周期 'a.我的理解是编译器不能改变引用的生命周期。编写安全 rust 来解决此问题的一种方法是使 ref_to_value 可变,并修改 Thing::increment。

您的不安全代码所做的是允许编译器为 ref_to_value 提供更短的生命周期,以及通过强制转换指针创建的新引用生命周期 'a.我认为你的不安全代码是“安全的”,因为没有任何 rust 的借用规则被破坏,而且我们知道新的引用不会超过数据。

struct Thing 
    value: i32


impl Thing 
    fn value(&self) -> &i32 
        &self.value
    
    fn mut_value(&mut self) -> &mut i32
        &mut self.value
    
    fn increment(val: &mut i32) 
        *val += 1;
    


/// Increments the value of `thing` if it is odd, and returns a reference to the value.
fn increment_if_odd<'a>(thing: &'a mut Thing) -> &'a i32 
    
    let ref_to_value : &'a mut i32 = thing.mut_value();
    if (*ref_to_value % 2) != 0 
        Thing::increment(ref_to_value);
    
    ref_to_value

【讨论】:

【参考方案2】:

可能是安全的,没有仔细观察它。另一方面,您可以将函数重写为:

fn increment_if_odd(thing: &mut Thing) -> &i32 
    if (*thing.value() % 2) != 0 
        thing.increment();
    
    thing.value()

【讨论】:

谢谢!这会调用value 两次,但我想避免这种情况。在我的代码中,value 很昂贵。 @JamesFennell 啊,好吧。问题是 increment 中的 &amp;mut self 对编译器意味着 self 的任何数据/内存都可以更改并使 value() 返回的引用后面的数据无效,如果您知道不会使其无效,则 unsafe 很好但强烈建议不要这样做,因为如果您将来改变行为,它很容易破坏。 如果increment 将不同的数据更改为value() 返回的数据,那么我建议将Thing 结构拆分为两个单独的结构。然后你可以改变一侧而不会使另一侧的不可变引用无效。否则可能创建一个value_mut(),然后让increment 采用&amp;mut i32 而不是&amp;mut self...?可能需要更多上下文来提供更好的建议:) 关键点是有两个分支,并且不会沿着返回引用的分支发生突变,所以代码没问题。借用检查器过于保守。 我不知道那个语言,但为什么不知道x &amp; 1 == 1

以上是关于这种使用不安全的琐碎安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

php邮件功能;这种使用方式安全吗?

这种使用模板对类层次结构进行函数重载的方式安全吗?

这种基于 JWT 的身份验证方法安全吗?

XSRF 和双重提交 cookie JWT 替代方案 - 这种实现安全吗?

输入可信的网站地址访问的网站一定是安全的这种方法正确吗?

这种动态 SQL 查询生成对注入安全吗?