Why a borrow doesn't give a compiler error

I have this code:

#[derive(Debug)]
struct S {
    value: String
}

impl S {
    fn weird_borrow(&self) {
        print!("{:?}", self)
    }
}

fn main() {
    let mut s = S {value: String::from("...")};
    s.weird_borrow();
}

So from what I read in the rust book (which I'm currently learning), it said that there can be either a single mutable borrow OR 1 or more immutable borrow in the same scope. However, in this code, I make a mutable variable s, then immutably borrow it.
Is the reason I don't get a compile error in the second line of main is because it's not a borrow but rather a "move", meaning s owns it here?
If so, it means I CAN mutably own something, and in the same scope immutably give it to a borrower?

The borrow checker is not exactly scope-based -- this finer-grained reasoning is what we got from non-lexical lifetimes (NLL). If you had a &mut s followed by &s (or your &self call) in the same scope, this would be fine as long as the mutable reference was no longer used, as if it were "scoped" to its last use.

But your example is not even that -- the shared reference is borrowed directly from the mutable source, which is fine. It does mean that s can't be modified while that borrow is active, but s.weird_borrow() only holds that for the duration of the call. The compiler checks all of this automatically, so don't worry about remembering any weird rules here. (At least as long as you avoid unsafe raw pointers.)

Here's an example that fails:

fn main() {
    let mut s = S {value: String::from("...")};
    let rs = &s;
    rs.weird_borrow();
    s.value = "foo".to_string();
    rs.weird_borrow();
}

(playground)

error[E0506]: cannot assign to `s.value` because it is borrowed
  --> src/main.rs:16:5
   |
14 |     let rs = &s;
   |              -- borrow of `s.value` occurs here
15 |     rs.weird_borrow();
16 |     s.value = "foo".to_string();
   |     ^^^^^^^ assignment to borrowed `s.value` occurs here
17 |     rs.weird_borrow();
   |     ----------------- borrow later used here

... but if you remove the last call, then NLL lets rs release its borrow earlier, so s.value can be modified after all.

6 Likes

And that's not a problem, since mutable binding (or mutable variable) is not the same as mutable borrow.

let mut is, essentially, just a lint; it doesn't change the semantics of the code, other then allowing some kind of actions (and triggering a warning if there's none done). If the program already compiles, we can replace each let with let mut, and its behavior doesn't change, either at compile-time (except the aforementioned warnings) and in runtime.

&mut T, on the other hand, is a different type from both T and &T, and that's what is checked by the borrow checker.

6 Likes

Or, to put it even more simply: in let mut s = ..., the variable s is not a reference to some value, it is the value itself.

2 Likes