为什么在使用 Rust 1.31 版本时,Rust 编译器可以突破借用规则?

6

我正在学习《Rust by Example》并运行来自“别名”页的代码:

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

最新的Rust编译器夜间版本(rustc 1.31.0-nightly (f99911a4a 2018-10-23))在Windows上运行此代码时不会出现编译错误。然而,在Rust Playground中,最新的Rust编译器夜间版本会提供预期的编译错误。

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

3个回答

9
当您使用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 edition switch

游乐场目前默认使用2015版,而在 Rust 1.31 稳定后,游乐场将把默认设置更改为2018版。

我该如何更改此示例以提供预期行为

您无法在Rust 2018中更改。在非词法生命周期之前,Rust编译器的智能程度不够。代码本身是安全的,但是编译器无法看到。现在编译器聪明到了这个地步,所以代码可以编译。没有理由设置编译器模式以使内在正确的代码无法编译。

您应该向 Rust by Example 提交问题报告,让他们知道他们的示例在 Rust 2018 中已经过时。


修改项目“Rust by examples”会很好,而且“书籍”可能需要更新代码安装以实现编译器的智能(酷炫)行为。 - Anatolii Kosorukov
@AnatoliiKosorukov:确实,我预计随着1.31的发布,将需要更新大量文档。不过,在它发布之前这样做有点过早。 - Matthieu M.
1
@MatthieuM。所有官方文档(包括 RBE)都与版本发布一起进行版本控制(例如:https://doc.rust-lang.org/nightly/rust-by-example/),但夜间版也没有更新。这意味着 RBE 将过时多个月 :-( - Shepmaster

5

解决方案在代码的前奏中。

数据可以被不可变地借用任意次数,但在不可变借用时,原始数据无法被可变借用。另一方面,一次只允许一个可变借用。原始数据只能在可变引用超出作用域后再次借用。

这意味着,您可以随意借用值,但每个作用域只能有一个可变借用。

您可能会想知道,为什么代码编译时使用了 #![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);

不起作用!记住这一点。


是的,没错。我不明白一件事——在playground上编译器按预期工作,不会编译这段代码。但在另一个地方,编译器的工作方式不同(所有代码都被编译)。 - Anatolii Kosorukov
@hellow - 如果你不知道的话,我们有一个SO聊天室,欢迎加入!(随时可以标记此评论为不再需要) - Shepmaster

3

这就是所谓的非词汇生命周期 - 这是一种目前正在进入语言中并且在2015版中不可用的功能。简而言之,使用它们后,引用将在不再使用时被丢弃,而不是在作用域结束时(如文档中所述)。您可以通过在mutable_borrow之后使用borrowed_point来检查此功能-即使使用NLL也应触发错误。


好的。请提供一个简短的示例(说明这种(正确)行为)。 - Anatolii Kosorukov
1
“not available in 2015 version” 不正确,您可以通过 #![feature(nll)] 启用该不稳定功能。 - hellow
我该如何更改这个例子以提供预期的行为 - “多个不可变引用,一个可变引用”?请提供一个示例来阐明您的建议。 - Anatolii Kosorukov
在源文件的第一行添加 #![feature(nll)] 后,它也会被编译。 - Anatolii Kosorukov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接