In the absence of test2
or wrap
or another immediate context with a Fn
-trait bound, the compiler will just use it's default inference -- the one that is notoriously bad with regards to higher-rankedness. test
isn't making things worse, it's just failing to make things better.
Your terminology is confusing. Or confused; non-compiling closures aren't unsound, and the compiler doesn't somehow partially compile closures by only picking the sound parts, whatever that means.
The compiler sees:
// wildcard lifetimes --vv------vv capture ------vv
let lambda = |this: & mut P<'_>| { this.v.push(&s) };
And tries to do something like this:
struct Closure<'a>(&'a String);
impl<'a> Closure<'a> {
// vv------vv
fn call(&self, this: & mut P<'_>) {
this.v.push(self.0);
}
}
And then errors when that fails borrow chek.
But with test2
or wrap
, it bases its implementation on the bound and instead does something like
// vv-------------+
struct Closure<'a>(&'a String); // |
impl<'a> Closure<'a> {// |
// vv
fn call(&self, this: & mut P<'a>) {
this.v.push(self.0);
}
}
which works.
You're talking about the signature of the Fn*
implementation as if it was the variance of the closure or such, I think, which is a bit off. And variance isn't the problem.
// 'r and 'a represent single, concrete lifetimes
fn(&'r mut P<'a>) // 'r position: contravariant, 'a pos.: invariant
fn(& mut P<'a>) // outer lifetime: higher ranked, 'a pos.: invar.
fn(& mut P<'_>) // both lifetimes: higher-ranked
// In all cases, inner lifetime: outer lifetime is implied
The problem isn't contravariance -- and the inner lifetime is always invariant in the signature. The problem is higher-rankedness of the inner lifetime. The last signature can be invoked as a fn(&'static mut P<'static>)
.
Covariance of the lifetime of &String
means a &'static String
capture would work with any of those signatures.
Even if the closures in question can be covariant over their capture, it wouldn't be useful in this case, where you need a specific lifetime in invariant position in the call signature.
However! What if we had bounds like this?
// 'a is the lifetime of the captured `&'a String`
for<'s where 'a: 's> fn(&mut P<'s>)
That would be compatible with the capture similar to how the fully higher-ranked signature is compatible with &'static String
. The lifetime of the capture is an upper bound on the lifetimes of P<'_>
which can be mutated.
We can emulate those conditional higher-ranked bounds with implied bounds.
fn wrap<'a, F>(f: F) -> F
where
F: for<'s> Fn(&mut P<'s>, [&'s &'a (); 0]),
// Implied under binder: ^^^^^^^ 'a: 's
No reason for it in the OP, and no advantage since you still need the wrap
and have to add dummy arguments everywhere, but it works.
It'd be nice if there was a way to get the compiler to emit the corresponding implementation without implied bounds, but we'd need at a minimum some syntax like for<'s where 'a: 's>
for the wrap
approach.