rust-lang / rust

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

Confusing diagnostic - error[E0308]: mismatched types #84400

Open elichai opened 3 years ago

elichai commented 3 years ago

https://play.rust-lang.org/?gist=d912ef0e107be493dd10b72f973897ba

use serde::*;

#[derive(Serialize)]
struct Test;

fn main() {
    serialize_wrap(serialize, &Test);
}

fn serialize_wrap<F: FnOnce(&Test)>(f: F, ser: &Test) {
    f(ser)
}
fn serialize<T: Serialize>(x: T) {
    todo!()
}

The current output is:

error[E0308]: mismatched types
  --> src/main.rs:7:5
   |
7  |     serialize_wrap(serialize, &Test);
   |     ^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected type `FnOnce<(&Test,)>`
              found type `FnOnce<(&Test,)>`
note: the lifetime requirement is introduced here
  --> src/main.rs:10:22
   |
10 | fn serialize_wrap<F: FnOnce(&Test)>(f: F, ser: &Test) {
   |                      ^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

I'm not quite sure what's the right diagnostic here, maybe that it should be by value?

estebank commented 3 years ago

The output in beta is

error: implementation of `FnOnce` is not general enough
 --> src/main.rs:7:5
  |
7 |     serialize_wrap(serialize, &Test);
  |     ^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
  |
  = note: `fn(&'2 Test) {serialize::<&'2 Test>}` must implement `FnOnce<(&'1 Test,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 Test,)>`, for some specific lifetime `'2`

You can make this compile by adding a named lifetime tying f and ser, but that might not be what you want to do, depending on the specifics.

elichai commented 3 years ago

@estebank, Thanks for responding. your fix allows me to understand the problem, if I got it correctly it means that: The compiler assumes that the &Test input to serialize_wrap and the &Test input to FnOnce have different lifetimes. by introducing a named lifetime we tell it that they need to have the same lifetime.

The new diagnostic is obviously better but I also don't think I fully understand it to be honest.

estebank commented 3 years ago

The diagnostic absolutely needs to be improved. The reason this doesn't compile is because the signature desugars to fn serialize_wrap<'b, F: for<'a> FnOnce(&'a Test)>(f: F, ser: &'b Test).

And that is a problem because the compiler can't ensure that 'b will live when F gets called.

jonhoo commented 3 years ago

Here's an even simpler one:

fn check<S: AsRef<str>>(_: S) -> bool {
    false
}

fn main() {
    let options: Vec<String> = Vec::new();
    let _ = options.into_iter().filter(check::<&String>);
}

On stable this produces the unhelpful

error[E0308]: mismatched types
 --> src/main.rs:7:33
  |
7 |     let _ = options.into_iter().filter(check::<&String>);
  |                                 ^^^^^^ one type is more general than the other
  |
  = note: expected type `FnOnce<(&String,)>`
             found type `FnOnce<(&String,)>`

On beta/nightly it's a little better, but still pretty inscrutable:

error: implementation of `FnOnce` is not general enough
 --> src/main.rs:7:33
  |
7 |     let _ = options.into_iter().filter(check::<&String>);
  |                                 ^^^^^^ implementation of `FnOnce` is not general enough
  |
  = note: `fn(&'2 String) -> bool {check::<&'2 String>}` must implement `FnOnce<(&'1 String,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 String,)>`, for some specific lifetime `'2`

In this particular case it can be fixed with:

fn check<S: AsRef<str>>(_: &S) -> bool {
    false
}

fn main() {
    let options: Vec<String> = Vec::new();
    let _ = options.into_iter().filter(check::<String>);
}

But that's not really a general solution. Just sharing in case others run into something similar and ends up here.

estebank commented 1 year ago

Current output:

error[[E0308]](https://doc.rust-lang.org/nightly/error_codes/E0308.html): mismatched types
  --> src/main.rs:8:5
   |
8  |     serialize_wrap(serialize, &Test);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected trait `for<'a> FnOnce<(&'a Test,)>`
              found trait `FnOnce<(&Test,)>`
note: the lifetime requirement is introduced here
  --> src/main.rs:11:22
   |
11 | fn serialize_wrap<F: FnOnce(&Test)>(f: F, ser: &Test) {
   |                      ^^^^^^^^^^^^^

error: implementation of `FnOnce` is not general enough
 --> src/main.rs:8:5
  |
8 |     serialize_wrap(serialize, &Test);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
  |
  = note: `fn(&'2 Test) {serialize::<&'2 Test>}` must implement `FnOnce<(&'1 Test,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 Test,)>`, for some specific lifetime `'2`
error[[E0308]](https://doc.rust-lang.org/nightly/error_codes/E0308.html): mismatched types
 --> src/main.rs:7:13
  |
7 |     let _ = options.into_iter().filter(check::<&String>);
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected trait `for<'a> FnMut<(&'a String,)>`
             found trait `FnMut<(&String,)>`
note: the lifetime requirement is introduced here
 --> /rustc/065a1f5df9c2f1d93269e4d25a2acabbddb0db8d/library/core/src/iter/traits/iterator.rs:925:12

error: implementation of `FnOnce` is not general enough
 --> src/main.rs:7:13
  |
7 |     let _ = options.into_iter().filter(check::<&String>);
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
  |
  = note: `fn(&'2 String) -> bool {check::<&'2 String>}` must implement `FnOnce<(&'1 String,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 String,)>`, for some specific lifetime `'2`