I am studying Rust by Example and running code from 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
I do not get compilation errors when running this code on Windows with the latest nightly build of the Rust compiler (rustc 1.31.0-nightly (f99911a4a 2018-10-23)
). The latest nightly build of the Rust compiler in the Rust Playground does provide the expected compilation errors.
Why is this? Why can the Rust compiler break borrowing rules? How can I fix this locally to get the expected errors?
When you create a new Cargo project with Rust 1.31, you automatically opt-into Rust edition 2018:
[package]
name = "example"
version = "0.1.0"
authors = ["An Devloper <[email protected]>"]
edition = "2018"
This turns on non-lexical lifetimes, which enables a smarter form of the borrow checker. If you want the old behavior, you can switch back to 2015
; this will cause your code to produce the expected errors. I would encourage you to continue using the 2018 edition, however.
The Rust Playground offers switching between editions:
The Playground currently defaults to edition 2015, and after Rust 1.31 is stabilized, the Playground will change its default to edition 2018.
How I can change this example to provide expected behavior
You cannot in Rust 2018. Before non-lexical lifetimes, the Rust compiler was simply insufficiently intelligent. The code itself is safe, but the compiler could not see that. The compiler is now that smart, so the code compiles. There is no reason to have a compiler mode to make intrinsically correct code fail to compile.
You should file an issue with Rust by Example, letting them know that their example is no longer valid in Rust 2018.
The solution is in the prelude of the code.
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.
Which means, you can borrow a value as often as you like, but only have one mutable borrow at a time (in a scope).
You may be wondering, why the code compile with #![feature(nll)]
.
The reason is that 'nll' (non-lexical-lifetimes) allows the compiler to create lifetimes for borrows beyond the scope of scopes (everything between {
and }
). It will now see, that after the use of the borrowed value for printing it won't be used anymore, so the lifetime of that borrow ends right after the println!
.
This won't break any rules as stated above. You can't have more than mutable borrow at the same time, e.g.
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);
won't work! Keep that in mind.
This is so-called non-lexical lifetimes - a feature currently coming into language and not available in 2015 version. In short, with them the reference is dropped once it is not used anymore, and not at the end of scope, as before (and as it is stated in docs). You can check this by using borrowed_point
after mutable_borrow
- this should trigger the error even with NLL.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With