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

  • A+
Category:Languages

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 <an.devloper@example.com>"] 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:

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

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.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: