artzok
artzok

Reputation: 181

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.

Upvotes: 3

Views: 349

Answers (1)

Masklinn
Masklinn

Reputation: 42502

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.

Upvotes: 5

Related Questions