Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why did compiler not error on this mutable borrow when there is an immutable borrowed string slice reference still in scope?

I am learning Rust from The Rust Programming Language book available from No Starch Press but ran into an issue where the compiler did not behave as explained in the book in chapter 4 on p. 77.

Chapter 4 of the book is discussing ownership, and the example on p. 77 is similar to this without the final println!() in main() (I've also added comments and the function from p. 76 to create an MCVE). I also created a playground.

fn main() {
    let mut s = String::from("Hello world!");
    let word = first_word(&s);

    // according to book, compiler should not allow this mutable borrow
    // since I'm already borrowing as immutable, but it does allow it
    s.clear();

    // but of course I do get error here about immutable borrow later being
    // used here, but shouldn't it have errored on the clear() operation before
    // it got here?
    println!("First word of s is \"{}\"", word);
}

// return string slice reference to first word in string or entire string if
// no space found
fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[..i];
        }
    }

    &s[..]
}

I understand why the compiler throws an error where it currently does. But my understanding from the book is that it should have caused a compiler error when I tried to clear the string because I cannot borrow s as mutable because it is also borrowed as immutable, thus eliminating the possibility of the error I received (i.e., it should not compile even without my final println!()). But it compiles fine for me so long as I don't try to use the reference to word after the clear() operation.

The book is using Rust 1.21.0 (see p. 2) whereas I am using Rust 1.31.0—so this is likely a change that has been introduced to the compiler, but I am trying to understand why. Why is it better to error as it currently does versus where the book said it would error?

To be clear, I understand the errors themselves. I'm trying to understand why it doesn't throw a compiler error in the location the book says it should (i.e., why the change in compiler behavior?).

like image 759
Dan Avatar asked Dec 28 '18 05:12

Dan


1 Answers

This is a change due to the non-lexical lifetimes, the update made in the latest versions of Rust (stabilized in the 2018 edition introduced with Rust 1.31, if I'm not mistaken).

In earlier versions of Rust (including the one on which the book is based), any reference was supposed to be alive for the whole scope, where it was created (that is, until the end of the enclosing braces). If you removed the line using word and try to compile the code on the old version, it would issue the same error — "borrowed as mutable while borrowed as immutable".

Now, the borrow checker tracks whether the reference is really used. If you weren't using word after s.clear(), it would be assumed that the immutable reference to s can be safely dropped before s.clear() takes a mutable one, so, as you've mentioned, this code will be safely compiled. When the println! is there, the borrow checker sees that the immutable and mutable borrows' scoped are intersecting, and tells you exactly that — note that the error is divided in three parts:

  1. start of immutable borrow,
  2. start of mutable borrow,
  3. usage of immutable borrow.
like image 128
Cerberus Avatar answered Oct 17 '22 23:10

Cerberus