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

Allow impl Fn() -> impl Trait #93082

Closed

Conversation

WaffleLapkin
Copy link
Member

@WaffleLapkin WaffleLapkin commented Jan 19, 2022

This allows writing the following function signatures:

fn f0() -> impl Fn() -> impl Trait;
fn f1(_: impl Fn() -> impl Trait);
fn f2<F: Fn() -> impl Trait>(_: F);
fn f3() -> &'static dyn Fn() -> impl Trait;
fn f4(_: &dyn Fn() -> impl Trait);

All of the above are already allowed with common traits and associated
types, there is no reason why Fn* traits should be special in this
regard.

There even is a test that f0 compiles:

fn allowed_in_ret_type() -> impl Fn() -> impl Into<u32> {
//~^ `impl Trait` not allowed
|| 5
}

But it was changed in PR 48084 (lines) to test the opposite, probably unintentionally given PR 48084 (lines).

r? @oli-obk


This limitation is especially annoying with async code, since it forces one to write this:

trait AsyncFn3<A, B, C>: Fn(A, B, C) -> <Self as AsyncFn3<A, B, C>>::Future {
    type Future: Future<Output = Self::Out>;

    type Out;
}

impl<A, B, C, Fut, F> AsyncFn3<A, B, C> for F
where
    F: Fn(A, B, C) -> Fut,
    Fut: Future,
{
    type Future = Fut;

    type Out = Fut::Output;
}

fn async_closure() -> impl AsyncFn3<i32, i32, i32, Out = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}

Instead of:

fn async_closure() -> impl Fn(i32, i32, i32) -> impl Future<Output = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}

This allows writing the following function signatures:
```rust
fn f0() -> impl Fn() -> impl Trait;
fn f1(_: impl Fn() -> impl Trait);
fn f2<F: Fn() -> impl Trait>(_: F);
fn f3() -> &'static dyn Fn() -> impl Trait;
fn f4(_: &dyn Fn() -> impl Trait);
```

All of the above are already allowed with common traits and associated
types, there is no reason why `Fn*` traits should be special in this
regard.
@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Jan 19, 2022
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 19, 2022
@oli-obk oli-obk added T-lang Relevant to the language team, which will review and decide on the PR/issue. I-lang-nominated Nominated for discussion during a lang team meeting. labels Jan 19, 2022
@oli-obk
Copy link
Contributor

oli-obk commented Jan 19, 2022

cc @rust-lang/lang this seems like a very straight-forward stabilization, especially considering there are workarounds that allow the same thing.

@cramertj
Copy link
Member

Eventually I'd like this to work:

fn print_some_things_times_two(times_two: impl Fn(impl Add) -> impl Debug) {
    println!("{:?}", times_two(1u8));
    println!("{:?}", times_two(1u32));
}

print_some_things_times_two(|x| x + x);

Note that the return impl Debug type from the Fn depends on the type of the impl Add. Today, impl Trait in the return position of Fn signatures would desugar to a single type parameter on the top-level function, which isn't, I think, the behavior we want long-term. It might be that there's no way to observe this without also allowing impl Trait in the argument position of Fn signatures, in which case this stabilization is forwards-compatible with such a change. However, I want to be pretty confident that we can evolve towards this in the future before landing this stabilization.

@WaffleLapkin
Copy link
Member Author

@cramertj shouldn't the type be impl Fn(impl Add<Output = impl Debug> + Copy) -> impl Debug for this to potentially work?

Note that this actually requires type-HRTB (or is it HKT?...) (as well as generic closures, oh wow):

for<T>
where
    T: Add<Output = impl Debug> + Copy
impl Fn(T) -> impl Debug

Personally, I think that

fn f(_: impl Fn(impl Tr)) {}

Should desugar to

fn f<F, A>(_: F)
where
    F: Fn(A),
    A: Tr,
{}

As this is more consistent with Ty<impl Tr>.

But I believe that this PR is compatible with both options.

@cramertj
Copy link
Member

@WaffleLapkin It's exactly type-HRTB + generic closures, yes.

@joshtriplett
Copy link
Member

So, if we decide to do this, we're deciding on an associativity for -> ... -> ... ->.

For instance, what happens if you write fn func() -> impl Fn() -> impl Fn() -> impl Debug? What precisely does that mean?

I would expect it to mean fn func() -> impl (Fn() -> impl (Fn() -> impl Debug)), which implies that -> is right-associative and binds from right to left. I think that's the correct answer; I don't think it would make sense for it to associate the other way.

Could you please add a test case demonstrating that associativity? Given such a test case, I'd be happy to propose this for FCP.

@joshtriplett joshtriplett removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Jan 19, 2022
@joshtriplett
Copy link
Member

(Removing T-compiler in order to do a T-lang FCP)

@joshtriplett
Copy link
Member

@WaffleLapkin Thanks, that test case looks great!

@rust-lang/lang: Do we want to commit to right-associativity (rather than non-associativity) for ->, such that fn func() -> impl Fn() -> impl Fn() -> impl Debug works?

@rfcbot merge

@rfcbot
Copy link

rfcbot commented Jan 19, 2022

Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:

Concerns:

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Jan 19, 2022
@joshtriplett joshtriplett removed the I-lang-nominated Nominated for discussion during a lang team meeting. label Jan 19, 2022
@ehuss
Copy link
Contributor

ehuss commented Jan 19, 2022

Would it be possible to update the documentation at https://github.com/rust-lang/reference/blob/master/src/types/impl-trait.md to make it clear this is allowed (and any other details that seem worthy to add)?

@veber-alex
Copy link
Contributor

Will this allow:

fn new() -> impl for<'a> FnOnce(&'a i32) -> (impl Future<Output = &'a i32> + 'a) {
    |x| async move { x }
}

instead of the current:

fn old() -> impl for<'a> FnOnce(&'a i32) -> Box<dyn Future<Output = &'a i32> + 'a> {
    |x| Box::new(async move { x })
}

?

@cramertj
Copy link
Member

@rfcbot concern forward-compatibility-with-apit-type-hrtb

See #93082 (comment)

@WaffleLapkin
Copy link
Member Author

@ehuss it should be possible to update the reference, yes (it also doesn't seem to mention impl Iterator<Item = impl Debug> kind of things).

@veber-alex this will allow the following:

fn new() -> impl FnOnce(i32) -> impl Future<Output = i32> {
    |x| async move { x }
}

Your example with lifetime currently segfaults compiler, see #88236. But when this bug will be fixed, your example will be fine too.

@WaffleLapkin
Copy link
Member Author

Actually I'm now thinking, when #88236 is fixed, will impl for<'a> Tr<'a, A = impl B + 'a> work as expected? ie will <T as Tr<'x>>::A and <T as Tr<'x>>::B be different types? 🤔

@nikomatsakis
Copy link
Contributor

We have been specifically ruling this out because we wanted a clear determination of what it means to use impl Trait in all places.

I do want to permit this, but I want to incorporate it into https://rust-lang.github.io/impl-trait-initiative/ with a clearly documented set of rules. We should also permit T: impl Foo<Output = impl Bar>, if we don't already, and also make sure to test lifetime erasure interactions.

@rfcbot concern niko-wants-to-do-a-good-review

@WaffleLapkin
Copy link
Member Author

@nikomatsakis impl Foo<Output = impl Bar> is already permitted. And impl Fn() -> impl Bar should follow the same rules.

If I understand correctly, in Assoc = impl Trait, impl Trait means the same as the "outer impl Trait".

For example param: impl Tr<A = impl Trait> means two generics and -> impl Tr<A = impl Trait> means two existential types.

@nikomatsakis
Copy link
Contributor

@WaffleLapkin ok, +1, and I agree they should have the same meaning.

@fredlahde
Copy link

I ran into a segfault when trying to return very deeply nested function compositions (with a depth of 560). I filed #93237 for that.

@ericsampson
Copy link

Has anyone nailed down the answer to Josh’s question about the desired spec for associativity?

@WaffleLapkin
Copy link
Member Author

I've created a PR to the reference, that clarifies existing and added in this PR rules on where impl Trait can be used: rust-lang/reference#1144

@scottmcm
Copy link
Member

impl Foo<Output = impl Bar> is already permitted. And impl Fn() -> impl Bar should follow the same rules.

That argument is persuasive to me.

Just to confirm, it's only about output position here? It's not enabling -> impl Bar<impl Hello, impl World>?

@nikomatsakis
Copy link
Contributor

@rfcbot resolve niko-wants-to-do-a-good-review

@rfcbot concern interaction-of-parens

Discussing in the @rust-lang/lang meeting and came up with some concerns. I am trending against stabilizing now, based on these. Let me try to spell it out.

The first concern is this: I have generally wanted '_ and impl Trait to act in analogous ways, but this proposal diverges from that. In particular, with impl Fn(&u32) -> Foo<'_>, the '_ refers back to something from the arguments, which is bound at the Fn level. But impl Fn() -> impl Debug introduces a binder at the surrounding fn item, which means that e.g. impl Fn(&u32) -> impl PartialEq<&u32> behaves in some very weird way.

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jan 25, 2022

@rfcbot concern what-should-return-in-arg-mean

We discussed what fn foo(x: impl Fn() -> impl Debug) would ideally mean and realized that it's rather unclear. The following seem not great:

  • The proposal fn foo<T: Debug>(x: impl Fn() -> T) means that T cannot reference higher-bound regions -- and I eventually expect there to be higher-ranked types too that cannot be named (or captured!).
  • The proposal fn foo(x: impl for<T: Debug> Fn() -> T) is very rarely what you want.

@cramertj pointed out that what you really want is probably something like fn foo(x: impl exists<T: Debug> Fn() -> T), which might be something we can express at some point (they wrote it as fn foo(x: impl Fn<(), Output: Debug>), essentially, I'm not sure what our current implementation of Foo: Bar bounds does in that situation....

I can whip up some tests here.

@nikomatsakis
Copy link
Contributor

BTW, @cramertj pointed out that this particular case would be ok:

fn foo() -> impl Fn() -> impl Debug

That seems probably true, although it is still somewhat more limited than what it could eventually support.

@WaffleLapkin
Copy link
Member Author

Just to confirm, it's only about output position here? It's not enabling -> impl Bar<impl Hello, impl World>?

@scottmcm Yes, here is the test for this:

fn bad_in_arg_position(_: impl Into<impl Debug>) { }

@WaffleLapkin
Copy link
Member Author

The proposal fn foo<T: Debug>(x: impl Fn() -> T) means that T cannot reference higher-bound regions -- and I eventually expect there to be higher-ranked types too that cannot be named (or captured!).

Well, it's actually pretty easy to come up with an example, where we want higher-bound regions -- |x: &_| move async { x }. That said, I still think that this (foo<T: Debug>) is the right choice here, because having an inconsistency between Fn() -> impl Trait and Fn<(), Output = impl Trait> seems very unpleasant.

The proposal fn foo(x: impl for<T: Debug> Fn() -> T) is very rarely what you want.

I don't even think that we can currently create a type that would satisfy such bound, at least on stable.

@cramertj pointed out that what you really want is probably something like fn foo(x: impl exists<T: Debug> Fn() -> T), which might be something we can express at some point (they wrote it as fn foo(x: impl Fn<(), Output: Debug>), essentially, I'm not sure what our current implementation of Foo: Bar bounds does in that situation....

  • impl exists<T: Debug> Fn() -> T seems weird to me, because I've always thought that exists<T: Debug> implies that there is a single type that is known as T, but we obviously (?) want to accept functions that return different types, so this is not an option, is it. Am I missing something?...
  • impl Fn<(), Output: Debug> if I'm not mistaken this should desugar to foo<F: Fn<()>> where F::Output: Debug which is practically equivalent to foo<F: Fn<(), Output = O>, O> where O: Debug (I'm not very familiar with this feature though, so don't take my word for it, but the RFC seems to suggest the same)

TIL that you can't write arg: impl for<'a> Add<&'a u32, Output = impl PartialEq<&'a u32>> uh oh (also the error isn't great for this, "use of undeclared lifetime name 'a")

@nikomatsakis
Copy link
Contributor

>  because having an inconsistency between Fn() -> impl Trait and Fn<(), Output = impl Trait> seems very unpleasant.

I thought that at first, but then I remembered that -- indeed -- that is part of the point of () syntax, that it is different from <> with respect to how higher-ranked regions are bound. For example, parameters like impl Fn(&u8) and impl Foo<&u8> are not the same. The former is for<'a> Fn(&'a u8) and the latter is Foo<&'a u8> where 'a is bound on the enclosing function.

@WaffleLapkin
Copy link
Member Author

So, it seems like we don't want impl Fn() -> impl Trait in argument position to mean what this PR makes it, or at least, not right now. Since you can already write practically the same on stable (f<T: Trait>(_: impl Fn() -> T)) there is no need for this either.

If I understood the conversation right, return position impl Fn is not as controversial (and it's also impossible to write it on stable).

So, am I right, that the way forward is to update this PR to only allow return-position-impl Fn?

@nikomatsakis
Copy link
Contributor

@rfcbot fcp cancel

We discussed this in our @rust-lang/lang meeting today. The conclusion was that we would like to do two things:

  • To preserve the insights that we gained here, in terms of potential interactions, on the https://rust-lang.github.io/impl-trait-initiative/ repository (that's on me!)
  • To update the PR to covert "RPITIRPIT", i.e., return-position impl Trait in return position impl trait. i.e., fn foo() -> impl Fn() -> impl Debug, specifically.

@WaffleLapkin if you are up to do the latter, that would be great! 💜 I'm going to close this PR for now, though, so that we can have a fresh discussion once you've got that ready.

@rfcbot rfcbot removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Feb 1, 2022
@cramertj
Copy link
Member

cramertj commented Feb 1, 2022

fn foo() -> impl Fn() -> impl Debug

One potential source of weirdness is the possible ambiguity created by lifetimes in the impl Debug (the RPITIRPIT) type. I assume we want it to be able to capture lifetimes introduced by the Fn. That is, -> for<'a> Fn(&'a u8) -> impl Debug + 'a. Can one write this as -> Fn(&u8) -> impl Debug + '_? it seems at least visually ambiguous whether the + '_ should apply to the impl Fn or the impl Debug-- presumably we want to just error in the case that elided lifetimes are introduced both in the top-level fn arguments as well as in the impl Fn?

@WaffleLapkin
Copy link
Member Author

@nikomatsakis sure, I'll try to update the PR to allow specifically RPITIRPIT :)

@Mathspy
Copy link

Mathspy commented Feb 2, 2022

RPITRPIT

The return frog joins the turbo fish in guiding the young crab in this new exciting adventure of finding the never type

@WaffleLapkin
Copy link
Member Author

New PR that only allows impl Fn() -> impl Trait in return position is up: #93582!

Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Oct 28, 2022
…errors

Allow `impl Fn() -> impl Trait` in return position

_This was originally proposed as part of rust-lang#93082 which was [closed](rust-lang#93082 (comment)) due to allowing `impl Fn() -> impl Trait` in argument position._

This allows writing the following function signatures:
```rust
fn f0() -> impl Fn() -> impl Trait;
fn f3() -> &'static dyn Fn() -> impl Trait;
```

These signatures were already allowed for common traits and associated types, there is no reason why `Fn*` traits should be special in this regard.

`impl Trait` in both `f0` and `f3` means "new existential type", just like with `-> impl Iterator<Item = impl Trait>` and such.

Arrow in `impl Fn() ->` is right-associative and binds from right to left, it's tested by [this test](https://github.com/WaffleLapkin/rust/blob/a819fecb8dea438fc70488ddec30a61e52942672/src/test/ui/impl-trait/impl_fn_associativity.rs).

There even is a test that `f0` compiles:
https://github.com/rust-lang/rust/blob/2f004d2d401682e553af3984ebd9a3976885e752/src/test/ui/impl-trait/nested_impl_trait.rs#L25-L28

But it was changed in [PR 48084 (lines)](https://github.com/rust-lang/rust/pull/48084/files#diff-ccecca938872d65ffe8cd1c3ef1956e309fac83bcda547d8b16b89257e53a437R37)  to test the opposite, probably unintentionally given [PR 48084 (lines)](https://github.com/rust-lang/rust/pull/48084/files#diff-5a02f1ed43debed1fd24f7aad72490064f795b9420f15d847bac822aa4621a1cR476-R477).

r? `@nikomatsakis`

----

This limitation is especially annoying with async code, since it forces one to write this:
```rust
trait AsyncFn3<A, B, C>: Fn(A, B, C) -> <Self as AsyncFn3<A, B, C>>::Future {
    type Future: Future<Output = Self::Out>;

    type Out;
}

impl<A, B, C, Fut, F> AsyncFn3<A, B, C> for F
where
    F: Fn(A, B, C) -> Fut,
    Fut: Future,
{
    type Future = Fut;

    type Out = Fut::Output;
}

fn async_closure() -> impl AsyncFn3<i32, i32, i32, Out = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}
```
Instead of:
```rust
fn async_closure() -> impl Fn(i32, i32, i32) -> impl Future<Output = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}
```
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Oct 28, 2022
…errors

Allow `impl Fn() -> impl Trait` in return position

_This was originally proposed as part of rust-lang#93082 which was [closed](rust-lang#93082 (comment)) due to allowing `impl Fn() -> impl Trait` in argument position._

This allows writing the following function signatures:
```rust
fn f0() -> impl Fn() -> impl Trait;
fn f3() -> &'static dyn Fn() -> impl Trait;
```

These signatures were already allowed for common traits and associated types, there is no reason why `Fn*` traits should be special in this regard.

`impl Trait` in both `f0` and `f3` means "new existential type", just like with `-> impl Iterator<Item = impl Trait>` and such.

Arrow in `impl Fn() ->` is right-associative and binds from right to left, it's tested by [this test](https://github.com/WaffleLapkin/rust/blob/a819fecb8dea438fc70488ddec30a61e52942672/src/test/ui/impl-trait/impl_fn_associativity.rs).

There even is a test that `f0` compiles:
https://github.com/rust-lang/rust/blob/2f004d2d401682e553af3984ebd9a3976885e752/src/test/ui/impl-trait/nested_impl_trait.rs#L25-L28

But it was changed in [PR 48084 (lines)](https://github.com/rust-lang/rust/pull/48084/files#diff-ccecca938872d65ffe8cd1c3ef1956e309fac83bcda547d8b16b89257e53a437R37)  to test the opposite, probably unintentionally given [PR 48084 (lines)](https://github.com/rust-lang/rust/pull/48084/files#diff-5a02f1ed43debed1fd24f7aad72490064f795b9420f15d847bac822aa4621a1cR476-R477).

r? ``@nikomatsakis``

----

This limitation is especially annoying with async code, since it forces one to write this:
```rust
trait AsyncFn3<A, B, C>: Fn(A, B, C) -> <Self as AsyncFn3<A, B, C>>::Future {
    type Future: Future<Output = Self::Out>;

    type Out;
}

impl<A, B, C, Fut, F> AsyncFn3<A, B, C> for F
where
    F: Fn(A, B, C) -> Fut,
    Fut: Future,
{
    type Future = Fut;

    type Out = Fut::Output;
}

fn async_closure() -> impl AsyncFn3<i32, i32, i32, Out = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}
```
Instead of:
```rust
fn async_closure() -> impl Fn(i32, i32, i32) -> impl Future<Output = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}
```
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this pull request Oct 30, 2022
…errors

Allow `impl Fn() -> impl Trait` in return position

_This was originally proposed as part of rust-lang#93082 which was [closed](rust-lang#93082 (comment)) due to allowing `impl Fn() -> impl Trait` in argument position._

This allows writing the following function signatures:
```rust
fn f0() -> impl Fn() -> impl Trait;
fn f3() -> &'static dyn Fn() -> impl Trait;
```

These signatures were already allowed for common traits and associated types, there is no reason why `Fn*` traits should be special in this regard.

`impl Trait` in both `f0` and `f3` means "new existential type", just like with `-> impl Iterator<Item = impl Trait>` and such.

Arrow in `impl Fn() ->` is right-associative and binds from right to left, it's tested by [this test](https://github.com/WaffleLapkin/rust/blob/a819fecb8dea438fc70488ddec30a61e52942672/src/test/ui/impl-trait/impl_fn_associativity.rs).

There even is a test that `f0` compiles:
https://github.com/rust-lang/rust/blob/2f004d2d401682e553af3984ebd9a3976885e752/src/test/ui/impl-trait/nested_impl_trait.rs#L25-L28

But it was changed in [PR 48084 (lines)](https://github.com/rust-lang/rust/pull/48084/files#diff-ccecca938872d65ffe8cd1c3ef1956e309fac83bcda547d8b16b89257e53a437R37)  to test the opposite, probably unintentionally given [PR 48084 (lines)](https://github.com/rust-lang/rust/pull/48084/files#diff-5a02f1ed43debed1fd24f7aad72490064f795b9420f15d847bac822aa4621a1cR476-R477).

r? `@nikomatsakis`

----

This limitation is especially annoying with async code, since it forces one to write this:
```rust
trait AsyncFn3<A, B, C>: Fn(A, B, C) -> <Self as AsyncFn3<A, B, C>>::Future {
    type Future: Future<Output = Self::Out>;

    type Out;
}

impl<A, B, C, Fut, F> AsyncFn3<A, B, C> for F
where
    F: Fn(A, B, C) -> Fut,
    Fut: Future,
{
    type Future = Fut;

    type Out = Fut::Output;
}

fn async_closure() -> impl AsyncFn3<i32, i32, i32, Out = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}
```
Instead of:
```rust
fn async_closure() -> impl Fn(i32, i32, i32) -> impl Future<Output = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}
```
RalfJung pushed a commit to RalfJung/miri that referenced this pull request Nov 5, 2022
Allow `impl Fn() -> impl Trait` in return position

_This was originally proposed as part of #93082 which was [closed](rust-lang/rust#93082 (comment)) due to allowing `impl Fn() -> impl Trait` in argument position._

This allows writing the following function signatures:
```rust
fn f0() -> impl Fn() -> impl Trait;
fn f3() -> &'static dyn Fn() -> impl Trait;
```

These signatures were already allowed for common traits and associated types, there is no reason why `Fn*` traits should be special in this regard.

`impl Trait` in both `f0` and `f3` means "new existential type", just like with `-> impl Iterator<Item = impl Trait>` and such.

Arrow in `impl Fn() ->` is right-associative and binds from right to left, it's tested by [this test](https://github.com/WaffleLapkin/rust/blob/a819fecb8dea438fc70488ddec30a61e52942672/src/test/ui/impl-trait/impl_fn_associativity.rs).

There even is a test that `f0` compiles:
https://github.com/rust-lang/rust/blob/2f004d2d401682e553af3984ebd9a3976885e752/src/test/ui/impl-trait/nested_impl_trait.rs#L25-L28

But it was changed in [PR 48084 (lines)](https://github.com/rust-lang/rust/pull/48084/files#diff-ccecca938872d65ffe8cd1c3ef1956e309fac83bcda547d8b16b89257e53a437R37)  to test the opposite, probably unintentionally given [PR 48084 (lines)](https://github.com/rust-lang/rust/pull/48084/files#diff-5a02f1ed43debed1fd24f7aad72490064f795b9420f15d847bac822aa4621a1cR476-R477).

r? `@nikomatsakis`

----

This limitation is especially annoying with async code, since it forces one to write this:
```rust
trait AsyncFn3<A, B, C>: Fn(A, B, C) -> <Self as AsyncFn3<A, B, C>>::Future {
    type Future: Future<Output = Self::Out>;

    type Out;
}

impl<A, B, C, Fut, F> AsyncFn3<A, B, C> for F
where
    F: Fn(A, B, C) -> Fut,
    Fut: Future,
{
    type Future = Fut;

    type Out = Fut::Output;
}

fn async_closure() -> impl AsyncFn3<i32, i32, i32, Out = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}
```
Instead of:
```rust
fn async_closure() -> impl Fn(i32, i32, i32) -> impl Future<Output = u32> {
    |a, b, c| async move { (a + b + c) as u32 }
}
```
@kevincox
Copy link
Contributor

kevincox commented Mar 7, 2023

Is there a tracking issue for this in an argument position? I specifically want to accept something like

fn foo(
    f: impl for<'a> FnOnce(&'a Args) -> impl 'a + std::future::Future<Res>,
)

@WaffleLapkin
Copy link
Member Author

@kevincox as far as I know, there isn't, and it would be hard to allow what you want, since impl 'a + Future<Res> here is a """family""" of types (a different type for each possible 'a) and Rust's type system just straight up does not support equating associated types to """families""". (I'm not sure if I used the correct terminology)

@Jules-Bertholet
Copy link
Contributor

@kevincox the following works on Nightly with -Ztrait-solver=next:

#![feature(unboxed_closures)]

use std::future::Future;

struct Args;
struct Res;

fn foo<F>(f: F)
where
    F: for<'a> FnOnce<(&'a Args,)>,
    for<'a> <F as FnOnce<(&'a Args,)>>::Output: Future<Output = Res>,
{
    todo!()
}

fn main() {
    foo(|_: &Args| async { Res })
}

@nikomatsakis
Copy link
Contributor

Filed rust-lang/impl-trait-initiative#15 to track impl Fn() -> impl Trait and rust-lang/impl-trait-initiative#16 for impl Fn(impl Trait) (cc @kevincox)

@traviscross traviscross added the A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. label Aug 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.