You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
async functions fail to move values into generated futures in the latest nightly #64391
error[E0597]: `config` does not live long enough
--> tokio-postgres/src/lib.rs:171:5
|
171 | config.connect(tls).await
| ^^^^^^-------------
| |
| borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
172 | }
| -
| |
| `config` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl core::future::future::Future`
|
= note: The temporary is part of an expression at the end of a block. Consider forcing this temporary to be dropped sooner, before the block's local variables are dropped. For example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block.
#64525 doesn't seem to have fixed this for my specific case:
error[E0597]: `config` does not live long enough
--> tokio-postgres/src/lib.rs:171:5
|
171 | config.connect(tls).await
| ^^^^^^-------------
| |
| borrowed value does not live long enough
| a temporary with access to the borrow is created here ...
172 | }
| -
| |
| `config` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl core::future::future::Future`
|
= note: The temporary is part of an expression at the end of a block. Consider forcing this temporary to be dropped sooner, before the block's local variables are dropped. For example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block.
error: aborting due to previous error
I recently tried to compile my codebase on nightly-2019-09-13 and I seem to have similar issues. I have hundreds of errors like @sfackler mentioned. Just to be sure I'm having the same issue, here is an example:
$ cargo test -p offst-relay
Compiling offst-relay v0.1.0(/home/real/projects/d/offst/components/relay)
error[E0597]: `access_control` does not live long enough
--> components/relay/src/client/client_listener.rs:512:17
|
510 | / inner_client_listener(511 | | connector,512 | | &mut access_control,
| | ^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
513 | | &mut incoming_access_control,
... |
519 | | Some(event_sender)520 | | ).await
| |_____________- a temporary with access to the borrow is created here ...
521 | }
| -
| |
| `access_control` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl core::future::future::Future`
|
= note:The temporary is part of an expression at the end of a block.Consider forcing this temporary to be dropped sooner, before the block's local variables are dropped.For example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block.
error: aborting due to previous error
For more information about this error, try `rustc --explain E0597`.
error:Could not compile `offst-relay`.
Indeed saving the result into a temporary variable x and then returning it seem to satisfy the compiler.
OK, so I dug into this a bit. The errors are... correct-ish, but horrific. They are an outcome of the current temporary-lifetime rules, and this is in that sense a dup of #46413. That said, there is some imprecision here, and it may be possible to work-around the problem by increasing the precision of our analysis without actually changing the dynamic semantics of any existing code.
Let me explain what's going on to start.
The desugaring converts $foo.await into match $foo { bar => $await(bar) }. In the MIR desugaring, what this effectively does is to (a) store $foo into a temporary value (in the surrounding temporary scope) and then (b) move from that temporary into bar. The $await(bar) then does the stuff to "await" the future, but that's not important to us here.
which is effectively desugared into something like
let config = 666;matchconnect2(&config,"".to_string()){
the_future => $await(the_future ),}
this then gets further desugared, as part of MIR lowering, into this:
// note the ordering here! This temporary is introduced into "the surrounding scope",
// which means it goes *before* the let-bound variables.
// This is basically the bug in our current rules.
let _3;
let config = 666;
_3= connect2(&config, "".to_string());
let the_future = _3;
$await(the_future);
...
drop(_temp);
Now, here's the interesting thing: the borrow checker is smart enough to see that the drop(_temp) is going to be a no-op, because we've moved from _temp into the_future. So you might wonder, where does the error come from?
The answer requires a bit more digging. It turns out that when we desugar the _temp = connect2 call further, we get this:
Here we have a drop(_6) that got added after the call. I'm not 100% sure how this falls out, but regardless it is a guaranteed no-op. This is because _6 is unconditionally moved in the connect2 call which dominates it. Nonetheless, we create the drop -- you can see the MIR graph in this gist. This in turn triggers us to create an unwind path -- maybe invoking the string destructor will panic!
Now this unwind path is problematic -- along that path we wind up doing the following:
bb10 {
StorageDead(config)
drop(_3) // this is the temporary we introduced!
}
Along this path, indeed, we are dropping _3 whose type indicates that it may hold a reference to config, but the memory for config was already invalidated. This is bad.
So there you have it:
we introduce a drop that we know is a no-op
but before we figure that out, there is an unwind path
if that drop did unwind, then _3 would be dropped before the config variable
I haven't 100% verified this is where the error arises, but I'm 99.9% sure.
So it seems like we could fix this if we avoided inserting the drop for move arguments -- I'll have to look into that, but it seems plausible.
Reacted by real, Rémy Rakic and Ellen Emilia Anna Zscheile
So it seems like we could fix this if we avoided inserting the drop for move arguments -- I'll have to look into that, but it seems plausible.
Looked into this a bit. Here are a few tips of what I've found so far:
We have a notion of a "local operand". This refers to an operand that is going to get freed when the expression is done, and it includes call arguments:
If you trace that method out, it ultimately bottoms out in the expr_as_temp helper, which will invoke this.schedule_drop to schedule that we should add a Drop. Note that this is actually the second call; the first class to schedule_drop adds a StorageDead terminator.
This is all correct so far and should not change. After all, we may need to execute a drop if (e.g.) one of the arguments being evaluated should unwind. We just don't need them once all the call arguments succeed.
What we can do however is -- once the call instruction has been emitted -- we could flag those operands as having been consumed (actually, we could probably do it right before emitting the call terminator, since it will move them before it could unwind). This would effectively cancel the calls to drop. I am imagining a call to a method cancel_drop which takes as argument the MIR temporary. It would search the enclosing scopes, find the drop of that temporary (if any), and mark it as "canceled". This would probably wind up being very simialr to what schedule_drop does to add a drop -- i.e., we have to invalidate caches. Presumably we could factor out the loop into some common code. The idea then would be that when we pop the scope we can avoid generating Drop terminators for values that have been canceled (we still want to StorageDead them).
I'm experimenting in a branch here. I actually took a slightly different tack than what I described above -- I'm not "removing" the drop but just recording that the operand was moved, and taking advantage of that info on the non-unwind path. We'll see how it goes, but it seems simpler.
Activity
sfackler commentedon Sep 12, 2019
This can be hacked around via an
async move {}block:nikomatsakis commentedon Sep 12, 2019
I think the root cause of this is #46413 -- feels like we ought to have more test cases covering these patterns, but oh well.
I haven't tried to look closely enough to decide what possible solution is.
nikomatsakis commentedon Sep 16, 2019
I can confirm that this is actually a duplicate of #64512, so this is a genuine bug. An alternative workaround is
return config.connect(tls).awaitnikomatsakis commentedon Sep 16, 2019
Minimized test case is just this:
Auto merge of #64525 - nikomatsakis:issue-64512-drop-order-tail-temp,…
sfackler commentedon Sep 18, 2019
#64525 doesn't seem to have fixed this for my specific case:
lqd commentedon Sep 18, 2019
Reduced:
playground
realcr commentedon Sep 19, 2019
I recently tried to compile my codebase on nightly-2019-09-13 and I seem to have similar issues. I have hundreds of errors like @sfackler mentioned. Just to be sure I'm having the same issue, here is an example:
This is the compilation error I get:
Indeed saving the result into a temporary variable
xand then returning it seem to satisfy the compiler.nikomatsakis commentedon Sep 19, 2019
OK, so I dug into this a bit. The errors are... correct-ish, but horrific. They are an outcome of the current temporary-lifetime rules, and this is in that sense a dup of #46413. That said, there is some imprecision here, and it may be possible to work-around the problem by increasing the precision of our analysis without actually changing the dynamic semantics of any existing code.
Let me explain what's going on to start.
The desugaring converts
$foo.awaitintomatch $foo { bar => $await(bar) }. In the MIR desugaring, what this effectively does is to (a) store$foointo a temporary value (in the surrounding temporary scope) and then (b) move from that temporary intobar. The$await(bar)then does the stuff to "await" the future, but that's not important to us here.So, in the case of our example, we have
which is effectively desugared into something like
this then gets further desugared, as part of MIR lowering, into this:
Now, here's the interesting thing: the borrow checker is smart enough to see that the
drop(_temp)is going to be a no-op, because we've moved from_tempintothe_future. So you might wonder, where does the error come from?The answer requires a bit more digging. It turns out that when we desugar the
_temp = connect2call further, we get this:Here we have a
drop(_6)that got added after the call. I'm not 100% sure how this falls out, but regardless it is a guaranteed no-op. This is because_6is unconditionally moved in theconnect2call which dominates it. Nonetheless, we create the drop -- you can see the MIR graph in this gist. This in turn triggers us to create an unwind path -- maybe invoking the string destructor will panic!Now this unwind path is problematic -- along that path we wind up doing the following:
Along this path, indeed, we are dropping
_3whose type indicates that it may hold a reference toconfig, but the memory forconfigwas already invalidated. This is bad.So there you have it:
_3would be dropped before theconfigvariableI haven't 100% verified this is where the error arises, but I'm 99.9% sure.
So it seems like we could fix this if we avoided inserting the
dropfor move arguments -- I'll have to look into that, but it seems plausible.nikomatsakis commentedon Sep 19, 2019
Looked into this a bit. Here are a few tips of what I've found so far:
We have a notion of a "local operand". This refers to an operand that is going to get freed when the expression is done, and it includes call arguments:
rust/src/librustc_mir/build/expr/as_operand.rs
Lines 10 to 22 in 9b9d2af
We create call arguments by invoking that method:
rust/src/librustc_mir/build/expr/into.rs
Lines 240 to 243 in 9b9d2af
If you trace that method out, it ultimately bottoms out in the
expr_as_temphelper, which will invokethis.schedule_dropto schedule that we should add aDrop. Note that this is actually the second call; the first class toschedule_dropadds aStorageDeadterminator.rust/src/librustc_mir/build/expr/as_temp.rs
Lines 115 to 123 in 9b9d2af
This is all correct so far and should not change. After all, we may need to execute a drop if (e.g.) one of the arguments being evaluated should unwind. We just don't need them once all the call arguments succeed.
What we can do however is -- once the call instruction has been emitted -- we could flag those operands as having been consumed (actually, we could probably do it right before emitting the call terminator, since it will move them before it could unwind). This would effectively cancel the calls to drop. I am imagining a call to a method
cancel_dropwhich takes as argument the MIR temporary. It would search the enclosing scopes, find the drop of that temporary (if any), and mark it as "canceled". This would probably wind up being very simialr to whatschedule_dropdoes to add a drop -- i.e., we have to invalidate caches. Presumably we could factor out the loop into some common code. The idea then would be that when we pop the scope we can avoid generatingDropterminators for values that have been canceled (we still want toStorageDeadthem).nikomatsakis commentedon Sep 19, 2019
I'm experimenting in a branch here. I actually took a slightly different tack than what I described above -- I'm not "removing" the drop but just recording that the operand was moved, and taking advantage of that info on the non-unwind path. We'll see how it goes, but it seems simpler.
nikomatsakis commentedon Sep 19, 2019
OK, my patch works, though I'm not entirely sure how happy I am with it. =)
nikomatsakis commentedon Sep 19, 2019
Checking rust-postgres with #64584. I have checked out the commit b7fe6bece5dd11ffc04d8178d5105f9e1a354ebd with the following diff applied:
I can confirm it does not build with nightly, but it does with #64584.
nikomatsakis commentedon Sep 23, 2019
Now that #64584 has landed, I'm going to close this again, since afaik we've fixed all the known bugs here.