rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.24k stars 12.71k forks source link

Closure-like blocks capture all generic type and const parameters #65442

Open jonhoo opened 5 years ago

jonhoo commented 5 years ago

I'd like to write the following code:

use std::future::Future;
struct Foo<A>(A);
impl<A> Foo<A> {
    fn bar<Q, R: Default>(&mut self, q: Q) -> impl Future<Output = R> {
        let _ = q;
        async move {
            R::default()
        }
    }

    fn baz(&mut self, x: &str) -> impl Future<Output = usize> {
        self.bar(x)
    }
}

In particular, I would like to have the impl Trait returned by baz not be tied to the lifetime of its &str argument. Since impl Trait captures the lifetimes of all generic arguments (as per RFC 1951), I can't write the code this way though. So instead, I tried

#![feature(type_alias_impl_trait)]
use std::future::Future;
struct Foo<A>(A);

type BarFut<A, R> = impl Future<Output = R>;
impl<A> Foo<A> {
    fn bar<Q, R: Default>(&mut self, q: Q) -> BarFut<A, R> {
        let _ = q;
        async move {
            R::default()
        }
    }

    fn baz(&mut self, x: &str) -> impl Future<Output = usize> {
        self.bar(x)
    }
}

However, with this, I get the error:

error: type parameter `Q` is part of concrete type but not used in parameter list for the `impl Trait` type alias

This seems odd, since Q is (intentionally) not used in the async block. I can work around this by adding an async fn and calling that instead of using async move, but that seems like an odd hack:

async fn make_r_fut<R: Default>() -> R {
    R::default()
}
// ...
    fn bar<Q, R: Default>(&mut self, q: Q) -> BarFut<A, R> {
        let _ = q;
        make_r_fut()
    }
// ...

Is it intentional that the async block "captures" Q here, even though it never contains a Q?

Mark-Simulacrum commented 5 years ago

I suspect the answer is no -- cc @rust-lang/wg-async-await -- this might contribute to "not Send/Sync" errors if we're capturing what we shouldn't be

nikomatsakis commented 5 years ago

I think my expectation is that the explicit type = impl Trait version should work. I'm not 100% sure why it doesn't, but I can do some investigation.

nikomatsakis commented 5 years ago

Assigning to myself to investigate and document the cause of the issue, at least.

Arnavion commented 4 years ago

Related to / dupe of https://github.com/rust-lang/rust/issues/42940

glittershark commented 3 years ago

I hate bumping issues but... any movement on this? Just ran into it myself in a very similar context

nikomatsakis commented 3 years ago

No progress I know of

ranile commented 2 years ago

Note that this bug does not require async to trigger. Playground where this triggered using type_alias_impl_trait. I'm not sure if/how this is related #42940 (as mentioned in the comment above). Looking through the discussion there, I couldn't find any mentions of the error returned by the compiler.

SOF3 commented 2 years ago

Reproduced with a more minimal example: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=319e66180308bede359b022cda8ed0bf

#![feature(type_alias_impl_trait)]

type Ty = impl Fn();

fn run<E>(_: E) -> Ty {
    || ()
}

   Compiling playground v0.0.1 (/playground)
error: type parameter `E` is part of concrete type but not used in parameter list for the `impl Trait` type alias
 --> src/lib.rs:6:5
  |
6 |     || ()
  |     ^^^^^

error: could not compile `playground` due to previous error```
traviscross commented 1 year ago

Here's the minimum async example of this problem:

#![feature(type_alias_impl_trait)]

use std::future::Future;

type Deferred = impl Future<Output = ()> + 'static;

fn future_out<F>(_: F) -> Deferred {
    async move { }
}
error: type parameter `F` is part of concrete type but not used in parameter list for the `impl Trait` type alias
 --> src/lib.rs:8:5
  |
8 |     async move { }
  |     ^^^^^^^^^^^^^^

error: could not compile `playground` (lib) due to previous error

Playground link.

traviscross commented 1 year ago

Notably, there's discussion over in #107645 about the possibility of limiting TAIT to exclude defining uses where the TAIT appears in argument position in the function signature. If that were to be adopted, one plausible workaround would look like this:

#![feature(type_alias_impl_trait)]

use std::future::Future;
use tokio::sync::oneshot::Sender;

type Deferred = impl Future<Output = ()> + 'static;

fn sends_future_kludge<F: Future<Output = ()> + 'static>(f: F) -> Deferred {
    async move { f.await }
}

fn sends_future(tx: Sender<Deferred>) {
    let _ = tx.send(sends_future_kludge(async move {}));
}

Playground link.

However, that workaround would be blocked on this issue.