Why is it legal to borrow a temporary?
Asked Answered
M

3

38

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?

Maynard answered 5/12, 2017 at 20:36 Comment(7)
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?Blankbook
@Blankbook &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.Maynard
@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.Trifurcate
@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.Maynard
@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 :)Trifurcate
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;Dextro
I asked a concise Question for common rust error error[E0716] error E0716: temporary value dropped while borrowed (rust). It links back to this Question.Carangid
B
39

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:

Blankbook answered 5/12, 2017 at 20:53 Comment(1)
Thanks! But would doc.rust-lang.org/1.48.0/reference/… actually be better link for this particular question?Vivianviviana
U
13

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.

Uriiah answered 12/6, 2018 at 13:44 Comment(4)
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!Muse
It is not a compile error, and as a C/C++ user, it is doubly confusing.Laddy
@Laddy It is indeed a compiler error. Check play.rust-lang.org/… In your code each let a declaration shadows the earlier declaration.Geostrophic
@Geostrophic Sorry. I did not pay attention to situations where the variables would be used. Now I get it. Thanks.Laddy
S
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.

Squama answered 5/12, 2017 at 21:46 Comment(2)
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.Maynard
@SvenMarnach if I'm not mistaken, the _0 tuple is just main()'s return value; still not the most interesting piece of info, though :).Squama

© 2022 - 2024 — McMap. All rights reserved.