为什么我可以强制方法的"&self"参数的引用移动语义,而不是函数参数?



我有两个版本的函数,旨在做同样的事情。

版本 1 - 有效!

pub fn example1() {
// Make a mutable slice
let mut v = [0, 1, 2, 3];
// Make a mutable reference to said slice
let mut v_ref = &mut v[..];
let len = v_ref.len();
// Reduce slice to sub-slice -> np ;)
v_ref = {
// Create sub-slices
let (v_l, v_r) = {
// Involves some edits -> need mut
v_ref.swap(0, len - 1);
{ v_ref }.split_at_mut(len / 2)
};
// Choose slice with which to overwrite
// (involves some non-trivial condition here)
match len % 2 {
0 => v_l,
_ => v_r,
}
};
// And then we do something with v_ref
println!("{:?}", v_ref);
}

本质上:

  • 我们从一个可变变量mut v_ref: &mut [i32]开始,其中包含对切片的可变引用
  • 我们使用split_at_mut* 从v_ref制作两个子切片
  • 覆盖变量v_ref以保存其中一个子切片

*(注意- 我们通过移动v_ref来避免两个可变引用的问题,而不是通过使用身份块重新借用(

(关于代码的意图 - 此切片缩减旨在循环重复;但是此细节不会影响问题(

版本 2 - 编译失败

版本2 与版本 1 几乎相同,只是子切片创建移动到其自己的函数中:

fn example2_inner(v_ref: &mut [i32]) -> (&mut [i32], &mut [i32]) {
// Recreate len variable in function scope
let len = v_ref.len();
// Same mutation here
v_ref.swap(0, len - 1);
// Same slice split here
v_ref.split_at_mut(len / 2)
}
pub fn example2() {
let mut v = [0, 1, 2, 3];
let mut v_ref = &mut v[..];
let len = v_ref.len();
// This now fails to compile :(
v_ref = {
// Mutating & slice spliting moved to function
let (v_l, v_r) = example2_inner({ v_ref });
match len % 2 {
0 => v_l,
_ => v_r,
}
};
println!("{:?}", v_ref);
}

当我尝试构建它时,我收到以下错误:

error[E0506]: cannot assign to `v_ref` because it is borrowed
--> src/lib.rs:19:5
|
19 | /     v_ref = {
20 | |         // Mutating & slice spliting moved to function
21 | |         let (v_l, v_r) = example2_inner({ v_ref });
| |                                           ----- borrow of `v_ref` occurs here
22 | |
...  |
26 | |         }
27 | |     };
| |_____^ assignment to borrowed `v_ref` occurs here
error[E0502]: cannot borrow `v_ref` as immutable because `*v_ref` is also borrowed as mutable
--> src/lib.rs:29:22
|
21 |         let (v_l, v_r) = example2_inner({ v_ref });
|                                           ----- mutable borrow occurs here
...
29 |     println!("{:?}", v_ref);
|                      ^^^^^ immutable borrow occurs here
30 | }
| - mutable borrow ends here

问题

  • 为什么这两个示例的编译方式不同?{vref}是否为方法(即split_at_mut(,但不是函数(即example2_inner(?为什么会这样呢?
  • 我想将example2_inner保留为独立的实用程序函数:我将如何更改示例 2 以适应这种情况?

你可以这样修复它:

v_ref = {
// move `v_ref` to a new variable which will go out of scope by the end of the block
let r = v_ref;
// Mutating & slice splitting moved to function
let (v_l, v_r) = example2_inner(r);
match len % 2 {
0 => v_l,
_ => v_r,
}
};

原因是v_1v_2都是对v_ref的引用,但是这些引用都比块活得更久,因为它们是从块中返回的。然后你尝试写信给v_ref,借用检查器不喜欢,因为周围仍然有这些实时引用。

v_ref分配给新变量r意味着v_ref不再有效 - 变量已被移动。实时引用,v_1v_2现在指的是r,而又是对v的引用,并且它只存在与块一样长的时间。您现在可以自由地写信给v_ref因为没有其他内容涉及它。


或者,您可以直接更新到 Rust Edition 2018,或启用非词法生命周期,这可以处理这种情况。

为什么这两个示例的编译方式不同?是可变的 强制实施引用移动语义(即来自 E1 的{vref}( 方法(即split_at_mut(,但不是函数(即example2_inner(? 为什么会这样呢?

实际上,这不是关于方法与函数,而是关于方法调用语法与函数调用语法。

每个方法调用都可以转换为UFCS(通用函数调用语法(,其形式通常为:

<Type as Trait>::method(args);

如果我们在版本1 中天真地尝试将{ v_ref }.split_at_mut(len / 2)的调用转换为 UFCS,我们最终会得到与版本 2 相同的错误:

<[_]>::split_at_mut({v_ref}, len / 2)

原因是上述内容等效于同样不会导致v_ref被移动到块中的东西:

<[_]>::split_at_mut({&mut *v_ref}, len / 2)

方法语法实际解析的是上述工作变体:

<[_]>::split_at_mut(&mut *{v_ref}, len / 2)

对于此变体,编译器推断v_ref确实应该移动到块中,因为编译器意识到方法调用所需的重新借用已经在{v_ref}上发生,因此它不会在v_ref上插入额外的多余重新借用。

现在我们知道了方法语法如何隐式解决您的问题,我们在版本 2 中为您的问题提供了替代解决方案:

example2_inner(&mut *{ v_ref });

最新更新