Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is "one type is more general than the other" in an Option containing a closure?

Tags:

rust

I wrote some code that compiled fine, then I turned T into Option<T> and now I'm getting this error:

error[E0308]: mismatched type...one type is more general than the other

When building a minimal case I noticed that if I change data: &str into data: String the code compiles fine again.

Playground link

pub trait Subscriber {
    fn recv(&mut self, data: &str);
}

pub struct Task<T>(pub Option<T>)
where
    T: Fn(&str);

impl<T> Subscriber for Task<T>
where
    T: Fn(&str),
{
    fn recv(&mut self, data: &str) {
        self.0.take().unwrap()(data)
    }
}

fn main() {
    Task(Some(|_| ()));
}

Can someone help me understand what's going on here?

like image 967
Pete Avatar asked Sep 16 '20 09:09

Pete


1 Answers

You seem to have hit a quirk in the compiler. The toilet closure (|_|()) generalizes into a function which is generic over its input type (<T> Fn(T)), but in such a way that does not fulfill the intended constraint over the lifetime of T. What we need here is a function that is generic over the lifetime of the input due to the bound for<'a> Fn(&'a str) in the implementation of Subscriber for Task<T>.

(Due to the current lifetime elision rules, the higher-ranked trait bound in Subscriber can be written without explicitly indicating for<'a>. This expansion was made in the previous paragraph for clarity.)

This is made a bit clearer by the compiler if you try to actually use the Task value as a Subscriber:

let mut task = Task(Some(|_| ()));
task.recv("Message");

The error message:

error[E0599]: no method named `recv` found for struct `Task<[closure@src/main.rs:19:30: 19:36]>` in the current scope
  --> src/main.rs:20:10
   |
5  | / pub struct Task<T>(pub Option<T>)
6  | | where
7  | |     T: Fn(&str);
   | |                -
   | |                |
   | |________________method `recv` not found for this
   |                  doesn't satisfy `_: Subscriber`
...
19 |       let mut task = Task(Some(|_| ()));
   |                                ------
   |                                |
   |                                doesn't satisfy `<_ as std::ops::FnOnce<(&str,)>>::Output = ()`
   |                                doesn't satisfy `_: std::ops::Fn<(&str,)>`
20 |       task.recv("Message");
   |            ^^^^ method not found in `Task<[closure@src/main.rs:19:30: 19:36]>`
   |
   = note: the method `recv` exists but the following trait bounds were not satisfied:
           `<[closure@src/main.rs:19:30: 19:36] as std::ops::FnOnce<(&str,)>>::Output = ()`
           which is required by `Task<[closure@src/main.rs:19:30: 19:36]>: Subscriber`
           `[closure@src/main.rs:19:30: 19:36]: std::ops::Fn<(&str,)>`
           which is required by `Task<[closure@src/main.rs:19:30: 19:36]>: Subscriber`

By explicitly marking the closure's single input parameter as a reference, the closure will then no longer generalize over the parameter type T, but over a higher-ranked lifetime of the reference input 'a in &'a _.

let mut task = Task(Some(|_: &_| ()));
task.recv("Message");

Playground.

While not impossible, adjusting the compiler to successfully infer the right type for the original closure would possibly require substantial changes to the current monomorphization rules.

See also:

  • Why isn't `std::mem::drop` exactly the same as the closure |_|() in higher-ranked trait bounds?
like image 108
E_net4 stands with Ukraine Avatar answered Nov 13 '22 23:11

E_net4 stands with Ukraine