Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
spec: document cycle restrictions for alias type declarations #25187
Per the original type-alias design doc from @rsc, it must be possible to "expand out" type alias declarations; this wouldn't be possible for
The spec doesn't say anything in this regard; and is generally vague or silent on the subject of cycles.
Terminology: I use "type alias cycles" to refer to things like
I see three options here:
I think with the type alias proposal we had decided on option 1 (and subsequently mostly implemented it), but I'm thinking now it was done without considering the larger scope. As far as I can tell, the problem that type alias cycles introduce shouldn't be any fundamentally trickier than interface cycles. (E.g., we already have types that cannot be expanded out: https://play.golang.org/p/8KQfNRbGwRV)
For consistency, I'd be inclined to favor options 2 or 3 over option 1.
Option 2 is definitely the simplest (other than how to explain it in the spec), but may risk breaking backwards compatibility.
I expect option 3 is necessary if we're interested in accepting #8082.
I think we could perhaps pursue option 3 by defining a type serialization format that avoids needing to "expand out" the types. For example, if we use
We'd need to make sure there's a canonical mapping though. For example,
@mdempsky Since such cycles can only be created via names in the first place, why not keep the names? Specifically, that might mean that in the compiler we would have to keep an Alias type node around (and always go to the aliased type before we use it). But it would also allow better error messages (that may refer to the alias name used in the error scenario, if any).
@griesemer I think keeping extra type information around at compile-time is fine, but I'm concerned about what we do at link-time and run-time.
There are a lot of ways that we depend on identical types mapping to the same runtime type descriptor. For example, type assertions, interface comparisons, or comparing reflect.Type values with ==.
So if we have
We could arbitrarily pick one, but that doesn't seem any better to me than creating an artificial type descriptor using synthetic placeholders like
Alternatively, we could possibly modify the runtime code to not depend on a correspondence between type identity and type descriptor uniqueness, but that seems invasive and likely to (mildly, but negatively) impact even Go programs that don't use anonymous cycles. I'm also not even certain we could keep == working on reflect.Type if we get rid of type descriptor uniqueness.
@mdempsky Good point. A numbering scheme seems like a better choice. Instead of counting "backwards" (or outwards), one could also just number the types that are referenced from left to right (when written out); that might be a bit easier to understand. So
I see 4 different possible encoding schemes. For each one, I'll use
I think all of these should work, so it seems like a matter of preference on which to use.
I'd argue we shouldn't use 2, because it would be inconsistent with how we handle non-cyclic DAG types. For example, I don't imagine we'd start encoding
I'd also argue we shouldn't use 3, because it makes it too easy for people to accidentally implement 2 instead, with it working most of the time, but not always. (Again, trickier/subtler, but not impossible.)
And lastly, I'd argue that 1 is nicer than 4 because it has the property that each time