[Solved] "one type is more general than the other", but they are exactly the same


#1

I have a CoverageExperiment struct:

pub struct CoverageExperiment<C, D> {
    agent_factory: Box<dyn Fn(&D) -> C>,
    domain_factory: Box<dyn Fn() -> D>,
    step_limit: usize,
}

impl<S: Space, A: Space, C, D> CoverageExperiment<C, D>
where C: Controller<S::Value, A::Value>,
      D: Domain<StateSpace = S, ActionSpace = A>
{
    pub fn new(agent_factory: Box<dyn Fn(&D) -> C>,
               domain_factory: Box<dyn Fn() -> D>,
               step_limit: usize) -> Self {
        CoverageExperiment {
            agent_factory,
            domain_factory,
            step_limit,
        }
    }
}

I use it here:

let agent_factory = Box::new(|domain| ModelRndAgent::new(domain, k, learning_rate));
let exp = CoverageExperiment::new(agent_factory, domain_factory, episode_length);

I get this error:

error[E0308]: mismatched types
  --> src/bin/coverage.rs:31:39
   |
31 |     let exp = CoverageExperiment::new(agent_factory, domain_factory, episode_length);
   |                                       ^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected type `std::ops::FnOnce<(&code::domain::acrobot_forever::AcrobotForever,)>`
              found type `std::ops::FnOnce<(&code::domain::acrobot_forever::AcrobotForever,)>`

(code is the local crate, as referenced by files in src/bin (the second code sample is from src/bin))


#2

Not really same, the closure is an unnamed struct that impls Fn(&D) -> C, not the trait object itself. You should explicitly cast it to boxed dyn trait type like agent_factory as Box<dyn Fn(&D)->C>. But note that, type inference is your friend, so you can write it as agent_factory as _


#3

Writing agent_factory as _, I get the same error as before.
What I did was: let exp = CoverageExperiment::new(agent_factory as _, domain_factory, episode_length);

One thing I find strange is that I have had no such problem with domain_factory, which is Box<dyn Fn() -> D>. (created like this: let domain_factory = Box::new(Env::default);)
I will write a minimal test case when I have time.


#4

Here is a minimal test case:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f9b25791e2f90fd234fc1fcb69ffb524

It doesn’t run on the website because of missing dependencies, but I added the dependencies in comments at the start of the code.


#5

This is a quirk of how type inference works with higher order regions in Rust. The current error message is pretty bad, but there is an open issue about this.

The thing here is, the type Box<dyn Fn(&i32)> is actually shorthand for Box<dyn for<'a> Fn(&'a i32)>. However, if you don’t annotate the type as soon as possible, rust will instead infer it to be Box<dyn Fn(&'b i32)> (where 'b is some as-yet unknown, but specific, lifetime).

The fix is to annotate your type fully enough for the compiler to see the borrow:

fn main() {
    // let f: _ = Box::new(|_| {}); // not specific enough
    // let f: Box<_> = Box::new(|_| {}); // not specific enough
    let f: Box<dyn Fn(&_)> = Box::new(|_| {}); // okay

    // type error if the type of f wasn't specific enough to
    // infer the for<'a> in the type
    let g: Box<dyn Fn(&i32)> = f;
}

#6

Thanks, that fixed my issue!