Reputation: 673
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
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
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