Garbage collected handles are lifetime-contravariant

18 points by aapoalas


rpjohnst

Contravariance has nothing to do with GC or self-reference. The mirror images of covariance and contravariance come from the mirror between producers and consumers - producing a value of type &'a T simultaneously produces a value with all shorter lifetimes, and vice versa a consumer of a value of type &'a T automatically accepts a value of any longer lifetime. But both forms of variance are really sort of a distraction here.

The problem with the initial example here is not that you should be allowed to assign a Handle<'local, T> into a place of type Handle<'external, T>. If 'local is, as you describe, a "lifetime during which it is guaranteed that garbage collection does not happen," then contravariance would allow you to store it in e.g. a static UNRELATED: Mutex<Handle<'static, T>>, essentially claiming that GC will never happen again.

The actual problem is on the other end: 'external is too long. The entire point of the GC heap is that references written into it will survive GCs, so it doesn't make any sense to use the type Handle<'external, T> for those references! The most natural type for an in-heap reference like this wouldn't have a lifetime in the first place, because its target is owned by the heap- just like Rc<T>.

The problem with just using owning references is that the GC needs to invalidate borrows derived from any of them, all at once, and then restore access to them again afterwards. From this perspective, it should be clear why neither 'local nor 'external makes any sense for in-heap references- we're trying to eat our cake (invalidate the borrows) and have it too (give back access to the pointers).

Fundamentally what we need is some way to connect all the borrows formed before a GC, so they can all be invalidated, but also keep them distinct from the in-heap references, so those can survive. The only reason in-heap lifetimes get involved at this point (as in the generative/branded lifetimes approach) is really more of a clever hack than anything to do with what lifetimes were designed to model- those lifetimes do not signify how long the in-heap references are valid, but they do control the lifetimes of out-of-heap borrows, and are effectively reset after each GC.