Why is it legal to borrow a temporary?

2019-01-08 00:31发布

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?

3条回答
甜甜的少女心
2楼-- · 2019-01-08 01:04

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.

查看更多
淡お忘
3楼-- · 2019-01-08 01:23

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 lifetime of temporary values is typically

  • the innermost enclosing statement; the tail expression of a block is considered part of the statement that encloses the block, or
  • the condition expression or the loop conditional expression if the temporary is created in the condition expression of an if or an if/else or in the loop conditional expression of a while expression.

When a temporary rvalue is being created that is assigned into a let declaration, however, the temporary is created with the lifetime of the enclosing block instead, as using the enclosing statement (the let declaration) would be a guaranteed error (since a pointer to the temporary would be stored into a variable, but the temporary would be freed before the variable could be used). The compiler uses simple syntactic rules to decide which values are being assigned into a let binding, and therefore deserve a longer temporary lifetime.

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:

查看更多
祖国的老花朵
4楼-- · 2019-01-08 01:26

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.

查看更多
登录 后发表回答