rust icon indicating copy to clipboard operation
rust copied to clipboard

Fn trait doesn't allow impl returns (impl Fn() -> impl Trait), which is inconsistent with all other traits

Open arduano opened this issue 1 year ago • 1 comments

I've been working on a functional interface for a library which relies on code generation, so I have functions returning functions, however I've hit a roadblock with functions returning closures which return traits (without resorting to dynamic dispatch).

The compiler doesn't allow impl Fn() -> impl Trait while allowing impl Trait<T = impl Trait> which seems inconsistent.

Here is my minimal reproduction code:

// This works of course
fn closure() -> impl Fn() -> bool {
    let f = || true;
    f
}

// Error
// `impl Trait` only allowed in function and inherent method return types, not in `Fn` trait return
fn future_closure() -> impl Fn() -> impl Future<Output = bool> {
    let f = || async { true };
    f
}

// Same error
fn future_closure_arg(arg: impl Fn() -> impl Future<Output = bool>) {}

// This works though
fn iter_impl(arg: impl Iterator<Item = impl Future<Output = bool>>) {}

However, seems like wrapping the Fn in another trait fixes the error, so this doesn't appear to be a functionality limitation but more of an oversight:

// This compiles fine

trait Func {
    type T;
    fn call(&self) -> Self::T;
}

impl<F: Fn() -> R, R> Func for F {
    type T = R;

    fn call(&self) -> Self::T {
        self()
    }
}

fn future_trait_closure() -> impl Func<T = impl Future<Output = bool>> {
    let f = || async { true };
    f
}

Meta

rustc --version --verbose:

rustc 1.63.0 (4b91a6ea7 2022-08-08)
binary: rustc
commit-hash: 4b91a6ea7258a947e59c6522cd5898e7c0a6a88f
commit-date: 2022-08-08
host: x86_64-unknown-linux-gnu
release: 1.63.0
LLVM version: 14.0.5

arduano avatar Sep 18 '22 07:09 arduano

Note that writing impl Fn<(), Output = impl Future<Output = bool>> with #![feature(unboxed_closures)] does not give the error, so perhaps its something to do with how Fn() -> Ret syntax is parsed or converted to a bound? (link to playground)

zachs18 avatar Sep 18 '22 18:09 zachs18

Note that writing impl Fn<(), Output = impl Future<Output = bool>> with #![feature(unboxed_closures)] does not give the error...

Just a quick note: compilation still fails for some code using the latest nightly builds (though it used to compile fine with older ones, such as nightly-2022-06-29). For example:

#![feature(unboxed_closures)]

use std::future::Future;

fn less_than<'a>(number: &'a i32) -> impl FnOnce<(i32,), Output = impl Future<Output = bool> + 'a> {
    move |n: i32| async move { n < *number }
}

fn main() {
    let zero = 0;
    let less_than_zero = less_than(&zero);
}

Compilation output:

error: concrete type differs from previous defining opaque type use
 --> src/lib.rs:6:19
  |
6 |     move |n: i32| async move { n < *number }
  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `impl Future<Output = bool>`, got `impl Future<Output = bool>`
  |
note: previous use here
 --> src/lib.rs:6:5
  |
6 |     move |n: i32| async move { n < *number }
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0720]: cannot resolve opaque type
 --> src/lib.rs:5:67
  |
5 | fn less_than<'a>(number: &'a i32) -> impl FnOnce<(i32,), Output = impl Future<Output = bool> + 'a> {
  |                                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot resolve opaque type

For more information about this error, try `rustc --explain E0720`.

Link to playground.

jpalaciosdev avatar Sep 20 '22 15:09 jpalaciosdev

@jpalaciosdev: That regressed in #96727 cc @oli-obk

Seems to be a problem with RPIT inference in borrowck

compiler-errors avatar Sep 20 '22 19:09 compiler-errors

A bit of an update: future_closure from the original issue now works with #![feature(impl_trait_in_fn_trait_return)] (tracking issue):

#![feature(impl_trait_in_fn_trait_return)]
use core::future::Future;

fn future_closure() -> impl Fn() -> impl Future<Output = bool> {
    let f = || async { true };
    f
}

Can this be closed then?


As a side note in argument position it still doesn't work (because we may want to special case fn trait syntax to work better with lifetimes...), however it's easy to transform APIT into a generic:

fn future_closure_arg(arg: impl Fn() -> impl Future<Output = bool>) {}
// =>
fn future_closure_arg<Fut: Future<Output = bool>>(arg: impl Fn() -> Fut) {}

WaffleLapkin avatar Oct 31 '22 11:10 WaffleLapkin