为什么 Rust 编译器在使用 Rust 1.31 时可以打破借用规则?

Why can the Rust compiler break borrowing rules when using Rust 1.31?

我正在学习 Rust by Example 和 运行 来自 the "Alias" page:

的代码
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    let mut point = Point { x: 0, y: 0, z: 0 };

    {
        let borrowed_point = &point;
        let another_borrow = &point;

        // Data can be accessed via the references and the original owner
        println!(
            "Point has coordinates: ({}, {}, {})",
            borrowed_point.x, another_borrow.y, point.z
        );

        // Error! Can't borrow point as mutable because it's currently
        // borrowed as immutable.
        let mutable_borrow = &mut point;
        println!(
            "Point has coordinates: ({}, {}, {})",
            mutable_borrow.x, mutable_borrow.y, mutable_borrow.z
        );

        let mutable_borrow2 = &mut point;
        println!(
            "Point has coordinates: ({}, {}, {})",
            mutable_borrow2.x, mutable_borrow2.y, mutable_borrow2.z
        );

        // TODO ^ Try uncommenting this line

        // Immutable references go out of scope
    }

    {
        let mutable_borrow = &mut point;

        // Change data via mutable reference
        mutable_borrow.x = 5;
        mutable_borrow.y = 2;
        mutable_borrow.z = 1;

        // Error! Can't borrow `point` as immutable because it's currently
        // borrowed as mutable.
        //let y = &point.y;
        // TODO ^ Try uncommenting this line

        // Error! Can't print because `println!` takes an immutable reference.
        //println!("Point Z coordinate is {}", point.z);
        // TODO ^ Try uncommenting this line

        // Ok! Mutable references can be passed as immutable to `println!`
        println!(
            "Point has coordinates: ({}, {}, {})",
            mutable_borrow.x, mutable_borrow.y, mutable_borrow.z
        );

        // Mutable reference goes out of scope
    }

    // Immutable references to point are allowed again
    let borrowed_point = &point;
    println!(
        "Point now has coordinates: ({}, {}, {})",
        borrowed_point.x, borrowed_point.y, borrowed_point.z
    );
}

Playground

当 运行 此代码在 Windows 上使用 Rust 编译器的最新夜间构建时 不会 出现编译错误(rustc 1.31.0-nightly (f99911a4a 2018-10-23) ). Rust Playground 中 Rust 编译器的最新夜间构建 确实 提供了预期的编译错误。

这是为什么?为什么 Rust 编译器可以打破借用规则?我如何在本地修复此问题以获得预期的错误?

这就是所谓的非词汇生命周期 - 一项当前进入语言的功能,2015 版本中不可用。简而言之,一旦不再使用它们,引用就会被删除,而不是像以前一样在范围的末尾(并且如文档中所述)。您可以在 mutable_borrow 之后使用 borrowed_point 来检查这一点 - 即使使用 NLL,这也应该触发错误。

解决办法在代码前奏

Data can be immutably borrowed any number of times, but while immutably borrowed, the original data can't be mutably borrowed. On the other hand, only one mutable borrow is allowed at a time. The original data can be borrowed again only after the mutable reference goes out of scope.

这意味着,您可以随心所欲地借用一个值,但一次只能有一个可变借用(在一个范围内)。

您可能想知道,为什么代码编译时使用 #![feature(nll)]

原因是 'nll'(非词法生命周期)允许编译器为超出范围范围的借用创建生命周期({} 之间的所有内容)。现在它会看到,在使用借来的值进行打印后,它将不再被使用,因此借用的生命周期在 println!.

之后结束

这不会违反上述任何规则。您不能同时拥有多个可变借用,例如

let mut point = Point { x: 0, y: 0, z: 0 };

let p1 = &mut point;
let p2 = &point;

println!("Point has coordinates: ({}, {})", p1.x, p2.y);

不行!请记住这一点。

当您使用 Rust 1.31 创建新的 Cargo 项目时,您会自动选择使用 Rust 2018 版:

[package]
name = "example"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"

这会启用 non-lexical lifetimes,从而启用更智能的借用检查器。如果你想要旧的行为,你可以切换回 2015;这将导致您的代码产生预期的错误。不过,我鼓励您继续使用 2018 版。

Rust Playground 提供版本之间的切换:

Playground 目前默认为 2015 版,待 Rust 1.31 稳定后,Playground 将默认更改为 2018 版。

How I can change this example to provide expected behavior

不能 在 Rust 2018 中。在非词法生命周期之前,Rust 编译器不够智能。代码本身 是安全的 ,但编译器无法识别。编译器现在很聪明,所以代码可以编译。没有理由让编译器模式使本质上正确的代码无法编译。

您应该通过 Rust by Example 提出问题,让他们知道他们的示例在 Rust 2018 中不再有效。