0

I have been learning rust lately and I am unable to figure out something

Why does this work

fn main() {
    let mut s = String::from("hello");
    println!("{}", &s);
    let r = &s;
    
    println!("{}", r);
    let x = &mut s;
    println!("{}", x);
    }

but this doesn't

fn main() {
    let mut s = String::from("hello");
    println!("{}", &s);
    let r = &s;
    let x = &mut s;
    println!("{}", r);
    
    println!("{}", x);
    }

It gives the following error:

cannot borrow `s` as mutable because it is also borrowed as immutable

If println takes the ownership of the string and doesn't return it, why does the first snippet work. Aren't we doing the same thing in both cases?

Sanskar Jethi
  • 544
  • 5
  • 17
  • 1
    *Aren't we doing the same thing in both cases?* - In the first case `r` is not used after the `println!()`, and the compiler is [smart enough](https://doc.rust-lang.org/edition-guide/rust-2018/ownership-and-lifetimes/non-lexical-lifetimes.html) to figure that out and behave as if it were in a scope that begins at `let r = ...` and ends right after `println!("{}", r)`. In the second snippet such transformation is not possible, as `x` is created before using `r`. – user4815162342 Apr 11 '21 at 07:42
  • 1
    `println!` surely doesn't take the ownership of the string. – Péter Leéh Apr 11 '21 at 07:44
  • 1
    You have two kinds of references: `&s`: a shared reference, and `&mut s`: an exclusive reference. The exclusive reference is the only one allowing mutation but it's exclusive. When you have an exclusive reference to something, you can't have another reference. – Denys Séguret Apr 11 '21 at 07:47
  • 1
    It's been a while but if I remember correctly, the first example used to crash as well with an old version of the borrowchecker. In 2018, they began changing it to the "polonius" implementation: https://github.com/rust-lang/polonius As of this change, variable scopes don't reach until the next curly bracket anymore but only to the last use of that variable. You can read up on it in excruciating detail in this post: https://smallcultfollowing.com/babysteps/blog/2018/04/27/an-alias-based-formulation-of-the-borrow-checker/ Edit: Sorry for the confusion, I kep hitting enter on accident – Zonico Apr 11 '21 at 07:48
  • 1
    @Zonico yes, in the old times, lifetime scopes were longer. Now the scope closes as soon as the variable isn't used anymore (see [non lexical lifetimes](https://stackoverflow.com/questions/50251487/what-are-non-lexical-lifetimes). – Denys Séguret Apr 11 '21 at 07:48

1 Answers1

1

Thank you for the suggestions.

To summarise, &mut s is an exclusive borrow, i.e. it doesn't allow any other kind of borrow in the same scope.

The first snippet works due to lexical scoping , i.e. , the compiler is smart enough to figure out that r is not being used after &mut s.

fn main() {
    let mut s = String::from("hello");
    println!("{}", &s);
    let r = &s;
    
    println!("{}", r);
    let x = &mut s;
    println!("{}", x);
    }

However, if we used this snippet instead of the first one. The code will give the same error as r is being used again &mut s which is not allowed in the language.

fn main() {
    let mut s = String::from("hello");
    println!("{}", &s);
    let r = &s;
    
    println!("{}", r);
    let x = &mut s;
    println!("{}", x);
    
    println!("{}", r);
    }
Sanskar Jethi
  • 544
  • 5
  • 17