-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
async/await: awaiting inside a match block captures borrow too eagerly #57017
Comments
This is just an artifact of the compiler holding the borrow of |
("liveness" is really the tag I want, but we don't have one for that ;) ) |
This might be a duplicate of #46525? |
I'm not certain it's only that; it does seem related to |
Marking this as deferred -- the problem here (discussed here on Zulip) has to do with how we use an "overapproximation" to decide what data may be inside a generator, at least initially. This is based on the HIR. Only later do we get more precise results when generating MIR. I think we could resolve this by making that over-appoximation more precise, but @Zoxc expressed a desire to do away with it altogether. This would obviously be better, if we can make it work, though I'm a bit skeptical. (See the Zulip topic for more details.) |
I'm adding the AsyncAwait-Unclear label, because I'd like to revisit this question and try to make some progress here before we stabilize, but I'm not willing to call it blocking yet =) |
Another instance of this from #61211. Doesn't only affect what is borrowed there but also the trait bounds of the generated future. let foo = Mutex::new("123");
let foo: Box<dyn futures::future::Future<Output = ()> + Send> = Box::new(async move {
// In a separate block works
// {
let bar = foo.lock().unwrap();
drop(bar);
// }
futures::future::lazy(|_| ()).await;
}); Fails with
|
FWIW it took me ~30 minutes or so to decide that this was likely a compiler "bug" with async/await, after spending 10-15 minutes reducing the amount of code I had added to just one match statement. The errors are long -- and largely unintelligible. I'm not sure if I'd call this a blocker, but I would say that if it's not fixed, we should definitely include it in a FAQ section of "common errors" or something along those lines. Here's a gist of the error. There is no clear mention of the actual source of the error, which is in a different crate (this snippet points at main.rs, which is a thin shim over the larger crate rooted at main.rs) that compiled fine(!) -- that means that this might be introduced accidentally in a library and not discovered until a client attempts to use said function in a manner requiring Sync/Send, for example. |
@Mark-Simulacrum https://gist.github.com/inv2004/9593c35216ee7a94268eb39f47ecaa52 |
My understanding is yes; but I can't be certain. |
@rustbot claim I'm assigning this to myself not to necessarily fix but to organize a call where we can dig a bit into what it would take to fix this, especially if we can move the MIR construction earlier. |
Following the suggestion solves the problem with the code, but the |
So excited this is closed! Nice work @eholk |
[generator_interior] Be more precise with scopes of borrowed places Previously the generator interior type checking analysis would use the nearest temporary scope as the scope of a borrowed value. This ends up being overly broad for cases such as: ```rust fn status(_client_status: &Client) -> i16 { 200 } fn main() { let client = Client; let g = move || match status(&client) { _status => yield, }; assert_send(g); } ``` In this case, the borrow `&client` could be considered in scope for the entirety of the `match` expression, meaning it would be viewed as live across the `yield`, therefore making the generator not `Send`. In most cases, we want to use the enclosing expression as the scope for a borrowed value which will be less than or equal to the nearest temporary scope. This PR changes the analysis to use the enclosing expression as the scope for most borrows, with the exception of borrowed RValues which are true temporary values that should have the temporary scope. There's one further exception where borrows of a copy such as happens in autoref cases also should be ignored despite being RValues. Joint work with `@nikomatsakis` Fixes rust-lang#57017 r? `@tmandry`
…-guard, r=nikomatsakis generator_interior: Count match pattern bindings as borrowed for the whole guard expression The test case `yielding-in-match-guard.rs` was failing with `-Zdrop-tracking` enabled. The reason is that the copy of a local (`y`) was not counted as a borrow in typeck, while MIR did consider this as borrowed. The correct thing to do here is to count pattern bindings are borrowed for the whole guard. Instead, what we were doing is to record the type at the use site of the variable and check if the variable comes from a borrowed pattern. Due to the fix for rust-lang#57017, we were considering too small of a scope for this variable, which meant it was not counted as borrowed. Because we now unconditionally record the borrow, rather than only for bindings that are used, this PR is also able to remove a lot of the logic around match bindings that was there before. r? `@nikomatsakis`
Ugh, so it looks like this isn't actually fixed yet. Playground use std::any::Any;
use std::future::Future;
struct Client(Box<dyn Any + Send>);
impl Client {
fn status(&self) -> u16 {
200
}
}
async fn get() {
}
pub fn wat() -> impl Future + Send {
let client = Client(Box::new(true));
async move {
match client.status() {
200 => {
let _x = get().await;
},
_ => (),
}
}
} Output:
|
I'm a bit confused by what is the part considered problematic and deemed fix-worty.
So { let temporary = &client;
match temporary.status() { 200 => {
…
}}
} // <- temporary dropped here which can be further simplified down to: let temporary = &client;
if temporary.status() == 200 {
… /* stuff, such as a yield or await point */
} So the problem in this issue becomes that of
So I guess the whole issue is about "making generator captures NLL-aware", of sorts? |
I think that's a good way of looking at it. As you point out, @danielhenrymantilla, technically |
This will be fixed by |
Should this issue be closed now? The issue referenced in April of 2023 has now been closed in April 2024. Found this issue by working backwards for the reasons for the |
The playground code now compiles on stable, so probably yes. |
If you use
await!(some_fut)
inside an arm of amatch X
, the generated future eagerly borrows the value ofX
, if it not needed.This may not usually be noticeable, but the issue compounds when the type
X
contains a trait object, and the future you wish to return isimpl Future + Send
. This causes a misleading error message that "dyn Trait + Send
cannot be shared between threads", which is required to for&X: Send
.Example
Here's a simple struct with a trait object:
Consider a function like this:
You could consider using a match to determine what kind of future to await (or what arguments to pass):
If the
await
is moved out of the match block, all is well:The
wat
function causes this compilation error:Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=2a9dbea32d31457d50d40b99c52ee214 (updated to latest syntax -Niko)
The text was updated successfully, but these errors were encountered: