unnamed eng
unnamed eng

Reputation: 328

What is the difference between `temp_string.as_str()` and `&temp_string`?

This code triggers a compiler error:

fn main() {
    let s = "FOO";
    let s_lower = s.to_lowercase().as_str();

    println!("{}", s_lower);    
}
error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:3:19
  |
3 |     let s_lower = s.to_lowercase().as_str();
  |                   ^^^^^^^^^^^^^^^^         - temporary value is freed at the end of this statement
  |                   |
  |                   creates a temporary value which is freed while still in use
4 |
5 |     println!("{}", s_lower);    
  |                    ------- borrow later used here

However, this code does not:

fn main() {
    let s = "FOO";
    let s_lower = &s.to_lowercase();

    println!("{}", s_lower);    
}

Why? Is it because &temp_string is a &String which is different from &str? If so, how?

Upvotes: 0

Views: 728

Answers (2)

unnamed eng
unnamed eng

Reputation: 328

Several links from @kmdreko & @sudo are helpful.

I understand why s.to_lowercase().to_str() does not compile but wondered why &s.to_lowercase() does not have the same problem. This is called Temporary lifetime extension, the doc says:

The temporary scopes for expressions in let statements are sometimes extended to the scope of the block containing the let statement. This is done when the usual temporary scope would be too small, based on certain syntactic rules. For example:

let x = &mut 0;
// Usually a temporary would be dropped by now, but the temporary for `0` lives
// to the end of the block.
println!("{}", x);

I find this special rule a bit arbitrary. Here's a related discussion on why it does not apply to temp.as_bytes() which I resonate.

Surprisingly there's a RFC which seemed to want to make this more regular, but somehow is not applicable here.

This article explains why Temporary Lifetime Extension is desired in some cases.

I found this older rust references helpful with understanding temporary values as well, with a lot of examples.

Upvotes: 1

0xm4r
0xm4r

Reputation: 548

There are a two points to make here.

  1. Yes, &String and &str are different types. The former is a reference to a dynamic heap string type, the latter is a string slice which is a reference to part of a string. I recommend reviewing this chapter of the Rust book on slices.
  2. The lifetime of the temporary is the real culprit as the error message indicates.

In the first example, the return type of to_lowercase() is String - it is returning a String that will be owned by the caller. However we are not binding this return value to a variable - we're attempting to call as_str() first and then binding the result of that to s_lower. as_str() can't be chained with to_lowercase() without first binding the result because it needs to take a reference to the calling String, which can't be done on a temporary. This is problematic because the String returned by to_lowercase() ends up being dropped (deallocated and unusable) before as_str() is called as the compiler realizes it can't be used.

To make it clearer, he first example is sort of equivalent to:

fn main() {
    let s = "FOO";
    // temporary scope
    {
        let temporary = s.to_lowercase();
    }
    let s_lower = temporary.as_str();
}

based on the temporary value rules The lifetime of the temporary is hopefully clearer this way. To fix the first example you would have to do:

fn main() {
    let s = "FOO";
    let s_lower = &s.to_lowercase();
    let s_lower_slice = s_lower.as_str();
    println!("{s_lower_slice}");
}

which is further explained in the answer @kmdreko linked above

Upvotes: 0

Related Questions