Why `Option<&T>` will cause "temporary value dropped while borrowed"?

When T in Option<&T> is pointer, like String/Box/Rc, I will get this kind of error: "temporary value dropped while borrowed".

let a = Some(&Rc::new(7));
println!("{:?}", a);

let a = Some(&Box::new(7));
println!("{:?}", a);

let a = Some(&"hello".to_string());
println!("{:?}", a);

then I got these errors:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:30:19
   |
30 |     let a = Some(&Rc::new(7));
   |                   ^^^^^^^^^^ - temporary value is freed at the end of this statement
   |                   |
   |                   creates a temporary which is freed while still in use
31 |     println!("{:?}", a);
   |                      - borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

Thx.

That happens because you create the Rc as a temporary (in the argument position of the Some constructor call). After execution of the constructor there is no owner for that Rc anymore so it is dropped. But you try to keep a borrow to the dropped Rc which the compiler does not allow of course.
Two straight forward solutions: Either put an owned value into the Option type or create an owner of the Rc that outlives the Option:

// Option 1: owned value
let a = Some(Rc::new(7));

// Option 2: owner for the Rc
let o = Rc::new(7);
let a = Some(&o);
// o needs to outlive a in that case
1 Like

And the less known option 3:

match Some(&Rc::new(7)) { a => {
    println!("{:?}", a);
}}

So, this "problem" arises whenever you have following pattern:
f(&g())

  1. First, g() creates an owned value, but there is no explicit owner to bind it to (Option 2 shows that if you explicit the owner then all is fine since Rust no longer has to guess).

  2. So Rust creates a hidden temporary variable/binding so that the created value does not immediately die/gets dropped.

  3. This anonymous variable is dropped as soon as possible! This is what allows you to write code such as

    fn just_read_it_for_a_moment<'a> (
       x: &'a impl Debug + 'a,
    ) -> () // <- no 'a !
    {
         println!("Reading {:?}", x);
         println!("Done.");
    }
    
    fn main ()
    {
      let x = just_read_it_for_a_moment(
        &String::from("hi")
      );
    }
    

    Just to be clear the fact the the above works is already impressive; Rust actually does something like the following:

    fn main ()
    {
       let x = {
         let anon = String::from("hi");
         let ret = just_read_it_for_a_moment(&anon);
         ::std::mem::drop(anon);
         ret
      };
    }
    

    There is no problem since just_read_it_for_a_moment return value does not need to maintain the borrow on its input.

  4. But the "ASAP drop" is problematic when the function does need to maintain the borrow. In your case:

    fn Some<'a, T: 'a> (x: &'a T) -> Option<&'a T>
    

    (Notice the input 'a being also in the ouput)

Hence the solutions 2 and 3:

  • Solution 2: do not let Rust handle the bindings and lifetimes for you by not using anonymous bindings.

  • Solution 3: "abuse" the fact that within a match expression, the matched scope is only dropped at the end of the whole match (i.e., not ASAP), and use a match with a single irrefutable pattern instead of a let binding.

4 Likes

&Rc is not a pointer, in the same sense that in C pointers are not integers. Technically it may be represented as such in memory, but semantics and usage is different.

Logically & is more like a temporary read-only lock on the data.
You can only borrow data that already has a permanent storage somewhere for the entire duration of the borrow.

In your case Rc is not stored anywhere, and isn't owned by anyone. Borrowed reference can't exist without its owned counterpart also existing.

Remember Rust doesn't have GC so it will never just magically figure out how and where to store data for you. Even when it's as simple as assigning it to a variable.

Hey your explanation gave me an idea to better explain & and &mut, they're both like an RwLock, but in the case of the RwLock, it has to deal with where the data actually is in memory while with standard references the user has to deal with where to place the data, meaning that RwLock is very similar to pre-existing rust semantics (Apart from the overhead and the differences I just discussed).

1 Like

You may be interested in this blog which expands on the idea that references are locks.