iDuanYingJie
iDuanYingJie

Reputation: 673

Why can't I call a method with a temporary value?

I can't call Foo::new(words).split_first() in the following code

fn main() {
    let words = "Sometimes think, the greatest sorrow than older";
/*
    let foo = Foo::new(words);
    let first = foo.split_first();
*/

    let first = Foo::new(words).split_first();

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

struct Foo<'a> {
    part: &'a str,
}

impl<'a> Foo<'a> {

    fn split_first(&'a self) -> &'a str {
        self.part.split(',').next().expect("Could not find a ','")
    }

    fn new(s: &'a str) -> Self {
        Foo { part: s }
    }
}

the compiler will give me an error message

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:8:17
   |
8  |     let first = Foo::new(words).split_first();
   |                 ^^^^^^^^^^^^^^^              - temporary value is freed at the end of this statement
   |                 |
   |                 creates a temporary which is freed while still in use
9  | 
10 |     println!("{}", first);
   |                    ----- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

If I bind the value of Foo::new(words) first, then call the split_first method there is no problem.

These two methods of calling should intuitively be the same but are somehow different.

Upvotes: 3

Views: 1627

Answers (2)

Jmb
Jmb

Reputation: 23463

Short answer: remove the 'a lifetime for the self parameter of split_first: fn split_first(&self) -> &'a str (playground).

Long answer:

When you write this code:

struct Foo<'a> {
    part: &'a str,
}

impl<'a> Foo<'a> {
    fn new(s: &'a str) -> Self {
        Foo { part: s }
    }
}

You are telling the compiler that all Foo instances are related to some lifetime 'a that must be equal to or shorter than the lifetime of the string passed as parameter to Foo::new. That lifetime 'a may be different from the lifetime of each Foo instance. When you then write:

let words = "Sometimes think, the greatest sorrow than older";
Foo::new(words)

The compiler infers that the lifetime 'a must be equal to or shorter than the lifetime of words. Barring any other constraints the compiler will use the lifetime of words, which is 'static so it is valid for the full life of the program.

When you add your definition of split_first:

fn split_first(&'a self) -> &'a str

You are adding an extra constraint: you are saying that 'a must also be equal to or shorter than the lifetime of self. The compiler will therefore take the shorter of the lifetime of words and the lifetime of the temporary Foo instance, which is the lifetime of the temporary. @AndersKaseorg's answer explains why that doesn't work.

By removing the 'a lifetime on the self parameter, I am decorrelating 'a from the lifetime of the temporary, so the compiler can again infer that 'a is the lifetime of words, which is long enough for the program to work.

Upvotes: 7

Anders Kaseorg
Anders Kaseorg

Reputation: 3875

Foo::new(words).split_first() would be interpreted roughly as

let tmp = Foo::new(words);
let ret = tmp.split_first();
drop(tmp);
ret

If Rust allowed you to do this, the references in ret would point [edit: would be allowed by the type of split_first to point*] into the now dropped value of tmp. So it’s a good thing that Rust disallows this. If you wrote the equivalent one-liner in C++, you’d silently get undefined behavior.

By writing the let binding yourself, you delay the drop until the end of the scope, thus extending the region where it’s safe to have these references.

For more details, see temporary lifetimes in the Rust Reference.

* Edit: As pointed out by Jmb, the real problem in this particular example is that the type

fn split_first(&'a self) -> &'a str

isn’t specific enough, and a better solution is to refine the type to:

fn split_first<'b>(&'b self) -> &'a str

which can be abbreviated:

fn split_first(&self) -> &'a str

This conveys the intended guarantee that the returned references do not point into the Foo<'a> (only into the string itself).

Upvotes: 6

Related Questions