-1

I need advice about borrowing in this code, specifically the last 4 lines.

I realize after the closing curly brace, variable e cannot be printed outside the scope and will cause an error if variable c (inside scope) is NOT commented out. Similarly, variable c cannot be printed inside scope and will cause an error if variable e (outside scope) is NOT commented out

In summary,

  • Line 1 removed, Line 2 stays: No Error
  • Line 1 stays, Line 2 removed: No Error
  • Line 1 & 2 stays: Error
fn main() {
    let mut c = String::from("hello");
    println!("c1 {}", c);

    c = String::from("hello2");
    println!("c2 {}", c);

    let e = &mut c;
    println!("e1 {}", e);

    e.pop();

    println!("e2 {}", e);
    // println!("c {}", c); //*! <- Error Immutable Borrow

    {
        e.pop();
        e.pop();

        println!("e3 {}", e);
        println!("c3 {}", c); //*? Line 1: Error Immutable Borrow when e4 is NOT commented out
    }

    // println!("e4 {}", e); //*? Line 2: Error Immutable Borrow when c3 is NOT commented out
    println!("c4 {}", c);
}

The error:

error[E0502]: cannot borrow `c` as immutable because it is also borrowed as mutable
  --> src/main.rs:14:22
   |
8  |     let e = &mut c;
   |             ------ mutable borrow occurs here
...
14 |     println!("c {}", c);
   |                      ^ immutable borrow occurs here
...
17 |         e.pop();
   |         - mutable borrow later used here

What is the reason for this behavior?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Hendry Lim
  • 1,929
  • 2
  • 21
  • 45
  • where is `c` defined ? this doesn't give error for me: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=084ca3c21b04a81705d1b05c114a70c1 – Psidom Mar 06 '20 at 03:35
  • hi. I have added in where c is defined and also expanded on the description of the error. Thanks – Hendry Lim Mar 06 '20 at 04:16
  • Does this answer your question? [What are non-lexical lifetimes?](https://stackoverflow.com/questions/50251487/what-are-non-lexical-lifetimes) – Jmb Mar 06 '20 at 07:38
  • Thanks @Jmb, I check it out – Hendry Lim Mar 06 '20 at 10:39
  • Thanks @Jmb, i just read through the post you recommended. In truth, I m unable to digest everything as Im a beginner in Rust. However, it seems non-lexical lifetime indeed could be the issue in this case. Thanks – Hendry Lim Mar 06 '20 at 11:30

1 Answers1

1

As a general rule a borrow will last until it goes out of the scope it was defined. In programming a what we normally refer to as a "scope" is formally known as a Lexical Scope.

Before rust got Non Lexical Lifetimes explained thoroughly in the link provided by Jmb, your example wouldn't compile in either case. This actually makes borrowing was a bit easier to learn since rules are more consistent but a lot more annoying in practice.

You can try this yourself by using the Godbolt Compiler Explorer setting the rustc version < 1.31. An example of this can be found here. As you see that gives a compiler error on version 1.30.

Example 1

let mut a = String::from("hello");

let b = &mut a;
println!("b1 {}", b);
println!("a1 {}", a);

One of the ways to solve that earlier was to add a new lexical scope like this:

Example2

let mut a = String::from("hello");
{
let b = &mut a;
println!("b1 {}", b);
}
println!("a1 {}", a);

In this case the exclusive &mut a borrow would only last until the end of the lexical scope it was in.

Now, it's obvious from looking at example 1 that we don't actually use the &mut a borrow after println!("b1 {}", b);, so intuitively for us as programmers having to insert a scope only to explain this to the compiler seems overly strict.

In addition, if we declare any other variable after let b = &mut a; we want to use after it's scope that won't work so we need to adapt and alter the way we write our programs only to please the borrow checker.

Rust later got what's called Non Lexical Lifetimes, which is just what the label says. Lifetimes that is not tied strictly to the Lexical Scope it's in.

Now, the compiler checks when you stop using a borrow and inserts an invisible "lifetime scope" (or at least you can think of it like that) just as in example 2, but without the downsides of introducing a true lexical scope.

That's why you're experiencing a bit of "magic" that might come off as a bit surprising in your example. I've tried to explain by showing the scope the compiler will use to check if a shared & borrowed of the pointer to c is allowed or not.

However, trust me, this little addition to the learning curve about the borrow checker is absolutely worth it once you realize some of the hoops you need to jump through without it.

let mut c = String::from("hello"); 
println!("c1 {}", &c);                        
c = String::from("hello2");
println!("c2 {}", &c);
                //  "lifetime scope" of `&mut c`
let e = &mut c; //{ |----------------------
println!("e1 {}", &e);   //        |      |            
                         //        |      |                        
e.pop();                 //        |      |                            
                         //        |      |                                
println!("e2 {}", &e);   //        |      |                          
// println!("c {}", &c); //        |      | (e3)                               
                         //        |      |                      
{                        //        | (e4) |                   
  e.pop();               //        |      |               
  e.pop();               //        |      |      
                         //        |      | 
  println!("e3 {}", &e); // }<-----|-------
  println!("c3 {}", &c); //        |                                            
}                        //        |                                                
                         //        |
// println!("e4 {}", &e);// }<------
println!("c4 {}", &c);

I've explicitly shown that println! takes a borrow, and doesn't take ownership to make the code a bit clearer when talking about lifetimes and borrows.

Normally passing in c instead of &c to a function passes on the ownership, but println! is a macro (not a function) which, in this case, only re-borrows it. The distinction can be important when learning about borrows and lifetimes.

cfs
  • 1,304
  • 12
  • 30