Skip to content
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

Misleading help suggests Sync bound when shareable reference is passed across or into await #129105

Open
mzabaluev opened this issue Aug 14, 2024 · 5 comments
Labels
A-async-await Area: Async & Await A-diagnostics Area: Messages for errors, warnings, and lints AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. D-confusing Diagnostics: Confusing error or lint that should be reworked. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@mzabaluev
Copy link
Contributor

mzabaluev commented Aug 14, 2024

#59245 results in an error that may be difficult to interpret when structuring generic async code:

use std::fmt::Display;

async fn run(mut state: impl Display) {
    do_stuff(&state).await;
    // ...
}

async fn do_stuff(state: &impl Display) {
    println!("{state}");
}

fn spawn_task<T>(state: T)
where
    T: Display + Send + 'static,
{
    tokio::spawn(run(state));
}

The compiler (as of 1.82.0-nightly (80eb5a8 2024-08-13)) produces this error output:

error[E0277]: `T` cannot be shared between threads safely
   --> src/main.rs:16:18
    |
16  |     tokio::spawn(run(state));
    |     ------------ ^^^^^^^^^^ `T` cannot be shared between threads safely
    |     |
    |     required by a bound introduced by this call
    |
    = note: required for `&T` to implement `Send`
note: required because it's used within this `async` fn body
   --> src/main.rs:8:41
    |
8   |   async fn do_stuff(state: &impl Display) {
    |  _________________________________________^
9   | |     println!("{state}");
10  | | }
    | |_^
note: required because it's used within this `async` fn body
   --> src/main.rs:3:35
    |
3   |   async fn run(state: impl Display) {
    |  ___________________________________^
4   | |     do_stuff(&state).await;
5   | |     // ...
6   | | }
    | |_^
note: required by a bound in `tokio::spawn`
   --> /home/mzabaluev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.39.2/src/task/spawn.rs:167:21
    |
165 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
166 |     where
167 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`
help: consider further restricting this bound
    |
14  |     T: Display + Send + 'static + std::marker::Sync,
    |                                 +++++++++++++++++++

A non-restrictive, but also unintuitive, solution is to make the reference passed to do_stuff mutable (i.e. provably exclusive), even though mutability is not required by the function body.

Desired outcome

The help heuristic should detect that the Sync bound arises due to a shareable reference becoming a member of an async closure for which Send is required, and suggest using an exclusive reference as an alternative to restricting the bound.

Originally posted by @mzabaluev in #59245 (comment)

@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Aug 14, 2024
@jieyouxu jieyouxu added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-async-await Area: Async & Await D-confusing Diagnostics: Confusing error or lint that should be reworked. labels Aug 15, 2024
@traviscross
Copy link
Contributor

@rustbot labels +AsyncAwait-Triaged -needs-triage

Interesting. We talked about this today in WG-async triage.

We minimized the issue as follows:

fn spawn<F: Future + Send + 'static>(_: F) {}

fn spawn_task<T: Send + 'static>(mut state: T) {
    spawn(async move {
        let x = &mut state; // <-- Remove `mut` for error.
        async move { _ = [x] }.await;
    });
}

fn main() {}

We also wrote this to demonstrate the interesting behavior of &mut here:

trait AllSend<'a, T: 'a + ?Sized>
where
    &'a mut T: Send, // <-- Remove `mut` to get error.
{
}

impl<'a, T: ?Sized + Send + 'a> AllSend<'a, T> for T {}

Overall, we felt in general that adding the Sync bound is going to be the right thing to do in most cases. We were skeptical about how generalizable the suggestion of threading mut throughout would be. We think the error message might just get too long if we were to try to suggest mut here, so we think things are probably fine as-is. Using mut to solve the problem here is a bit of a subtle technique.

@rustbot rustbot added AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Sep 5, 2024
@traviscross traviscross closed this as not planned Won't fix, can't repro, duplicate, stale Sep 5, 2024
@mzabaluev
Copy link
Contributor Author

mzabaluev commented Nov 4, 2024

I understand that carrying a reference across await points requires Sync in general. But the suggestion to add the Sync bound without alternatives is misleading in cases when a simple code refactoring can make in unnecessary.

@traviscross
Copy link
Contributor

Perhaps you could make a proposal here for exactly what the diagnostic should say and how it should look. When on WG-async we talked about this, we just couldn't really picture a good way to do this. A concrete proposal might help here.

@mzabaluev
Copy link
Contributor Author

The current hint could be augmented like so:

help: consider further restricting this bound
    |
14  |     T: Display + Send + 'static + std::marker::Sync,
    |                                 +++++++++++++++++++
help: alternatively, `&mut T` is unique and can be safely passed between threads

@traviscross
Copy link
Contributor

Reopening so as to enable consideration of the concrete proposal above.

@traviscross traviscross reopened this Nov 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await A-diagnostics Area: Messages for errors, warnings, and lints AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. D-confusing Diagnostics: Confusing error or lint that should be reworked. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants