Don't know how to describe, but here is the minimal reproducing snippet (also on playground):
struct Ctx(Vec<Box<dyn Fn(&mut MockCtx)>>);
struct MockCtx<'a>(&'a mut Ctx);
impl MockCtx<'_> {
// this works
fn push<F: 'static + Fn(&mut MockCtx)>(&mut self, f: F) {
self.0.0.push(Box::new(f));
}
}
trait Push {
fn push<F: 'static + Fn(&mut Self)>(&mut self, f: F);
}
impl Push for MockCtx<'_> {
// fn push<F: 'static + Fn(&mut Self)>(&mut self, f: F) { (1)
fn push<F: 'static + Fn(&mut MockCtx)>(&mut self, f: F) { // (2)
MockCtx::push(self, f)
}
}
If I take (1), compiler reports
expected a `Fn<(&mut MockCtx<'_>,)>` closure, found `F`
and recommand to add explicit lifetime to restrict. If I do so or simply take (2), compiler reports
impl has stricter requirements than trait
The truth is I do not understand the problem from starting point... I understand what is lifetime and simple "outlive" rules, and to me compiler should have no worry about 'a
for these code so far, because that can only be a thing when these Box<dyn Fn>
actually get called.
Thanks for any replying and explaining!
This is understandably a little opaque, because several lifetimes are elided. It should become a little more clear if we write those out.
Let's first look at the container:
struct Ctx(Vec<Box<dyn Fn(&mut MockCtx)>>);
This is short for:
struct Ctx(Vec<Box<dyn for<'a, 'b> Fn(&'a mut MockCtx<'b>) + 'static>>);
The 'static
requires that any closure you put in the container must own its data. It cannot refer to other data that depends on a lifetime. The elided lifetime is 'static
because Box
has no lifetime parameters.
The construct for<'a, 'b> Fn(&'a mut MockCtx<'b>)
is a higher rank trait bound, or HRTB for short. This means that any closure you put in the container must be able to accept a mutable reference with any lifetime to a MockCtx<'_>
with any lifetime parameter. Note for later that there are two lifetime parameters here.
Now, let's go to the impl:
impl MockCtx<'_> {
fn push<F: 'static + Fn(&mut MockCtx)>(&mut self, f: F);
}
This is short for:
impl MockCtx<'_> {
fn push<F: 'static + for<'a, 'b> Fn(&'a mut MockCtx<'b>)>(&mut self, f: F);
}
The requirements on the closure are the same as for the container. No conflicts. But note that the closure must accept types other than Self
. It must accept a mutable reference to a MockCtx<'_>
with a different lifetime.
Now let's go to the trait:
trait Push {
fn push<F: 'static + Fn(&mut Self)>(&mut self, f: F);
}
This is short for:
trait Push {
fn push<F: 'static + for<'a> Fn(&'a mut Self)>(&mut self, f: F);
}
This is going to conflict. A closure given to Push
can only be called with a reference to Self
, of any lifetime. But a closure that we store in our container must accept some types other than Self
. There is a whole range of Self
-like types that have different lifetime parameters that the closure needs to accept for our container.
Now, we can explain why the two attempts failed. #1 fails because the closure in the trait and the trait impl can only accept Self
, but we're attempting to store it in a container that holds closures that can accept MockCtx<'_>
of any lifetime. #2 fails because the trait only requires the closure accept Self
, but our trait impl tries to accept a more restrictive set of closures that can accept MockCtx<'_>
for any lifetime.
There are several ways to fix this, but they all amount to fixing this disagreement on the bounds for the argument of the closure.
One way is to explicitly accept the full range of types in the closure bound. You have to mention types other than Self
. So, for example, you can mention the concrete types:
trait Push {
fn push<F: 'static + Fn(&mut MockCtx<'_>)>(&mut self, f: F);
}
Using a nightly feature of trait aliases, you could use a common name for all the requirements:
trait MockFn = 'static + Fn(&mut MockCtx<'_>);
trait Push {
fn push<F: MockFn>(&mut self, f: F);
}
Using a nightly feature of generic associated types, you could also make the trait slightly more general:
trait Push {
type SelfLike<'a>;
fn push<F: 'static + Fn(&mut Self::SelfLike<'_>)>(&mut self, f: F);
}
Alternatively, as @Jmb says in the comments, you can make the closure a generic parameter on the trait:
trait Push<F> {
fn push(&mut self, f: F);
}
This is a relatively clean approach. It is asserted in the comments that this is useless, but I don't think it is.