这种使用不安全的琐碎安全吗?
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
中的 &mut self
对编译器意味着 self 的任何数据/内存都可以更改并使 value()
返回的引用后面的数据无效,如果您知道不会使其无效,则 unsafe 很好但强烈建议不要这样做,因为如果您将来改变行为,它很容易破坏。
如果increment
将不同的数据更改为value()
返回的数据,那么我建议将Thing
结构拆分为两个单独的结构。然后你可以改变一侧而不会使另一侧的不可变引用无效。否则可能创建一个value_mut()
,然后让increment
采用&mut i32
而不是&mut self
...?可能需要更多上下文来提供更好的建议:)
关键点是有两个分支,并且不会沿着返回引用的分支发生突变,所以代码没问题。借用检查器过于保守。
我不知道那个语言,但为什么不知道x & 1 == 1
?以上是关于这种使用不安全的琐碎安全吗?的主要内容,如果未能解决你的问题,请参考以下文章