So I was playing with the variance rule, checking if I'm understanding anything, which turns out to be a... big NO. Consider this snippet (if you prefer playground)
#[derive(Debug)]
struct Foo<'f>(#[allow(unused)] &'f str);
impl Foo<'static> {
fn is_static_shared(&self) {}
fn is_static_exclusive(&mut self) {}
}
fn foo(_f: &mut Foo<'static>) {
/*
* This line fails to compile, as expected:
* the fact `Foo<'static>` and `Foo<'_>` has subtype relation
* does not imply relation between `&mut Foo<'static>` and `&mut Foo<'_>`:
* `&mut T` is invariant over `T`
*/
// std::mem::swap(&mut Foo(&String::from("not static")), _f);
}
fn bar<'f>(#[allow(unused)] f: &mut Foo<'f>) {
// group A and group B cannot coexist!
let mut g = Foo("static");
{
// group A
// g.is_static_shared();
// (&mut g).is_static_shared();
// (&mut g).is_static_exclusive();
}
{
// group B
std::mem::swap(f, &mut g);
std::mem::swap::<Foo<'f>>(f, &mut g);
}
}
fn main() {
let main = String::from("main");
let mut main = Foo(&main);
bar(&mut main);
foo(&mut Foo("static"));
dbg!(main);
}
My question is around bar
:
- If we enable only group B, it actually compiles just fine. From a generic "prevent dangling pointers" PoV, this is completely fine: we're replacing a
Foo
that may borrow from some other variables, to one that borrow nothing from the context of program execution. But from a variance PoV things get tricky: we have&'_ mut Foo<'f>
and&'_ mut Foo<'static>
, and they are not subtype of one another by the fact&'a mut T
being invariant overT
, but somehowstd::mem::swap
thinks there's someT
upon which both of the operand exclusive references agree via subtype coersion. Again, suchT
should not exist, no? - So I thought maybe I've made some wrong assumptions. Maybe
g
is notFoo<'static>
in the first place. So I added group A. Group A itself alone, without group B, compiles just fine. It's when enabling both group A and group B the compiler becomes confused. - Last but not least, when both group A and group B are enabled, the compiler actually complains about code in A enabling some variable to escape the function body.
- Which is kinda weird since judging by signature, the only possible place it might escape is actually group B...?
- And then again there's the fact group B alone, without group A, compiles just fine.
error[E0521]: borrowed data escapes outside of function
--> src/main.rs:29:9
|
20 | fn bar<'f>(#[allow(unused)] f: &mut Foo<'f>) {
| -- - `f` is a reference that is only valid in the function body
| |
| lifetime `'f` defined here
...
29 | (&mut g).is_static_exclusive();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `f` escapes the function body here
| argument requires that `'f` must outlive `'static`
|
= note: requirement occurs because of a mutable reference to `Foo<'static>`
= note: mutable references are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
For more information about this error, try `rustc --explain E0521`.
error: could not compile `playground` (bin "playground") due to 1 previous error
I'm assuming the reason here is Rust doing the variance type checking and subtype coersion not just at function call site, but also at local variable definition site, specifically the definition for g
, i.e. the let mut g = Foo("static");
line before both group A and B.
- If only group B were present, Rust treats the
g
asFoo<'f>
, i.e. a subtype coersion happens here; it should be okay sinceFoo<'f>
being covariant over'f
, and sunshine and rainbows. - If only group A were present, Rust treats the
g
asFoo<'static>
, again all sunshine and rainbows. - If both group A and group B are present, the fact
&'a mut T
being invariant overT
rejects the code, just like the line infoo
should be commented out.- But then the compiler message seems a bit misleading here...?
Does such statement, i.e. Rust variance type coersion happens also at local variable definition site besides function call site, make any sense? If so, are there some materials around this aspect of Rust? Or maybe I missed something s.t. the statement is completely off and things just don't work like this?