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

Confusing interaction between associated types, async fn and impl Future #89657

Open
jpdoyle opened this issue Oct 8, 2021 · 1 comment
Open
Labels
A-async-await Area: Async & Await A-lifetimes Area: Lifetimes / regions AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug. S-needs-repro Status: This issue has no reproduction and needs a reproduction to make progress. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-async Working group: Async & await

Comments

@jpdoyle
Copy link

jpdoyle commented Oct 8, 2021

With the following files:
Cargo.toml:

[package]
name = "future_pin_issue"
version = "0.1.0"
authors = ["Joe Doyle <[email protected]>"]
edition = "2018"

[dependencies]
futures = "0.3.16"

[features]
use_async_fn = []

src/main.rs:

use futures::Future;
use std::pin::Pin;

pub struct MyBackend<'a> {
    _marker: std::marker::PhantomData<&'a ()>,
}

impl<'a> Backend<'a> for MyBackend<'a> {
    type MyStorage = AtomicStorage;
}

pub trait Storage<'a> {}

pub struct AtomicStorage {}
impl<'a> Storage<'a> for AtomicStorage {}

pub trait Backend<'a>: Send {
    type MyStorage: Storage<'a>;
    fn store<F>(&mut self, _update: F) -> Pin<Box<dyn Future<Output = ()> + Send>>
    where
        F: Fn(Self::MyStorage),
    {
        unimplemented!()
    }
}

// https://stackoverflow.com/questions/50547766/how-can-i-get-impl-trait-to-use-the-appropriate-lifetime-for-a-mutable-reference
pub trait Captures<'a> {}
impl<'a, T: ?Sized> Captures<'a> for T {}

#[cfg(feature = "use_async_fn")]
pub async fn transfer<'a, B: 'a + Backend<'a> + Send + Sync>(backend: &mut B) {
    backend.store(|_t| {}).await
}

#[cfg(not(feature = "use_async_fn"))]
pub fn transfer<'a, 'b, B: 'a + Backend<'a> + Send + Sync>(
    backend: &'b mut B,
) -> impl 'b + Captures<'a> + std::future::Future<Output = ()> + Send {
    async move { backend.store(|_t| {}).await }
}

// This works
fn _test1<'a>(mut backend: impl 'a + Backend<'a> + Send + Sync) {
    let _: Pin<Box<dyn Send>> = Box::pin(async {
        transfer(&mut backend).await;
    });
}

// This doesn't
fn _test2<'a>(mut backend: MyBackend<'a>) {
    let _: Pin<Box<dyn Send>> = Box::pin(async {
        transfer(&mut backend).await;
    });
}

fn _hidemytype<'a>(backend: MyBackend<'a>) -> impl 'a + Backend<'a> + Send + Sync {
    backend
}

// This does!
fn _test3<'a>(backend: MyBackend<'a>) {
    let _: Pin<Box<dyn Send>> = Box::pin(async {
        transfer(&mut _hidemytype(backend)).await;
    });
}

// And so does this
fn _test4<'a>(backend: MyBackend<'a>) {
    _test1(backend);
}

fn main() {}

It succeeds with cargo build (using the second transfer), but if you use the first transfer by running cargo build --features use_async_fn, you get a very confusing error:

$ cargo build --features use_async_fn
   Compiling future_pin_issue v0.1.0 (/home/joe/issue-test)
error: implementation of `Backend` is not general enough
  --> src/main.rs:52:33
   |
52 |       let _: Pin<Box<dyn Send>> = Box::pin(async {
   |  _________________________________^
53 | |         transfer(&mut backend).await;
54 | |     });
   | |______^ implementation of `Backend` is not general enough
   |
   = note: `Backend<'1>` would have to be implemented for the type `MyBackend<'0>`, for any two lifetimes `'0` and `'1`...
   = note: ...but `Backend<'2>` is actually implemented for the type `MyBackend<'2>`, for some specific lifetime `'2`

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

The second transfer implementation (used when use_async_fn is off) is a workaround based on the desugar of async fn, plus a Captures trait I found on stackoverflow.

Confusingly, the error only ever occurs in _test2. _test3 is especially concerning, because it means that "forgetting" information about the type makes the typechecking succeed somehow!

Removing the Backend::<'a>::MyStorage associated type, changing store to store<F,MyStorage: Storage<'a>>, and calling backend.store::<_,AtomicStorage>(...) rather than backend.store(...) also fixes the error.

This looks like a potential type inference bug, but it might be expected behavior. If it's expected, I think the error messages could use some work.

Meta

rustc --version --verbose:

$ rustc --version --verbose
rustc 1.55.0 (c8dfcfe04 2021-09-06)
binary: rustc
commit-hash: c8dfcfe046a7680554bf4eb612bad840e7631c4b
commit-date: 2021-09-06
host: x86_64-unknown-linux-gnu
release: 1.55.0
LLVM version: 12.0.1
$ cargo --version --verbose
cargo 1.55.0 (32da73ab1 2021-08-23)
release: 1.55.0
commit-hash: 32da73ab19417aa89686e1d85c1440b72fdf877d
commit-date: 2021-08-23
@jpdoyle jpdoyle added the C-bug Category: This is a bug. label Oct 8, 2021
jbearer added a commit to EspressoSystems/espresso that referenced this issue Jan 12, 2022
@fmease fmease added A-lifetimes Area: Lifetimes / regions T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-async-await Area: Async & Await S-needs-repro Status: This issue has no reproduction and needs a reproduction to make progress. and removed needs-triage-legacy labels Jan 24, 2024
@traviscross
Copy link
Contributor

@rustbot labels +AsyncAwait-Triaged +WG-async

We reviewed this today in WG-async triage.

We believe this is a manifestation of what's being worked on and tracked in #110338.

@rustbot rustbot added AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. WG-async Working group: Async & await labels Feb 26, 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-lifetimes Area: Lifetimes / regions AsyncAwait-Triaged Async-await issues that have been triaged during a working group meeting. C-bug Category: This is a bug. S-needs-repro Status: This issue has no reproduction and needs a reproduction to make progress. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-async Working group: Async & await
Projects
None yet
Development

No branches or pull requests

5 participants