rust-lang / rust

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

Closure type mismatch on higher-ranked bounds #51004

Open cramertj opened 6 years ago

cramertj commented 6 years ago

In the following code, identical impls for a concrete type, a fn type, and a closure exist yet the only the closure fails to meet the higher-ranked bound:

trait FnLt<'a> {
    fn apply(self, input: &'a u8) -> &'a u8;
}

// Struct impl
struct Foo;
impl<'a> FnLt<'a> for Foo {
    fn apply(self, input: &'a u8) -> &'a u8 {
        input
    }
}

// Closure impl
impl<'a, T> FnLt<'a> for T
where
    T: FnOnce(&'a u8) -> &'a u8,
{
    fn apply(self, input: &'a u8) -> &'a u8 {
        (self)(input)
    }
}

fn take_fn_lt(_: impl for<'a> FnLt<'a>) {}

fn main() {
    take_fn_lt(Foo); // Works

    fn foo(x: &u8) -> &u8 { x }
    take_fn_lt(foo); // Works

    take_fn_lt(|x: &u8| -> &u8 { x }); // Doesn't work
}

The error is this:

error[E0271]: type mismatch resolving `for<'a> <[closure@src/main.rs:31:16: 31:37] as std::ops::FnOnce<(&'a u8,)>>::Output == &'a u8`
  --> src/main.rs:31:5
   |
31 |     take_fn_lt(|x: &u8| -> &u8 { x }); // Doesn't work
   |     ^^^^^^^^^^ expected bound lifetime parameter 'a, found concrete lifetime
   |
   = note: required because of the requirements on the impl of `for<'a> FnLt<'a>` for `[closure@src/main.rs:31:16: 31:37]`
note: required by `take_fn_lt`
  --> src/main.rs:23:1
   |
23 | fn take_fn_lt(_: impl for<'a> FnLt<'a>) {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Edit: the closure can be made to work through coercion to a fn pointer (which is unsurprising, since it's then the same as foo). take_fn_lt({ |x: &u8| -> &u8 { x } } as fn(&u8) -> &u8); compiles.

Mark-Simulacrum commented 6 years ago

I believe this is the same issue as https://github.com/rust-lang/rust/issues/41078.

cramertj commented 6 years ago

@Mark-Simulacrum I'm not sure this is a proper duplicate-- when I've hit that other issue, I've always been able to get it to go away by explicitly writing out the signature of the closure types. I can't do that here-- you'll notice that the type of the closure is specified manually, yet it still fails.

cc @nikomatsakis

cramertj commented 6 years ago

I'm going to reopen this, as I think it at least needs further investigation in order to determine if it is the same as #41076 (which I doubt).

aturon commented 6 years ago

cc @eddyb @nikomatsakis, this is a pretty persistent and important issue for async/await work. It might be most expedient to address it by adding AsyncFn/AsyncFnMut/AsyncFnOnce unstable traits -- does that seem plausible?

eddyb commented 6 years ago

@nikomatsakis Looks like this works fine if the bound in take_fn_lt is replaced with FnOnce(&u8) -> &u8, which means that either the "closure signature deduction" hack is kicking in there and making it work (unlike FnLt, which doesn't get the deduction) or going through trait selection imposes additional constraints.

@aturon Not entirely opposed to AsyncFn* - if unstable, they're easy to add, but I worry that blanket impls won't cut it (as they don't in this issue). And trait aliases aren't implemented yet AFAIK.

Enselic commented 11 months ago

Triage: Rust 1.74 gives more details on why it rejects this code. Playground link. It assigns different lifetimes to the closure input and output, contrary to usual elision rules:

error: lifetime may not live long enough
  --> src/main.rs:31:34
   |
31 |     take_fn_lt(|x: &u8| -> &u8 { x }); // Doesn't work
   |                    -       -     ^ returning this value requires that `'1` must outlive `'2`
   |                    |       |
   |                    |       let's call the lifetime of this reference `'2`
   |                    let's call the lifetime of this reference `'1`