Where can I find the document specifying what the relationship is if the covariance nested in a invariant?

Subtyping and Variance - The Rust Reference says

Type Variance in 'a Variance in T
&'a T covariant covariant
&'a mut T covariant invariant

So, consider this type &'a mut T where T = &'b U; The second row says T is invariant in &'a mut T, that is, &'b U is invariant in &'a mut &'b U, however, the first row instead says 'b and U is covariant in &'b U, so, eventually, what the variance of 'b and U in &'a mut &'b U? Is it specified by first row or second row? How to determine the variance if we meet such a composite type with combined variance?

Here's a brief table of "variance algebra". I've seen a longer form post about it which I'll also link to if I can find it again... ah there it is, linked at the bottom of that article.

2 Likes

Sorry, that was abrupt. [1] As per the "transform" table, The U in &'a mut &'b U is

(invariant) × (covariant) = (invariant)

Since primitives don't compose, the only relevance of their bivariance is for the GLB, which basically says they're inert. I believe there's technically bivariance elsewhere in the language (generic otherwise-unconstrained associated type parameters), but I'm not sure it can be demonstrated.

So generally you can ignore it as a no-op,[2] and the remaining rules are

  • In composition
    • invariance persists
    • covariance is the identity
    • contravariance "flips" the other
  • In GLB
    • covariance + covariance = covariance
    • contravariance + contravariance = contravariance
    • anything else is invariant (including: invariance persists)

  1. I got interrupted ↩︎

  2. I've read articles that go so far as to say Rust doesn't have bivariance ↩︎

2 Likes

Oh, what does the variance on the left side of x and that on the right side of x correspond to in the table?
截屏2023-07-18 16.44.51

For example, V x W, Do we find V in the first element in each row? Or find V in the first element in each column?

Moreover, for a compound type, Is V the variance of the outermost type? For example:

 fn(&'_ i32);

The outermost type is fn(T), in order to determine the variance of i32 in the type, we say fn(T) is contravariant over T, so V designates the variance of T? Then &'_ i32 is covariant over i32, hence W means covariant, hence the variance of i32 in the complete type is calculated by contravariant x covariant, where contravariant corresponds to the symbol in the first element of the fourth row and covariant corresponds to the symbol in the first element of the third column, the result is -, so it is contravariant?

If we continue to wrap an fn in the outside of the above type, namely, fn(fn(&'_ i32)), now the variance of i32 is the result of contravariant * contravariant, which is covariant?

V on the left and W on the top. And if you ignore bivariance, it's symmetric.


fn(&'_ i32)
  • i32 is bivariant / no sub or super types so irrelevant
  • &'a U is covariant in 'a and U
  • fn(T) is contravariant in T
    • fn(&'a U) is contravariant in 'a and U (contra x co)
      • fn(&'a i32) is bivariant/irrelevant in i32 (contra x bi)

i32 is still bivariant/irrelevant, but the variance of the lifetime for the overall type flips depending on how many levels you have.

1 Like

Ah, I'm wrong on the above example, I should have wanted to discuss the variance of lifetime in these types.

I was under the impression that (other than when using interior mutability behind a shared borrow) this is generally not practically constructible?

If I am correct in that, is there any relevance beyond mental gymnastics?

If I'm incorrect, how could something like &mut & T be constructed?

As easily as let mut_ref_to_ref = &mut &value.

Why this might be practically useful is another question; in short, &mut &T can be used when we want not to change the value, but to swap it.

1 Like

Ooh I see it now, like void** in C.

Interesting, it's not a pattern I've come across often (or at all, really) in Rust.

Consider impl Read for &[u8].

6 Likes

That's a neat hack, it broke my brain just a tiny bit...

That code has a bug - it neglects the match case for (Variance::Bivariant, Variance::Invariant)

Wait, no - I have the ordering flipped. (Variance::Invariant, Variance::Bivariant) is the covered case. The chart prefers to keep the left side in both In- and Bi- variant cases.

The comments about columns corresponds to the rows in the diagram (and the order is different too).

If we must map & mut &T to a C/C++ syntax, I think it is more like void const **

In modern C/C++ that's probably correct. But by the time const ptrs were added to those 2 languages, I'd long since stopped programming in them due to the fractal footgun that is memory unsafety (even with things like shared_ptr etc).
Since it's after my time (so to speak) that could never have been a frame of reference for me :slight_smile:

To this date, C types syntax always confus… well, let’s look this up! => “pointer to pointer to const void”

I suppose that checks out of being - a least roughly - “&mut &T”

(ignoring nullability, and general memory safety, as well as the fact that there’s a generic T here, and last but not least the fact that const * in C/C++ does not mean immutable but just “you’re not supposed (allowed?) to mutate it but others could, at any time”, …)

const* in C++ means you can't modify without casting, but void requires casting anyway. It's incredibly bad form to modify const and in some cases is UB (e.g. if it happens to point to a static const).

It's kind of ironic that casting a *const to *mut is common for containers in rust to get covariance.