Skip to content

Problem with GATs, async, and Send-bounds #90696

Open
@JanBeh

Description

@JanBeh

I have experienced problems with Send bounds on GATs. The original code where the problem occurred uses the async-trait crate and looks as follows:

#![feature(generic_associated_types)]

use async_trait::async_trait;
use std::ops::Deref;

async fn some_async_task() {}

#[async_trait]
trait Source {
    type T;
    type Wrapper<'a>: Deref<Target = Self::T> + Send
    where
        Self: 'a;
    async fn retrieve(&mut self) -> Self::Wrapper<'_>;
}

struct S {
    state: i32,
}

impl S {
    fn new() -> Self {
        S { state: 0 }
    }
}

#[async_trait]
impl Source for S {
    type T = i32;
    type Wrapper<'a> = &'a Self::T;
    async fn retrieve(&mut self) -> Self::Wrapper<'_> {
        self.state += 1;
        &self.state
    }
}

#[async_trait]
trait User {
    // After updating nightly rustc, this won't work anymore:
    async fn process<'a, 'b, S>(
        &'a self,
        source: &'b mut S,
    ) -> S::Wrapper<'b>
    where
        S: Source + Send,
    {
        let result = source.retrieve().await;
        some_async_task().await;
        result
    }
}

struct U {}

#[async_trait]
impl User for U {}

#[tokio::main]
async fn main() {
    let mut s = S::new();
    let u = U {};
    let value: &i32 = u.process(&mut s).await;
    println!("Result = {}", value);
}

Using the Rust playground with Nightly version: 1.58.0-nightly (2021-11-07 46b8e74), I get:

   Compiling playground v0.0.1 (/playground)
error[E0311]: the parameter type `S` may not live long enough
  --> src/main.rs:46:5
   |
40 |       async fn process<'a, 'b, S>(
   |                                - help: consider adding an explicit lifetime bound...: `S: 'c`
...
46 | /     {
47 | |         let result = source.retrieve().await;
48 | |         some_async_task().await;
49 | |         result
50 | |     }
   | |_____^ ...so that the type `S` will meet its required lifetime bounds

error: could not compile `playground` due to previous error

If I follow the compiler's advice literally, then of course I'll get:

error[E0261]: use of undeclared lifetime name `'c`

If I declare 'c as a lifetime parameter, then the compiler demands I should add a bound S: 'd, and so on.

This code worked with some earlier version of nightly Rust (without the where Self: 'a bound, and before where Self: 'a was required in the GAT). Unfortunately I do not remember which version that was (a few weeks ago).

However, even before the update of rustc, I had difficulties to remove the Send bound from the GATs definition and to include a bound in another method that uses the GATs. This problem still persists and can be demonstrated with the following code:

#![feature(generic_associated_types)]

use async_trait::async_trait;
use std::ops::Deref;

async fn some_async_task() {}

#[async_trait]
trait Source {
    type T;
    // I removed the `Send` bound here:
    type Wrapper<'a>: Deref<Target = Self::T>
    where
        Self: 'a;
    async fn retrieve(&mut self) -> Self::Wrapper<'_>;
}

struct S {
    state: i32,
}

impl S {
    fn new() -> Self {
        S { state: 0 }
    }
}

#[async_trait]
impl Source for S {
    type T = i32;
    type Wrapper<'a> = &'a Self::T;
    async fn retrieve(&mut self) -> Self::Wrapper<'_> {
        self.state += 1;
        &self.state
    }
}

#[async_trait]
trait User {
    async fn process<'a, 'b, S>(
        &'a self,
        source: &'b mut S,
    ) -> S::Wrapper<'b>
    where
        S: Source + Send + 'static,
        // And added it here:
        <S as Source>::Wrapper<'b>: Send,
    {
        let result = source.retrieve().await;
        some_async_task().await;
        result
    }
}

struct U {}

#[async_trait]
impl User for U {}

#[tokio::main]
async fn main() {
    let mut s = S::new();
    let u = U {};
    let value: &i32 = u.process(&mut s).await;
    println!("Result = {}", value);
}

Using again Rust playground with Nightly version: 1.58.0-nightly (2021-11-07 46b8e74), I get:

   Compiling playground v0.0.1 (/playground)
error: implementation of `Send` is not general enough
  --> src/main.rs:48:5
   |
48 | /     {
49 | |         let result = source.retrieve().await;
50 | |         some_async_task().await;
51 | |         result
52 | |     }
   | |_____^ implementation of `Send` is not general enough
   |
   = note: `<S as Source>::Wrapper<'0>` must implement `Send`, for any lifetime `'0`...
   = note: ...but `Send` is actually implemented for the type `<S as Source>::Wrapper<'b>`

error: could not compile `playground` due to previous error

If I try to use a HRTB (for<'z> <S as Source>::Wrapper<'z>: Send), then I get:

   Compiling playground v0.0.1 (/playground)
error[E0277]: `<_ as Source>::Wrapper<'z>` cannot be sent between threads safely
  --> src/main.rs:64:25
   |
64 |     let value: &i32 = u.process(&mut s).await;
   |                         ^^^^^^^ `<_ as Source>::Wrapper<'z>` cannot be sent between threads safely
   |
   = help: the trait `for<'z> Send` is not implemented for `<_ as Source>::Wrapper<'z>`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

I'm not sure if these problems are related, but there seem to be a link. There has been a discussion on the Rust users forum where another smaller example (without async-trait macros) was created:

#![feature(generic_associated_types)]
use std::{future::Future, marker::PhantomData};

trait Trait {
    type Associated<'a>: Send
    where
        Self: 'a;
}

fn future<'a, S: Trait + 'a, F>(f: F) -> F
where
    F: Future<Output = ()> + Send,
{
    f
}

fn foo<'a, S: Trait + 'a>() {
    future::<'a, S, _>(async move {
        let result: PhantomData<S::Associated<'a>> = PhantomData;
        async {}.await;
    });
}

This results (using the same compiler version) in:

   Compiling playground v0.0.1 (/playground)
error[E0311]: the parameter type `S` may not live long enough
  --> src/lib.rs:18:5
   |
17 | fn foo<'a, S: Trait + 'a>() {
   |            -- help: consider adding an explicit lifetime bound...: `S: 'b +`
18 |     future::<'a, S, _>(async move {
   |     ^^^^^^^^^^^^^^^^^^ ...so that the type `S` will meet its required lifetime bounds...
   |
note: ...that is required by this bound
  --> src/lib.rs:12:30
   |
12 |     F: Future<Output = ()> + Send,
   |                              ^^^^

error: could not compile `playground` due to previous error

If I replace Send with SomeTrait with impl<T> SomeTrait for T {}, the code will compile. We guessed the problem might be auto-trait related?

See also:

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-GATsArea: Generic associated types (GATs)A-associated-itemsArea: Associated items (types, constants & functions)A-async-awaitArea: Async & AwaitA-lifetimesArea: Lifetimes / regionsA-trait-systemArea: Trait systemAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.GATs-triagedIssues using the `generic_associated_types` feature that have been triagedT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.requires-nightlyThis issue requires a nightly compiler in some way.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions