rust-lang / rust

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

Trait resolution fails with unclear error when using function types as input parameters #62385

Open gnzlbg opened 5 years ago

gnzlbg commented 5 years ago

This works (Playground):

trait Call<F> {
    type Ret;
    fn call(self, f: F) -> Self::Ret;
}

impl Call<f32> for f32 {
    type Ret = f32;
    fn call(self, f: f32) -> Self::Ret {
        f
    }
}
impl Call<i32> for f32 {
    type Ret = i32;
    fn call(self, f: i32) -> Self::Ret {
        f
    }
}

fn main() {
    let _ = (0_f32).call(0_i32);
}

but this fails (Playground):

trait Call<F> {
    type Ret;
    fn call(self, f: F) -> Self::Ret;
}

impl Call<fn(f32) -> f32> for f32 {
    type Ret = f32;
    fn call(self, f: fn(f32) -> f32) -> Self::Ret {
        f(self)
    }
}
impl Call<fn(f32) -> i32> for f32 {
    type Ret = i32;
    fn call(self, f: fn(f32) -> i32) -> Self::Ret {
        f(self)
    }
}

fn main() {
    fn u0(x: f32) -> f32 {
        x
    }
    let _ = (0_f32).call(u0);
}

with

error[E0277]: the trait bound `f32: Call<fn(f32) -> f32 {main::u0}>` is not satisfied
  --> src/main.rs:23:21
   |
23 |     let _ = (0_f32).call(u0);
   |                     ^^^^ the trait `Call<fn(f32) -> f32 {main::u0}>` is not implemented for `f32`
   |
   = help: the following implementations were found:
             <f32 as Call<fn(f32) -> f32>>
             <f32 as Call<fn(f32) -> i32>>

error: aborting due to previous error

I'm not sure if this is by design or not (AFAICT the trait bound is satisfied - EDIT: it is by design, see below), but the error message could be at least much clearer.

jonas-schievink commented 5 years ago

This is expected, since u0 doesn't have type fn(f32) -> f32, since every function item gets its own anonymous type (like closures). These types will coerce to the corresponding function pointer type (fn(f32) -> f32 here), but that coercion only kicks in when there's a known expected type to coerce to.

gnzlbg commented 5 years ago

Ah! So that's what's the {main::u0} in the fn(f32) -> f32 {main::u0} is!

I think this error could use a note, explaining that the fn(f32) -> f32 {main::u0} is an anonymous type, different from fn(f32) -> f32, but that it can be coerced into fn(f32) -> f32, although this coercions are not triggering here.

Maybe suggest using .. as fn(f32) -> f32 here to trigger the coercion.

cc @estebank

estebank commented 5 years ago

Another example from reddit:

struct Dummy;

impl From<fn(u32) -> String> for Dummy {
    fn from(_: fn(u32) -> String) -> Self { unimplemented!() }
}

fn foo(_: u32) -> String { unimplemented!() }

fn main() {
    Dummy::from(foo);
}

we currently emit

error[E0277]: the trait bound `Dummy: std::convert::From<fn(u32) -> std::string::String {foo}>` is not satisfied
  --> src/main.rs:10:5
   |
10 |     Dummy::from(foo);
   |     ^^^^^^^^^^^ the trait `std::convert::From<fn(u32) -> std::string::String {foo}>` is not implemented for `Dummy`
   |
   = help: the following implementations were found:
             <Dummy as std::convert::From<fn(u32) -> std::string::String>>
   = note: required by `std::convert::From::from`

The solution is changing the call to Dummy::from(foo as fn(u32) -> String);.

benesch commented 4 years ago

I know this issue is about improving the diagnostic, but it seemed as good a place as any to note that fixing this is desirable, but apparently there are a number of implementation challenges, according to @nikomatsakis (Zulip source).

https://github.com/rust-lang/rust/issues/62621#issuecomment-510867961 suggested that the next step might be an RFC, but it sounds like the more pressing issue is sorting through some of the implementation challenges.

estebank commented 5 months ago

Current output:

Only change to the path of the fn item

error[E0277]: the trait bound `f32: Call<fn(f32) -> f32 {u0}>` is not satisfied
  --> src/main.rs:23:26
   |
23 |     let _ = (0_f32).call(u0);
   |                     ---- ^^ the trait `Call<fn(f32) -> f32 {u0}>` is not implemented for `f32`
   |                     |
   |                     required by a bound introduced by this call
   |
   = help: the following other types implement trait `Call<F>`:
             <f32 as Call<fn(f32) -> i32>>
             <f32 as Call<fn(f32) -> f32>>

Much better output for this case

error[E0277]: the trait bound `Dummy: From<fn(u32) -> String {foo}>` is not satisfied
  --> src/main.rs:10:5
   |
10 |     Dummy::from(foo);
   |     ^^^^^ the trait `From<fn(u32) -> String {foo}>` is not implemented for `Dummy`
   |
   = help: the trait `From<fn(u32) -> String>` is implemented for `Dummy`
   = help: for that trait implementation, expected `fn(u32) -> String`, found `fn(u32) -> String {foo}`

We need to explicitly check for this case and detect fn(u32) -> String { foo } can be cast to fn(u32) -> String and provide a structured suggestion to do so for both cases presented.