fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T {
v
}
fn bar<T>() {
let fp1: for<'a, 'b> fn(&'a &'b (), &'b T) -> &'a T = foo;
let fp2: for<'a, 'b> fn(&'a &'static (), &'b T) -> &'a T = fp1;
}
but not this:
fn foo<'a, 'b, T>(_: &'a mut &'b mut (), v: &'b mut T) -> &'a mut T {
v
}
fn bar<T>() {
let fp1: for<'a, 'b> fn(&'a mut &'b mut (), &'b mut T) -> &'a mut T = foo;
let fp2: for<'a, 'b> fn(&'a mut &'static mut (), &'b mut T) -> &'a mut T = fp1;
}
?
Maybe this has something to do with the invariant behaviour of mutable borrows but my assumption was that due to the contravariance in the input of fn-pointers I should be able to make the coercion also with mutable borrows when it works with immutable borrows...
Hint: This code came up while I was playing around with #25860, so maybe there is some buggy behaviour involved here too?
fp2's signature fn(&'a mut &'static mut (), ... implies that
after the function is called, the pointed-to location will contain an &'static mut (), and
the function can swap it for any other &'static mut () if it likes
but fp1 can swap in an &'b mut () (perhaps reborrowed from the &'b mut T somehow) which, if done with fp2's signature, would leave the location with something that lives only for 'b, not 'static. It’s difficult to demonstrate this with () and T, but here is a version with concrete types, using transmute to substitute for the disallowed function pointer cast, that is flagged as UB by Miri:
#[derive(Debug)]
struct St(i32);
fn foo<'a, 'b>(p: &'a mut &'b mut St, mut v: &'b mut St) -> &'a mut St {
std::mem::swap(p, &mut v);
v
}
fn main() {
let fp1: for<'a, 'b> fn(&'a mut &'b mut St, &'b mut St) -> &'a mut St = foo;
let fp2: for<'a, 'b> fn(&'a mut &'static mut St, &'b mut St) -> &'a mut St =
// SAFETY: No. This demonstrates a cast that is prohibited.
unsafe { std::mem::transmute(fp1) };
let mut static1: &'static mut St = Box::leak(Box::new(St(1)));
{
let mut place2 = St(2);
fp2(&mut static1, &mut place2);
}
dbg!(static1); // UB: dereferences dangling pointer to place2
}
Of course, in your original code, () is zero bytes, so it would be impossible to cause this particular UB, but the subtyping/variance rules have no exceptions that would make them more permissive just because the referent is a zero-sized type.
@quinedot Yes, I've read the blog post from lcnr where he shows that you can construct the unsoundness also through covariance. The actual problem was my understanding of variance in this context. Due to contravariance in T in fn(T) -> U I naively assumed that you can just coerce T to something which is "more", but as you showed you also have to respect the inner variances of T.
Might this be the case because I reduced 'b here from a higher-ranked lifetime to a regular lifetime? In the initial example 'b still exists as higher-ranked lifetime for fp2. I'm confused.
You can see this as being connected: when you change a lifetime in an invariant position, the resulting type is “more” in one way and “less” in another way, so it ends up being neither a sub- nor supertype of the original type.
In this case you are substituting 'static for all occurrences of 'b, so this is just narrowing for<'b> to a single concrete case out of the infinite set of cases for<'b> refers to, which is valid. Similarly, we can substitute all occurrences of 'b in your original example and get one that compiles:
fn bar<T: 'static>() {
let fp1: for<'a, 'b> fn(&'a &'b (), &'b T) -> &'a T = foo;
let fp2: for<'a, 'b> fn(&'a &'static (), &'static T) -> &'a T = fp1;
}
Thanks! Ok just as I suspected. This works also with the version with mutable borrows but in the immutable version you mentioned you can just coerce one higher-ranked 'b to 'static
fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T {
v
}
fn bar<T>() {
let fp1: for<'a, 'b> fn(&'a &'b (), &'b T) -> &'a T = foo;
let fp2: for<'a, 'b> fn(&'a &'static (), &'b T) -> &'a T = fp1; // works!
}
... and it works.
That is/was one of the main reasons of my confusion but as far as I understand it now it should work because of the covariance in the shared references when the reason that this: