5

当我想知道可变引用如何进入方法时,所有的问题都开始了。

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.

所以,我在谷歌上搜索了更多关于“再借”的细节。

这就是我所拥有的。

在任何代码中,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 x在 的生命周期内不可用,&mut *&mutx也不知道为什么在 的生命周期mut x后重新可用&mut *&mutx,但是“好吧,我们就这么说吧”。

但是看看这个。对于一个清晰和一般的理解,我完全没有想到。

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???(这是我的猜测)。

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

4

1 回答 1

3

这些是一些很好的问题!我会尽我所能回答我能回答的。

Rust 参考是寻找此类问题答案的好地方,关于 Rust 的更深层次的语义。

首先,对于您关于方法解析的问题,参考资料说

在查找方法调用时,接收器可能会自动取消引用或借用以调用方法。这需要比其他函数更复杂的查找过程,因为可能有许多可能的方法可以调用。使用以下程序:

第一步是建立一个候选接收器类型列表。通过重复取消引用接收器表达式的类型,将遇到的每种类型添加到列表中,最后尝试在末尾进行未调整大小的强制转换,如果成功则添加结果类型来获得这些。然后,对于每个候选者T, 在 之后立即将&T和添加&mut T到列表中T

例如,如果接收者有 type Box<[i32;2]>,那么候选类型将是Box<[i32;2]>, &Box<[i32;2]>, &mut Box<[i32;2]>, [i32; 2](通过解引用),&[i32; 2], &mut [i32; 2], [i32](通过未调整大小的强制),&[i32], 最后是&mut [i32]

上面的链接更详细。

对于您的其余问题,我认为这主要与类型强制有关。一些相关的强制是

  • &mut T&T
  • &T或者如果实现&mut T_&UTDeref<Target = U>
  • &mut T&mut U如果T实现_DerefMut<Target = U>

值得注意的是,&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,这就是智能指针(如Box,std::cell::RefMut等)的情况。但是,引用得到特殊处理:Rust 可以分析它们指向的内存。

所以当你写的时候let b = &mut *ab其实并不借a!它只借用数据a点。这种区别以前并不重要,但在这里它是允许代码编译的原因。

至于你的最后一个例子,我无法按照你描述的方式打破它。

于 2020-12-28T21:58:43.040 回答