struct A;
struct B<'a>(&'a A);
impl<'a> Drop for B<'a> {
fn drop(&mut self) {}
}
fn main() {
B(&A);
}
Rust complains:
error: borrowed value does not live long enough
--> ab.rs:9:8
|
9 | B(&A);
| ^ - temporary value dropped before borrower
| |
| temporary value created here
|
= note: values in a scope are dropped in the opposite order they are created
= note: consider using a `let` binding to increase its lifetime
Using a let binding to increase the lifetime of that A value does fix it.
But why is there a problem in the first place? Shouldn't the A value be created before, and thus outlive, the B value?
In C++, the answer would be that the scope of A is only within the parentheses, so it is created just before B is created and passed into B, but then at the end of the call to create B, A goes out of scope and is no longer valid to use. This, of course, is before B goes out of scope at the end of the function. So if anything inside B tried to use its reference to A, it would actually be using stack space that could have been reused by some other variable. Moving A out to a let on a separate line gives it the same scope as B, so it all works out.
I'm a Rust newbie, but I think Rust has the same scoping rules here, and that's why it flags this as an error.
I've encountered very similar bugs in the past when writing C++, where the compiler would reuse stack space from an object that had been created and gone out of scope, but other objects still held a reference to those objects. The tricky thing there is that they often don't bite you right away, but then you change something unrelated in the function later, suddenly it blows up. So it's nice that Rust catches this.
EDIT: I realize my description above may be a bit obtuse. Let me know if this doesn't make sense and I'll try to explain it in a different way.
I think in C++, the temporary lasts to the end of the complete expression. Here's a C++ example; the output shows A being created before, and destroyed after, B.
Ah, so it does. As usual, the best way to verify a behavior is to actually try it Thanks for the correction.
A similar experiment using println! reveals that B is dropped before the end of the function as well, which leaves us back with your original question. I only have idle speculation at this point, but will be interested to see what the experts have to say.
When an rvalue is used in an lvalue context, a temporary un-named lvalue is created and used instead. The lifetime of temporary values is typically the innermost enclosing statement; the tail expression of a block is considered part of the statement that encloses the block.
This doesn't seem to match what the compiler's saying in its error message rejecting the original example.
(I understand that the Rust Reference isn't authoritative, but I wasn't able to find a discussion of this in The Book, and the compiler's behavior doesn't make sense to me here.)
In C++14, the relevant chapter and verse is §12.2/3 Temporary Objects [class.temporary]:
Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created.
§1.9/10 Program execution [intro.execution] explains:
A full-expression is an expression that is not a subexpression of another expression.
So even in C++, the temporary should last to the end of the call.
The error message of the stable compiler is actually a bit clearer here:
rustc 1.12.0 (3191fbae9 2016-09-23)
error: borrowed value does not live long enough
--> <anon>:9:8
|
9 | B(&A);
| ^ does not live long enough
|
note: reference must be valid for the destruction scope surrounding statement at 9:4...
--> <anon>:9:5
|
9 | B(&A);
| ^^^^^^
note: ...but borrowed value is only valid for the statement at 9:4
--> <anon>:9:5
|
9 | B(&A);
| ^^^^^^
help: consider using a `let` binding to increase its lifetime
--> <anon>:9:5
|
9 | B(&A);
| ^^^^^^
error: aborting due to previous error
It seems that the drop is called in an implicit scope surrounding the actual statement, like:
fn main() {
{ // implicit scope for `drop`s
let b = { // implicit scope of the original statement
B(&A)
};
drop(&mut b);
}
}
Okay, @bluss and @talchas kindly explained this to me on IRC.
The problem is in the granularity of the drop-checking rules, which require A to strictly outlive B. From the point of view of that code, A and B have the same lifetime, as if one had written:
struct A;
struct B<'a>(&'a A);
impl<'a> Drop for B<'a> {
fn drop(&mut self) {}
}
fn main() {
let (a, b);
a = A;
b = B(&a);
}
The reason the presence of the Drop impl matters is that, when it's only references involved, it's permissible for reference and referent to have identical lifetimes: the reference can't be used unsafely. Introducing a Drop impl to the situation requires the referent to strictly outlive the reference, to ensure there is a clear order in which to run drop methods.
What I don't get is why the two temporaries have the same lifetime! Where does that constraint come from? What rule? And is the rule necessary for soundness?
There is some inconsistency (I've never looked at Rust or C++ Ref/Std deeply so I might be wrong).
In C++ a temp bound by a const ref is supposed to outlive the site of binding and the object being bound to. In rust this does not seem to be the case. Just need to bypass the lifetime check to know what's happening - so using pointers.
Consider:
struct A;
impl Drop for A { fn drop(&mut self) { println!("Drop A.") } }
struct B(*const A);
impl Drop for B { fn drop(&mut self) { println!("Drop B.") } }
fn main() {
let _ = B(&A as *const A); // B is destroyed after this expression itself.
}
struct A;
impl Drop for A { fn drop(&mut self) { println!("Drop A.") } }
struct B(*const A);
impl Drop for B { fn drop(&mut self) { println!("Drop B.") } }
fn main() {
let _b = B(&A as *const A); // _b will be dropped when scope exits main()
}
TBH, the second one is what I'd expect. A is a temporary, that only lives for the statement while B is a named variable that lives until the end of main.
The first variant is probably a special case and B is regarded as a temporary as well, because _ is not really a named variable.
Anyway, B takes a pointer (not a reference) to A, so A is not borrowed and the lifetimes of A and B are not related at all. That's why I'd say that the example is not relevant to the original problem.
I guess that all temporaries in the same expression are given the same lifetime.
Otherwise the compiler would have to determine a strict ordering between all temporaries.
I image that this would also lead to more (but different) restrictions. How would the ordering be determined? Left to right? Inside to outside? Or dynamically to determine the most "liberal" ordering?
g can call f even though f requires arguments with matching lifetimes, because the arguments are reborrowed (with a shorter, common lifetime) at the f(a, b) call site.
(Update: Actually, even if this were not the case, the reference lifetime of &A need not exactly match the lifetime of the A temporary, IIUC, so it might still work!)
(Also, it seems reasonable for temporaries without destructors to be grouped together and given the same lifetime, just like bindings for values without destructors that are declared in the same pattern.)