可变引用的再借用

17
当我想知道可变引用如何移动到方法中时,所有问题都开始了。
let a = &mut x;
a.somemethod(); // value of a should have moved
a.anothermethod(); // but it works.

我已经进行了大量的谷歌搜索(真的很多),并且我注意到作为参数传递给函数的可变引用,总是会经历以下转换(称为重新借用)。

fn test(&mut a) -> ();

let a = &mut x;
test(a); // what we write in code.
test(&mut *a); // the actual action in code.

所以,我对“reborrowing”进行了更详细的谷歌搜索。

以下是我找到的内容:

在任何代码中,x都指代任意数据。我没有提及它,因为我认为它的类型对讨论并不重要。(然而,在我的代码中我使用了i32)。

let a = &mut x;
let b = &mut *a; // a isn't available from now on
*a = blahblah; // error! no more access allowed for a
*b = blahblah; // I've wrote this code not to confuse you of the lifetime of b. If not mentioned, it always live till the scope ends.

let a = &mut x;
{
    let b = &*a;
    // *a = blahblah in this scope will throw an error, just like above case.
}
*a = blahblah; // but this works.

好的。这很有趣。看起来b不仅借用了x,还借用了a

也许,我们可以这样澄清重新借用:&'a *(&'b mut x)。 它借用了x(在此处有一个生命周期为'a'), 但也借用了a(它的生命周期为'b')。

所以我运行了以下代码来确认我的猜想。

let x: i32 = 1; // I wanted to make it clear that x lives in this scope.
let b;
{
    let a = &mut x;
    b = &mut *a;
}
*b = 0;

但这可行!

什么?? 但我刚决定使用&'a mut *&mutx,而不是&'a mut *&'b mutx

我不知道为什么在&mut *&mutx的生命周期内mut x无法使用,也不知道为什么在&mut *&mutx的生命周期之后mut x又可以使用了,但“好吧,就这样吧”。

但看看这个。它完全超出了我的理解范围。

let x: i32 = 1;
let b;
{
    let a = &mut x;
    let b = &**&a;
} // error!! a should live longer than b!

生命周期不是简单地依赖于实际数据 b 所指的吗??? 应该使用 &'a **& &mut x,而不是 &'a **&'b &'c mut x

现在怎么办??

&'a **&'b &mut x ???(这是我的猜测)。

我应该如何接受这种复杂的情况呢?


1
我的终身表达完全是无意义的。对不起,我的问题很糟糕。请忽略它。 - kwonryul
1个回答

10
这些是一些很好的问题!我会尽力回答我能回答的问题。
Rust参考文档是寻找像这样关于Rust深层语义的问题答案的好地方。
首先,对于您关于方法解析的问题,参考文档 表示
当查找方法调用时,接收器可能会自动取消引用或借用以调用方法。这需要比其他函数更复杂的查找过程,因为可能有许多可调用的方法。使用以下过程:
第一步是构建候选接收器类型列表。通过反复取消引用接收器表达式的类型来获得这些类型,将遇到的每种类型添加到列表中,最后在结尾尝试无大小 coercion,如果成功,则添加结果类型。然后,对于每个候选T,请立即在T之后添加&T和&mut T。
例如,如果接收器的类型为Box<[i32;2]>,则候选类型将是Box<[i32;2]>、&Box<[i32;2]>、&mut Box<[i32;2]>、[i32; 2](通过去引用)、&[i32; 2]、&mut [i32; 2]、[i32](通过无大小 coercion)、&[i32],最后是&mut [i32]。
上面的链接提供了更多详细信息。
至于您其余的问题,我认为这主要与类型 coercion 有关。一些相关的coercions 是
- &mut T to &T - &T或&mut T到&U,如果T实现Deref - &mut T到&mut U,如果T实现DerefMut。
值得一提的是,&U&mut U 都实现了 Deref<Target=U>,而且 &mut U 还实现了 DerefMut<Target=U>。因此,第二/第三条规则导致以下强制转换:
T 为:
&U &&U &U
&U &mut &U &U
&mut U &&mut U &U
&mut U &mut &mut U &mut U

现在去掉引用,让我们更详细地看看你的问题:
let a = &mut x;
let b = &mut *a; // a isn't available from now on
*a = blahblah; // error! no more access allowed for a
*b = blahblah; // I've wrote this code not to confuse you of the lifetime of b. If not mentioned, it always live till the scope ends.
为了写入一个引用,它必须是可变(也称为唯一)引用。当你写下let a = &mut x时,现在a是访问x的唯一方式。现在当你写下let b = &mut *a时,它基本上意味着let b = &mut *(&mut x),这简化为let b = &mut x
在这里,b可变地借用a,Rust不会让你使用a直到b被销毁......或者目前看起来是这样。
let a = &mut x;
{
    let b = &*a;
    // *a = blahblah in this scope will throw an error, just like above case.
}
*a = blahblah; // but this works.
在这里,let b = &*a转换为 let b = &*(&mut x),因此 let b = &x。无论b是什么类型的引用,a都是可变引用并且必须是唯一的,因此在b消失(超出作用域)之前不能使用它。
let mut x: i32 = 1; // I wanted to make it clear that x lives in this scope.
let b;
{
    let a = &mut x;
    b = &mut *a;
}
*b = 0;

(我假设你的意思是 let mut x)。

现在,这就变得有趣了。因为 ab 都可变地指向同一个对象,所以 Rust 不允许你使用 a 直到 b 被销毁。看起来推理应该是“由于 b 可变地借用了 a”,但实际上不是这样的。通常,b 确实会借用 a,智能指针如 Boxstd::cell::RefMut 等也是如此。然而,引用却有特殊的待遇:Rust 可以分析它们指向的内存。

因此,当你编写 let b = &mut *a 时,b 实际上并没有借用 a!它只借用了 a 指向的数据。这个区别以前并不相关,但在这里它正是代码能够编译的原因。

至于你最后一个例子,我无法按照你描述的方式使其出错。


由于这个,它在综合和理解大量信息以使其清晰方面非常有帮助。在撰写本文时,我的思维非常混乱,问题没有组织好。非常感谢! - kwonryul
我很高兴能够帮助!如果回答解决了你的问题,请务必将其标记为已接受。 - Coder-256

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接