难以理解 Rust 借用系统

Troubles understanding rust borrowing system

我正在经历 rustlings exercises 但我似乎不明白借用语义是如何工作的。

我创建了这个简单的示例(检查 playground):

fn main() {
    let mut vec0: Vec<i64> = Vec::new();

    let vec1 = &mut vec0;

    println!("{} has content `{:?}`", "vec0", vec0);

    println!("{} has content `{:?}`", "vec1", vec1);
}

出现以下错误:

   Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `vec0` as immutable because it is also borrowed as mutable
  --> src/main.rs:9:47
   |
7  |     let vec1 = &mut vec0;
   |                --------- mutable borrow occurs here
8  | 
9  |     println!("{} has content `{:?}`", "vec0", vec0);
   |                                               ^^^^ immutable borrow occurs here
10 | 
11 |     println!("{} has content `{:?}`", "vec1", vec1);
   |                                               ---- mutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` due to previous error

我虽然在借用一个值时原始绑定保留了所有权,也就是说,在 let vec1 = &mut vec0; vec0 之后应该仍然拥有 vec。

此外,我不明白为什么在 println!("{} has content `{:?}`", "vec0", vec0) 的第 9 行会发生不可变借用。 vec 不是仍然属于 vec0 吗?

不能同时对同一个值有两个可变引用。如果您像这样重新排列函数中的行:

fn main() {
    let mut vec0: Vec<i64> = Vec::new();
    println!("{} has content `{:?}`", "vec0", vec0);
    let vec1 = &mut vec0;
    println!("{} has content `{:?}`", "vec1", vec1);
}

,不会有问题,因为在那个变体 vec0 中,在创建对它的可变引用后就不会访问它。

如果 vec1 是一个普通的(不可变的)引用,即使你原来的行顺序也不会有问题,因为有多个不可变的(read-only)引用不是问题:

fn main() {
    let mut vec0: Vec<i64> = Vec::new();
    let vec1 = &vec0;
    println!("{} has content `{:?}`", "vec0", vec0);
    println!("{} has content `{:?}`", "vec1", vec1);
}

引用最常用于函数参数,当您不想移动一个值,而只是传递一个引用给它时。只有当你需要改变函数内部的底层值时,你才需要使引用mutable.

你说得对 vec0 仍然是底层内存的所有者。

println! 需要借用 vec0 的原因是 println! 宏需要能够从向量中读取以便打印它,因此它需要一个 read-only参考。 read-only 引用自动创建并超出此宏的范围。

您在这里遇到编译器错误的原因是您违反了 borrow checker rules:

At any given time, you can have either one mutable reference or any number of immutable references.

了解 println 如何引用其参数的最佳方法是使用 cargo-expand,它将显示宏展开的结果。对于您的代码,它扩展为

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
fn main() {
    let mut vec0: Vec<i64> = Vec::new();
    let vec1 = &mut vec0;
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["", " has content `", "`\n"],
            &match (&"vec0", &vec0) { // Immutable borrow
                _args => [
                    ::core::fmt::ArgumentV1::new(_args.0, ::core::fmt::Display::fmt),
                    ::core::fmt::ArgumentV1::new(_args.1, ::core::fmt::Debug::fmt),
                ],
            },
        ));
    };
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["", " has content `", "`\n"],
            &match (&"vec1", &vec1) { // Immutable borrow
                _args => [
                    ::core::fmt::ArgumentV1::new(_args.0, ::core::fmt::Display::fmt),
                    ::core::fmt::ArgumentV1::new(_args.1, ::core::fmt::Debug::fmt),
                ],
            },
        ));
    };
}

如您所见,println 对其参数采用 不可变引用 。(这意味着 vec0 stills 拥有向量)。但这里的问题是 Rust 在编译时强制执行“多个读者或单个作者”规则。只要存在对某个值的可变引用,您就不能使用所有者,直到可变引用消失。同样,只要存在对值的多个共享引用,即使它的所有者也不能修改它。

例如,这将编译,

fn main() {
    let mut vec0: Vec<i64> = Vec::new();

    {
        let vec1 = &mut vec0;
        println!("{} has content `{:?}`", "vec1", vec1);
    } // The mutable reference goes out of scope here.

    println!("{} has content `{:?}`", "vec0", vec0);
}