我有两个版本的函数,旨在做同样的事情。
版本 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_1
和v_2
都是对v_ref
的引用,但是这些引用都比块活得更久,因为它们是从块中返回的。然后你尝试写信给v_ref
,借用检查器不喜欢,因为周围仍然有这些实时引用。
将v_ref
分配给新变量r
意味着v_ref
不再有效 - 变量已被移动。实时引用,v_1
和v_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 });