UnsafeCell 从函数获取可变引用:无法返回对临时值的引用

问题描述 投票:0回答:2

我正在尝试

UnsafeCell
,这是一个例子:

use std::cell::UnsafeCell;

fn get_ref_compile_error(cell: &UnsafeCell<i32>) -> &mut i32 {
    &mut unsafe { *cell.get() } // error: cannot return reference to a temporary value
}

fn get_ref_compiles_fine(cell: &UnsafeCell<i32>) -> &mut i32 {
    unsafe { &mut *cell.get() } // ok
}

fn main(){
    let cell = UnsafeCell::new(1);
    let ref_compiles_fine = unsafe { &mut *cell.get() }; // ok
    let ref_compiles_fine_2 = &mut unsafe { *cell.get() };  // ok
}

游乐场

我怀疑

&mut unsafe { *cell.get() }
无法编译的原因是
unsafe { *cell.get() }
可能具有功能块作用域的生命周期。

这对我来说有点奇怪,因为根据

unsafe
块内的引用是否借用相同的对象会导致不同的行为。

问题: 为什么可以归还

unsafe { &mut *cell.get() }
,但不能归还
&mut unsafe { *cell.get() }
,而这实际上是借用同一个对象。

pointers rust lifetime borrow-checker unsafe
2个回答
0
投票

unsafe { *cell.get() }
评估为
i32
。它将单元格中的值复制到函数本地临时变量中,然后尝试使用
&mut
返回对该临时变量的可变引用。

&mut *cell.get()
执行指针目标的重新借用。

构造

&*
&mut *
执行重新借用(可能在 deref 强制转换之后,尽管这里不会发生)。从语法上讲,这要求解引用和借用不能被分解。在第一个示例中,
unsafe
块位于它们之间,但这并不是打破重借模式的唯一结构;如果你这样做,你会得到同样的错误:

unsafe { &mut { *cell.get() } }

0
投票

这是一个更简单的示例,可以更好地显示差异:

pub fn unmut_string(s: &mut String) -> &String {
    // this is fine
    &*s
}

pub fn unmut_string2(s: &mut String) -> &String {
    // this is not
    &{ *s }
}

差异在于位置表达式值表达式

*s
是一个地方表达式,当你将
&
应用到这个地方表达式时,你会得到一个参考值。然而,块
{}
只能包含值表达式,因此它尝试通过复制将位置表达式转换为值。只有在复制之后,它才会尝试引用新值。

error[E0507]: cannot move out of `*s` which is behind a mutable reference
 --> src/lib.rs:8:8
  |
8 |     &{ *s }
  |        ^^ move occurs because `*s` has type `String`, which does not implement the `Copy` trait

error[E0515]: cannot return reference to temporary value
 --> src/lib.rs:8:5
  |
8 |     &{ *s }
  |     ^------
  |     ||
  |     |temporary value created here
  |     returns a reference to data owned by the current function

注意:我交换了这些错误的顺序

第一个错误是因为

String
值无法复制。您的代码不会产生此错误,因为
i32
可以被复制,但这不是您想要的。您想要引用现有值,而不是对副本的引用。

从那里,第二个错误更加清晰:(假设复制成功)您正在尝试创建对刚刚创建的值的引用。当函数返回时,该值将被删除,并且引用将不再有效。

这是一个两个版本都可以工作的示例,以及您由此获得的差异。第一个符合您的预期,而第二个则保持

x
不变。这就是
// ok
末尾的两行
main
中发生的情况。

let mut x = 5;
let r = &mut x;
let y = &mut *r;
*y += 1;
println!("{x}"); // 6

let mut x = 5;
let r = &mut x;
let y = &mut { *r };
*y += 1;
println!("{x}"); // 5

在此代码中,第二个

y
是对经历临时生命周期延长的值的引用。也就是说,它相当于这段代码:

let mut x = 5;
let r = &mut x;
let mut _y = *r; // _y is a hidden variable
let y = &mut _y;
*y += 1;
println!("{x}"); // 5

在此代码中,应该清楚的是,

y
仅在
_y
存在时才有效,并且
_y
与已复制的
x
是分开的。

回到您的代码,不安全块是一种块,因此它们只能包装值表达式。无法单独包装不安全的位置表达式,但由于位置表达式通常非常短,因此很容易包装周围的值表达式。实际上,

&mut *
是一个将原始指针转换为引用的操作。

© www.soinside.com 2019 - 2024. All rights reserved.