String slice vs as_ref

As far as I understand after reading a similar thread, the code

fn main() {
    let s: &str = String::from("abc").as_ref();
    println!("{}", s);
}

does not compile because String::from("abc") creates a temporary value whose lifetime is limited at its statement (then there is actually no way to use s). But why does the following code compile:

fn main() {
    let s = &String::from("abc")[..];
    println!("{}", s);
}

Is the lifetime of String::from("abc") expanded in this case?

2 Likes

Shared references are special cased by the compiler to work that way for ease of use, as_ref can’t be special cased because it runs user defined code.


Disclaimer:
I think this is how it works, but I have not worked on the compiler so I may be wrong.

Good question, actually, naively I would have expected the second example to be rejected too.

See https://doc.rust-lang.org/1.30.0/reference/expressions.html#place-expressions-and-value-expressions.
expr[..] is a “place expression” while expr.as_ref() is a “value expression”.

2 Likes

I did some language lawyering on this question a while ago:

3 Likes

Thanks all for helps,

After reading the responses, as far as I understand, the problem is about “place/value expressions” and “temporary lifetimes”. I just try to summary the details from responses.

First, String::from("abc"), which is a value expression, needs a temporary location for its evaluation, as the reference says:

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…

The lifetime of this temporary location is the statement itself, but it would be “promoted” to the enclosing block by a let, as the reference says:

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…

That may explain why

let abc = String::from("abc");
let s = s.as_str();            // or s: &str = s.as_ref()

compiles, but

let s = String::from("abc").as_str();

does not. It’s because in the former, the lifetime of String::from("abc") is promoted to the enclosing block thanks to let abc =. However in the later, the temporary memory location is just passed into as_ref(), the function cannot return a valid reference because the lifetime of the passed temporary memory is limited by the statement.

Note that the error comes from the trying to make a reference let s = ..., if we do not do that, the following statement

String::from("abc").as_str();

compiles just fine.

Second, &String::from("abc"), which is a place expression, still has a temporary location (there is no magic) but the location is “already promoted” by the borrow operator &:

The left operand of … is a place expression context, as is the single operand of a unary borrow

That means, &String::from("abc") itself

… represents a memory location

whose lifetime is the enclosing block (!?), then there’s no problem in passing it into the indexing operator [..].

Note that,

let s: &str = &String::from("abc")[..];

is actually

let s: &str = (&String::from("abc"))[..];

and not

let s: &str = &(String::from("abc")[..]);

I am still uncomfortable about the place expression, so correct me if I’m wrong.

2 Likes