I am reminded of a conversation that @scalexm and I were having. We were discussing how to represent types and things, and the differences between rustc and chalk. It occurs to me that we might avoid the question if we adopted the āunivariantā scheme I describe below, in which case everything is just a āvariableā ā but having written that out, I think the downsides are large enough itās probably best to be avoided for now.
In Rustc, right now we have three distinct region variants, all of which might conceptually be considered variables:
-
ReLateBound ā bound within some enclosing type or term
- e.g., the
'x in for<'x> T: Trait<'x> or for<'x> fn(&'x u32)
- of course if you are iterating over terms you might encounter it, particularly when you āskipā through a
Binder<T> type to the T within
-
ReSkolemized ā a universally quantified thing bound in the āenvironmentā, essentially (inference context, more specifically)
- this is used when doing the āleak checkā, but is also the subject of discussion
-
ReVar ā an existentially quantified thing bound in the environment (again, inference context, more specifically)
In Chalk, we also have some distinctions, but fewer. We represent the equivalent of ReVar and ReLateBound using the same variant ā āvariableā, but we use debruijn indices to track the difference. That is, if you have an inference variable with index 0, then when you enter a for<'x> binder, it would be referenced with index 1:
T: Foo<'?0, for<'x> fn(&'?0 u32, &'?1 u32)>
^^^ ^^^
these are both references to inference variable zero
In Chalk, we still distinguish the āuniversalā case, which we represent as a kind of !1 where 1 is a universe index, but one could imagine not doing that. Instead, we could represent everything with just a debruijn index. When that debruijn index exceeds the binders in the term, it refers into the surrounding inference context, and that context would track whether it was universally bound (āskolemizedā) or existentially bound (āinference variableā).
I find this appealingly simple. It has some efficiency gains (perhaps minor):
Notably, if you have a fully canonicalized term, it is essentially a āmini inference contextā. We can create an inference context around it without doing any substitution of any kind (chalk is also able to do this).
But I also see some concrete downsides:
First off, you wouldnāt be able to use match and a variant to determine which of the three cases you are dealing with; youād just get an index and youād have to go look into the inference context to figure it out.
Second, right now we keep āthings that refer into the inference contextā interned and allocated in a separate memory arena, and we use the variants to figure out where things belong. The scheme breaks down if ?0 is āsometimesā bound within a global term and sometimes the surrounding infcx.