rust-lang / rust-lang/rust

Regression compared to 1.98: future is not `Send`

Open
#162,261 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-diagnostics T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Code
pub async fn ldap_listen<D: DatabaseInterface>(listener: Listener, db: Database<D>) {
    while let Ok(Some(stream)) == listener.accept_ldap().await {
        let db = db.clone();
        tokio::spawn(ldap_handler(stream, db));
    }
}

pub async fn ldap_handler<D: DatabaseInterface>(mut stream: LdapStream, mut db: Database<D>) {
    while let Ok(msg) = stream.next().await {
        match ldap_handler_inner(&mut stream, msg, &mut state, &mut db).await {
            Ok(true) => continue,
            _ => retur,
        }
    }
}

pub async fn ldap_handler_inner<D: DatabaseInterface>(
    stream: &mut LdapStream,
    msg: LdapMsg,
    db: &mut Database<D>,
) -> Result<bool, LdapStreamError> {
    ...
    // Perform some operation that indirectly involves db.get_user()
    Ok(true)
}

...

pub trait DatabaseInterface: Clone + Send + Sync + 'static {
    async fn get_user(&self, user: &UserRef) -> Result<Option<User>, BoxedError>;
    async fn create_user(
        &mut self,
        user: User,
    ) -> Result<Result<(), UserAlreadyExists>, BoxedError>;
}

...

pub type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
Current output
error: future cannot be sent between threads safely
   --> src/ldap/handler.rs:18:30
    |
 18 |                 tokio::spawn(ldap_handler(stream, db));
    |                              ^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `ldap_handler` is not `Send`
    |
    = help: within `impl futures_util::Future<Output = ()>`, the trait `std::marker::Send` is not implemented for `{async fn body of ldap_handler_inner<D>()}`
note: future is not `Send` as it awaits another future which is not `Send`
   --> src/ldap/handler.rs:41:30
    |
 41 |             Ok(msg) => match ldap_handler_inner(&mut stream, msg, &mut state, &mut db).await {
    |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here on type `impl futures_util::Future<Output = Result<bool, LdapStreamError>>`, which is not `Send`
note: required by a bound in `tokio::spawn`
   --> /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.53.1/src/task/spawn.rs:176:21
    |
174 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
175 |     where
176 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`

warning: `llldap` (bin "llldap") generated 1 warning
Desired output
error: future cannot be sent between threads safely
   --> src/ldap/handler.rs:18:30
    |
 18 |                 tokio::spawn(ldap_handler(stream, db));
    |                              ^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `ldap_handler` is not `Send`
    |
    = help: within `impl futures_util::Future<Output = ()>`, the trait `std::marker::Send` is not implemented for `impl Future<Output = Result<Option<User>, Box<dyn Error + Send + Sync>>>`
note: future is not `Send` as it awaits another future which is not `Send`
   --> src/db/interface.rs:6:9
    |
  6 |         self.inner.read().await.get_user(user).await
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here on type `impl futures_util::Future<Output = Result<std::option::Option<User>, Box<(dyn std::error::Error + std::marker::Send + Sync + 'static)>>>`, which is not `Send`
note: required by a bound in `tokio::spawn`
   --> /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.53.1/src/task/spawn.rs:176:21
    |
174 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
175 |     where
176 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`
    = note: the full name for the type has been written to '/home/user/.cargo/target/debug/deps/llldap-7b3edec36a4dec48.long-type-16072208530409140482.txt'
    = note: consider using `--verbose` to print the full type name to the console
help: `std::marker::Send` can be made part of the associated future's guarantees for all implementations of `DatabaseInterface::get_user`
   --> src/db/interface.rs:18:5
    |
 18 -     async fn get_user(&self, user: &UserRef) -> Result<Option<User>, BoxedError>;
 18 +     fn get_user(&self, user: &UserRef) -> impl std::future::Future<Output = Result<Option<User>, BoxedError>> + std::marker::Send;
Rationale and extra context

The desired output is from rustc 1.98.0, and is much more clear because it points to deeper in the call stack some result error that is not Send. nightly is pretty clueless right now.

Interestingly, the program compiled fine before i moved out logic from main into the ldap_listen function, despite not touching the get_user method at all. This may be something i'm missing and not an actual additional problem. I added trait bounds on DatabaseInterface including Send. I'm not sure why the Result from get_user is not send and needs massaging, despite BoxedError being a type alias for Box<dyn std::error::Error + Send + Sync + 'static>.

The last three commits on this branch are respectively the workaround suggested by stable rustc, the refactor that didn't compile, and the previous commit that compiled fine.

Other cases

Rust Version
rustc 1.100.0-nightly (908501772 2026-08-30)
binary: rustc
commit-hash: 90850177249efe0321573c569aec5d12b257f8d6
commit-date: 2026-08-30
host: x86_64-unknown-linux-gnu
release: 1.100.0-nightly
LLVM version: 23.1.0

Edit: also confirmed on latest nightly

rustc 1.100.0-nightly (2e2b193f8 2026-09-02)
binary: rustc
commit-hash: 2e2b193f8ada105f27608b7be81c293e0d7292cb
commit-date: 2026-09-02
host: x86_64-unknown-linux-gnu
release: 1.100.0-nightly
LLVM version: 23.1.1
Anything else?

Thank you for making diagnostics awesome!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the diagnostic with the supplied lldap example, focusing on src/ldap/handler.rs and the async trait method in src/db/interface.rs. Compare the current nightly output with the rustc 1.98 output and trace why the deeper non-Send future is not reported; done means the diagnostic identifies the relevant await in src/db/interface.rs and provides the associated Send-bound guidance.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.