为什么我可以为方法的“&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);
}

本质上:

*(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

问题

你可以这样修复:

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,或者启用非词法生命周期,这可以处理这种情况。

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 });