rusttemporary

Why are temporary values of Rust sometimes referential and sometimes not?


First of all, the following code is correct:

fn main() {
    let a = &get_i32();
    println!("{}", a);
}
fn get_i32() -> i32 {
    return 100;
}

But following code get error:

fn main() {
    let a;
    a = &get_i32();
    println!("{}", a);
}
error[E0716]: temporary value dropped while borrowed
 --> src/bin/rust_course.rs:8:10
  |
8 |     a = &get_i32();
  |          ^^^^^^^^^- temporary value is freed at the end of this statement
  |          |
  |          creates a temporary value which is freed while still in use
9 |     println!("{}", a);
  |                    - borrow later used here
  |
help: consider using a `let` binding to create a longer lived value
  |
8 ~     let binding = get_i32();
9 ~     a = &binding;
  |

For more information about this error, try `rustc --explain E0716`.

What are the essential differences between these two pieces of code?I understand that &get_i32() always returns a temporary value, so it should always report an error should all.

A similar problem:

fn main() {
    let s1 = &String::from("hello world");
    println!("{}", s1);
    let s2 = String::from("hello world").as_str();
    println!("{}", s2);
}
error[E0716]: temporary value dropped while borrowed
 --> src/bin/rust_course.rs:6:14
  |
6 |     let s2 = String::from("hello world").as_str();
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^         - temporary value is freed at the end of this statement
  |              |
  |              creates a temporary value which is freed while still in use
7 |     println!("{}", s2);
  |                    -- borrow later used here
  |
help: consider using a `let` binding to create a longer lived value
  |
6 ~     let binding = String::from("hello world");
7 ~     let s2 = binding.as_str();
  |

For more information about this error, try `rustc --explain E0716`.

What's the difference between s1 and s2?

There is a more similar problem:


fn main() {
    print(String::from("hello world").as_str());
}

fn print(str: &str) {
    println!("{}", str);
}

The above code is correct, but I can't understand why String::from("hello world").as_str() can be passed to functions but not assigned to variables.


Solution

  • What's the difference between s1 and s2?

    The answer is temporary lifetime extension. Sadly this is a somewhat informal process (and as the page notes it's subject to change), but broadly speaking it's that let used to bind a literal reference (so &something) can trigger a lifetime extension where something will get an implicit temporary. So let a = &get_i32(); and let s1 = &String::from("hello world"); benefit from this.

    a = &get_i32(); does not, because TLE only works with let.

    let s2 = String::from("hello world").as_str(); also does not, because temporaries get lifetime-extended to the end of a statement, so a chain essentially compiles to a sequence of calls in a block e.g.:

    let s2 = {
        let _temp = String::from("hello world");
        _temp.as_str()
        // _temp is dropped here so `as_str` becomes invalid
    };
    

    However note that the temporary lives to the end of the statement, in case of

    print(String::from("hello world").as_str());
    

    the statement lasts until the end of the print, in essence:

    {
        let _temp1 = String::from("hello world");
        let _temp2 = _temp1.as_str();
        print(_temp2)
    };
    

    which is perfectly fine.

    This is also why you can write things like:

        match &Some(String::new()).as_deref() {
            Some("") => println!("ok"),
            Some(_) => println!("??"),
            None => println!("ko"),
        }
    

    The entire match is a single statement, so the Option<String> temporary lives until its end, which means we can get references to both the inner and outer values, and work with an &Option<&str> to a value we never bound anywhere (this is nonsense code but it shows the principle and it's the first thing I thought about).

    However there are also cases where it causes issues if e.g. you try to match on a borrow then move the original value in one of the branch. This has gotten a lot less common with non-lexical lifetimes (and borrow checking) but it still occurs from time to time.