3

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.

3
  • 3
    The compiler literally tells you what's wrong: temporary value is freed at the end of this statement, creates a temporary which is freed while still in use and borrow later used here. I think the error message is pretty clear. If not, please tell us, what you are you understanding. Commented Jan 17, 2019 at 6:33
  • 2
    Possible duplicate of "borrowed value does not live long enough" when using the builder pattern Commented Jan 17, 2019 at 10:05
  • I don't think that the duplicate applies, since here the temporary could be dropped without trouble if it wasn't prevented by a wrong explicit lifetime. Commented Jan 17, 2019 at 11:30

2 Answers 2

7

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.

Sign up to request clarification or add additional context in comments.

3 Comments

That’s a good point. I never checked that split_first actually returns references into its input, merely that its type says it can. Fixing the type is a better solution.
Let's say I do make the adjustment that you suggest in your short answer, does the restructuring of the lifetimes actually change the assembly code, or are we just making compiler-enforceable commitments to the compiler regarding the lifetimes of certain references?
@ClarkMcCauley lifetimes have no effect on the generated assembly. They only exist so that the compiler (and developers) can know how a function or interface behaves without needing to look at the code on the other side of the interface.
6

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).

3 Comments

@AndersKaseorg I would like to read if there is a reference or documentation for the roughly interpretation that you mentioned?
@ÖmerErden Sure, I’ve added a link.
I still don't think think this is correct. Rust does allow the example that you've said it disallows. Also, in C++ this should not be UB for exactly the same reason: because the reference is to words which does live long enough.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.