Need clarity on ownership

I'm just beginning with Rust, and going through references. 'The Book' says there can be only one mutable reference or many immutable references. But what surprises me is the below code executes, Can someone explain why, please?

fn main() {
    let mut s = String::from("Hello World");

    let word = first_word(&s);
    let another_word = first_word(&s);
    let allnew_word = &mut s;	
    println!("{}", allnew_word);
}

fn first_word(s : &String) -> &str {
    let bytes = s.as_bytes();

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

It is possible to choose lifetimes for this program in a way that the immutable references and the mutable reference never exist at the same time. Here is one possible choice that works:

let mut s = String::from("Hello World");

let word = first_word(&s);         // ─┐lifetime for word (&)
                                   //  │
let another_word = first_word(&s); //  │ ─┐lifetime for another_word (&)
                                   // ─┘ ─┘
let allnew_word = &mut s;          // ─┐lifetime for allnew_word (&mut)
                                   //  │
println!("{}", allnew_word);       //  │
                                   // ─┘

If you write a program that can't be broken down this way, for example by using word / another_word after allnew_word has been created, then you will find that it does not compile.

2 Likes

I don't know if the Book already covers the non-lexical-lifetimes (NLL). Before Rust 2018 lifetimes were evaluated depending on their scope which means your code wouldn't compile since the lifetimes of word and allnew_word overlap.
However, since Rust 2018 NLL is used. Now liftimes are evaluated depending on their usage which means your code compile because you don't use the immutable references after borrowing mutable. For example the following code shouldm't compile:

fn main(){
  let mut s = String::from(“Hello World”);

  let word = first_word(&s);
  let another_word = first_word(&s);
  let allnew_word= &mut s;	
  println!("{}",allnew_word);
  println!("{}", word); // <-- overlapping lifetime of immutable and mutable references
}
1 Like

We're still figuring out the best way to cover NLL. In some sense, we shouldn't have to discuss it at all-- NLL makes code Just Work the way most folks expect it to. However, as OP pointed out, it gets tricky to explain what "at the same time" or "in the same scope" means in regards to when you're allowed to have mutable references or not.

I'd love thoughts from folks learning Rust post-NLL!

2 Likes

I'm surprised by and disagree with your last sentence. Rust's approach to memory safety is unique among programming languages, and lifetimes are a central part of that approach. People who are new to Rust, even people who are very experienced in other languages, don't have intuitions about lifetimes gained from years of experience, because there is no other source of that experience. So unless the intended audience for your book consists of people who already know Rust, which I doubt, then it really does need to be discussed.

And I'd point out that NLL represent a complication of the lifetime rules, precisely because the pre-NLL rules were too conservative and rejected correct programs because they didn't take the flow of control into account. So I'm not surprised by your first sentence -- that you are still figuring this out. I don't think this is easy to explain and I look forward to seeing what you produce.

This is what I meant by "makes code Just Work the way most folks expect it to"-- that correct programs are accepted. Sorry if I was unclear!

@carols10cents. I strongly disagree too. The practice of not explaining things because 3 references are intuitive and "Just Works" only dis-serve developers because it is only matter of time when this lack of understanding will bite in the rear in real world situation. And when after painful days of struggle you discover that the book left something out to make language look simpler, well... I felt unhappy.

1 Like

The book didn't really leave anything out. The book just talks about
scope in an extremely light way. It turns out that some programmers
think in lexical scope, some do not. Pre-NLL, when we talked a lot
about lexical scope, a lot of people found it confusing. Now, post
NLL, when we are a bit more vague about scope, some people find it
easier, but some, like you, also find it more confusing. It is
impossible to satisfy everyone. On the whole, the number of people who
are confused has gone down, though.

2 Likes

I completely agree that we should be explaining ideas as developers need that understanding. The question is what is the right way and the right times to get into all the details for all the different developers reading the book, and that's what we're still figuring out.

I'd love post-NLL examples of real world situations where NLL made a situation confusing and led to painful days of struggle!

And again, if anyone reading this is in the process of learning Rust post-NLL, I'd love to chat with you about your experiences as you represent our target audience going forward.

1 Like

For example, @Nagaraj_hegde if the book had said this in the section where mutable references are introduced, would that have made your example less surprising that it compiled?

4 Likes

When I show Rust to friends / colleagues for the first time, I often find myself switching to Edition 2015 since lexical scopes are easier to reason about and trigger compiler errors with :sweat_smile:

It is obviously hard to make a single book that pleases/matches everyone's thinking patterns, but I actually like @carols10cents example above, it would be nice to have at least one mention to scopes not being lexical ones (most people would not expect a static analysis to be that smart about scopes as NLL is, to be honest)

This sounds lot better. I know, may be that all would have made sense once someone had developed an understanding of lifetimes. However, for me, one who was reading the book in a sequential order and to the one who considers 'The Book' as primary source of understanding of the language, I guess such elucidations are important. Especially when you have spent some decent efforts explaining few general programming concepts, which I really appreciate very much, it is paramount that you spend efforts in covering the language nuances. I think, yes, such clear coverage right at the time when you layout the rules in the ownership chapter is important, both for understanding and to save lot of experimental efforts to figure out things. And, you know, for a moment I even got doubtful about cargo implementation of the language, I guess which is not a great impression for newcomers. But on the other hand, I really love you all folks, both development team and the user members, wow, what an active folks you guys are! This really made me feel welcome to the language than anytime before!

3 Likes