35

Coming from C++, I'm rather surprised that this code is valid in Rust:

let x = &mut String::new();
x.push_str("Hello!");

In C++, you can't take the address of a temporary, and a temporary won't outlive the expression it appears in.

How long does the temporary live in Rust? And since x is only a borrow, who is the owner of the string?

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 3
    *In C++, you can't take the address of a temporary* — I don't know C++, but is this always true? [Does a const reference prolong the life of a temporary?](https://stackoverflow.com/q/2784262/155423) – Shepmaster Dec 05 '17 at 20:58
  • @Shepmaster `&x` takes the address of `x`, and I believe that this is never valid for temporaries. I should probably have compared this to creating a reference to a temporary, which is indeed possible, and even expands the lifespan of the temporary, so overall the behaviour is actually quite similar to what Rust does. – Sven Marnach Dec 05 '17 at 21:09
  • @SvenMarnach: You can perfectly take the address of a temporary in C++, `struct T { T* me() { return this; } };` will return you the address of the instance of `T` regardless of whether it's a temporary or not. Furthermore, C++ allows binding a const-reference or r-value reference to temporaries, and a reference it little more than a pointer in disguise. – Matthieu M. Dec 06 '17 at 07:56
  • @MatthieuM. Yeah, the comparison I made doesn't really make sense. I should have compared it to creating a reference in C++, instead of comparing to `&temp` just because the syntax looks similar. – Sven Marnach Dec 06 '17 at 09:31
  • 1
    @SvenMarnach: No worries :) The syntax is very similar, the effect is similar (since a reference is a pointer), so it seems like a very natural mistake. It's just that somehow Stroustrup decided some things where allowed and others not because of a gut feeling he had this would be error prone... and the lack of uniformity is perhaps more confusing in hindsight :) – Matthieu M. Dec 06 '17 at 10:22
  • In C++ you can perfectly do same thing: `int& v = 5; v+=1; std::cout << v;` Or even do such thing: `auto& v = classname().field_name; std::cout << v;` – Angelicos Phosphoros Oct 14 '20 at 11:39
  • I asked a concise Question for common rust error `error[E0716]` [error E0716: temporary value dropped while borrowed (rust)](https://stackoverflow.com/questions/71626083/error-e0716-temporary-value-dropped-while-borrowed-rust). It links back to this Question. – JamesThomasMoon Mar 26 '22 at 07:27

3 Answers3

37

Why is it legal to borrow a temporary?

It's legal for the same reason it's illegal in C++ — because someone said that's how it should be.

How long does the temporary live in Rust? And since x is only a borrow, who is the owner of the string?

The reference says:

the temporary scope of an expression is the smallest scope that contains the expression and is for one of the following:

  • The entire function body.
  • A statement.
  • The body of a if, while or loop expression.
  • The else block of an if expression.
  • The condition expression of an if or while expression, or a match guard.
  • The expression for a match arm.
  • The second operand of a lazy boolean expression.

Essentially, you can treat your code as:

let mut a_variable_you_cant_see = String::new();
let x = &mut a_variable_you_cant_see;
x.push_str("Hello!");

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Thanks! But would https://doc.rust-lang.org/1.48.0/reference/destructors.html#temporary-lifetime-extension actually be better link for this particular question? – khuttun Dec 11 '20 at 07:12
12

From the Rust Reference:

Temporary lifetimes

When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead

This applies, because String::new() is a value expression and being just below &mut it is in a place expression context. Now the reference operator only has to pass through this temporary memory location, so it becomes the value of the whole right side (including the &mut).

When a temporary value expression is being created that is assigned into a let declaration, however, the temporary is created with the lifetime of the enclosing block instead

Since it is assigned to the variable it gets a lifetime until the end of the enclosing block.

This also answers this question about the difference between

let a = &String::from("abcdefg"); // ok!

and

let a = String::from("abcdefg").as_str(); // compile error

In the second variant the temporary is passed into as_str(), so its lifetime ends at the end of the statement.

starblue
  • 55,348
  • 14
  • 97
  • 151
  • 1
    `In the second variant the temporary is passed into as_str(), so its lifetime ends at the end of the statement.`. Now I understand it, thank you! – neevek Nov 17 '19 at 09:21
  • [It is not a compile error](https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=b318ecba77f123dba751f754f1103038), and as a C/C++ user, it is doubly confusing. – arunanshub May 26 '21 at 08:36
  • 1
    @arunanshub It is indeed a compiler error. Check https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=9d36935f2d46ef9337d09945116b4fe5 In your code each `let a` declaration shadows the earlier declaration. – sateesh May 26 '21 at 14:14
  • @sateesh Sorry. I did not pay attention to situations where the variables would be used. Now I get it. Thanks. – arunanshub May 26 '21 at 20:03
10

Rust's MIR provides some insight on the nature of temporaries; consider the following simplified case:

fn main() {
    let foo = &String::new();
}

and the MIR it produces (standard comments replaced with mine):

fn main() -> () {
    let mut _0: ();
    scope 1 {
        let _1: &std::string::String; // the reference is declared
    }
    scope 2 {
    }
    let mut _2: std::string::String; // the owner is declared

    bb0: {                              
        StorageLive(_1); // the reference becomes applicable
        StorageLive(_2); // the owner becomes applicable
        _2 = const std::string::String::new() -> bb1; // the owner gets a value; go to basic block 1
    }

    bb1: {
        _1 = &_2; // the reference now points to the owner
        _0 = ();
        StorageDead(_1); // the reference is no longer applicable
        drop(_2) -> bb2; // the owner's value is dropped; go to basic block 2
    }

    bb2: {
        StorageDead(_2); // the owner is no longer applicable
        return;
    }
}

You can see that an "invisible" owner receives a value before a reference is assigned to it and that the reference is dropped before the owner, as expected.

What I'm not sure about is why there is a seemingly useless scope 2 and why the owner is not put inside any scope; I'm suspecting that MIR just isn't 100% ready yet.

ljedrz
  • 20,316
  • 4
  • 69
  • 97
  • Thanks! This shows that the equivalent code given by Shepmaster isn't just an analogy, but rather quite literally what's happening. Intermediate representations always tend to contain lots of seemingly useless bits that arre optimised out in later stages. I also like the mutable empty tuple that gets assigned at some random point. – Sven Marnach Dec 05 '17 at 21:56
  • 1
    @SvenMarnach if I'm not mistaken, the `_0` tuple is just `main()`'s return value; still not the most interesting piece of info, though :). – ljedrz Dec 05 '17 at 22:04