(Edit: any way to write literal asterisks in HN?)
The former is "read-only" (can read elements which are all guaranteed to be shapes). The latter is "write-only" (we can add circles to it, but not all elements in it are guaranteed to be circles).
I use quotes because it is not strictly true, but any writes or reads will be of the sort that they don't cause type problems. For instance you can call clear() on a ArrayList<? extends Shape>
In Rust it ends up actually being pretty clean because mutability is inherited. Generic values are covariant because (as you note), they're by definition unshared. &T is covariant in T because you can't mutate T through an &. &mut T is invariant in T because you can mutate through it.
This therefore enables one to pass &Collection<T> where &Collection<U> is expected if T is a subtype of U, because we know the collection only supports "read only" operations. It's been a while since I've used C++ and never really touched const stuff, but if it works like Rust, that means a `Vector<T> &const` could be soundly covariant. It wouldn't work if you could get `T*`'s out of a `Vector<T> &const`.
That said, we don't have OOP-style inheritance in Rust, so variance only applies to lifetimes. This allows you to pass an &LongLived where an &ShortLived is expected.
I give a basic rundown here: https://doc.rust-lang.org/nightly/nomicon/subtyping.html
Note that I deviate from the academic standard and simply use "variance" for covariance, because the academic terms are dumb and confusing (so many Rust devs, even with PL backgrounds, were constantly getting confused when we used that terminology). Also you don't really need to care about contravariance anyway because it's also dumb and confusing (as this post notes).