Consider the code here where I define two functions which each take an argument a : Cow<'short, S<'long>> and an argument b : Cow<'short, S<'short>>, where 'long outlives 'short, and S is a type parametric in a lifetime. One of both functions assigns a = b, the other assigns b = a. Both functions are rejected by the borrow checker.
When I try to reason about the reason for the failed borrowcheck, I always end up feeling that one of them is indeed bad, but that the other one seems valid. When I start reasoning about the other, I suddenly feel like the first one can't actually go wrong.
Can any of you help me get a proper intuition about why both cases are potentially bad?
(As a bonus, the error messages are super-confusing, as they read "these two types are declared with different lifetimes", "...but data from a flows into a here" which sound like a perfectly fine assignment.)
This is ultimately a case where the compiler rejects valid code. To see this, consider the equivalent code:
fn f1<'long, 'short>(mut a: Cow<'short, S<'long>>, mut b: Cow<'short, S<'short>>)
where
'long: 'short,
{
match a {
Cow::Borrowed(a) => {
b = Cow::Borrowed(a);
},
Cow::Owned(a) => {
b = Cow::Owned(a);
},
}
}
which compiles. What happens here is that the owned variant is defined as
Owned(<B as ToOwned>::Owned)
and this makes the Cow type invariant in T because no special handling has been implemented to check that in this case, the associated type doesn't cause trouble when the lifetime is shortened.
Ah, I naively assumed covariance. That explanation makes a lot of sense, thanks.
I can't seem to turn your workaround into a workable solution for my problem, though. In retrospect, in an attempt to have a minimal representative example, I have oversimplified. A more representative example would have struct S looks like this:
Here, the proposed workaround does not really work: I shouldn't have to deconstruct arbitrarily many levels of HashMap<_, S<'long>> and move the data over into new HashMap<_, S<'short>>s, to end up with the exact same data structure. At first sight, the sledgehammer approach works:
let long: Cow<'short, S<'long>> = get_it_somewhere();
let short: Cow<'short, S<'short>> = core::mem::transmute(long);
but I'm not 100% sure the transmute is sound, and — related to that — I prefer to keep as many of Rust's checks in place since rustc is better at checking soundness than I am.
Do some of your unrelated fields cause S<'l> to be invariant in 'l? Because it seems like it is covariant. If it is, it seems you could make your own safe transmute:
Oh my... I swear it doesn't work in the full context of my codebase...
I'll dissect the issue some more on my own. You definitely helped me understand the kind of problem that the error message indicates, which is a big step in the right direction. Thanks for your persistence.