为什么我可以为方法的“&self”参数强制引用移动语义,而不是函数参数?
Why can I force reference move semantics for `&self` parameter of method, but not function parameters?
我有一个函数的两个版本,它们的目的是做同样的事情。
版本 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
被覆盖以保存其中一个子切片
*(Note - 我们通过 moving v_ref
, [=22= 避免了有两个可变引用的问题])
(关于代码的意图 - 这个切片缩减旨在循环重复;但是这个细节不会影响问题)
版本 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
问题
- 为什么这两个示例的编译方式不同?可变引用移动语义(即来自 E1 的
{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,或者启用非词法生命周期,这可以处理这种情况。
Why don't these two examples compile the same way? Are mutable
reference move semantics (i.e., the {vref}
from E1) enforced for
methods (i.e. split_at_mut
), but not functions (i.e., example2_inner
)?
Why is this the case?
其实不是方法vs函数,而是方法调用语法vs函数调用语法
每个方法调用都可以翻译成UFCS(通用函数调用语法),一般是这样的形式:
<Type as Trait>::method(args);
如果我们天真地尝试将 { v_ref }.split_at_mut(len / 2)
的调用转换为版本 1 中的 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 });
我有一个函数的两个版本,它们的目的是做同样的事情。
版本 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
制作两个子切片
*(Note - 我们通过 moving v_ref
, [=22= 避免了有两个可变引用的问题])
(关于代码的意图 - 这个切片缩减旨在循环重复;但是这个细节不会影响问题)
版本 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
问题
- 为什么这两个示例的编译方式不同?可变引用移动语义(即来自 E1 的
{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,或者启用非词法生命周期,这可以处理这种情况。
Why don't these two examples compile the same way? Are mutable reference move semantics (i.e., the
{vref}
from E1) enforced for methods (i.e.split_at_mut
), but not functions (i.e.,example2_inner
)? Why is this the case?
其实不是方法vs函数,而是方法调用语法vs函数调用语法
每个方法调用都可以翻译成UFCS(通用函数调用语法),一般是这样的形式:
<Type as Trait>::method(args);
如果我们天真地尝试将 { v_ref }.split_at_mut(len / 2)
的调用转换为版本 1 中的 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 });