I find this a fascinating topic, so forgive me for this reply which has turned in to a mini blog post of sorts...
Go embraces the common insight that you can encode a sum as a dependent product. That is, here, you have a discriminated union of atoms and cons cells, but the discriminant is not a specific tag value, as it would be in Haskell or ML or similar, but rather the nil/non-nil state of these fields. Of course, there is no static enforcement of this property, as there would be in contemporary languages with sum types, including Rust's enums.
The quintessential example of this in Go is result/error multi-value returns. The result is valid if the error is nil. If your goal is to save bits (which is rarely the case for Go programs), as you note, avoiding an explicit discriminant (such as an interface pointer) means that you're encoding the discriminant's information in to less space, utilizing the specific domain knowledge of mutual exclusion.
Stepping further from Go, this same principle is at play in Clojure, which favors open maps for information. In Clojure, multimethods can dispatch on arbitrary functions of values. This means that any field can easily be used as a discriminant. Now, saving bytes is certainly not the purpose of this in Clojure, but it's interesting to think about. Forcing dispatch in to a privileged discriminant tag means that data needs to be transformed/parsed in order to re-arrange information in to the tag for dispatch.
As excellent as that article is, I think the truth lies in some balance. Both parsing and validation are useful techniques. Contemporary typed languages tend to push you down a path towards parsing instead of validation, which is probably the way the pendulum should swing for many use cases. However, there is one language that stands in stark contrast: TypeScript. Because it needs to support existing JavaScript idioms, it has grown a type system powerful enough to enable reasonable type safety with a validation based approach. Tools like control-flow sensitive type checking, literal types, and type-guards make that possible. You can use an arbitrary key in some JS object with a literal type value as a discriminant and the type system will do the right thing with union types.
There is one other area where Go is interesting here: zero values. Go zero-initializes all freshly allocated memory and encourages a style that embraces that. These freshly allocated objects are called "zero values" and often they are useful right out of the gate. You're encouraged to make code no-op safely with zero values, or apply some sane defaults. This makes an important distinction between `EnableFoo bool` and `DisableFoo bool`. Looping zero times is a perfectly valid thing to do. Silently skipping nil values is a perfectly valid thing to do in some cases. Etc. Clojure is similar with nil punning. While not without it's downsides, this is an interesting point in the design space that seems totally ignored by theoreticians and totally under-appreciated by working programmers, even those who work with Go and Clojure regularly. I'd really like to see that change, as I've found my programs have generally improved as I make judicious use of these techniques.
Objective-C zero-initializes ivars, and it's common to rely on this. The big problem is that you still need to test that code is doing the right thing when it encounters a nil state.
If you define away the zero states completely with sum types, code flow is completely accounted for at compile time, and entire categories of "oops, that shouldn't be nil right now" bugs simply don't exist. On Apple platforms, this is a major reason to use Swift instead of Objective-C.
I haven't used Go, only read some code occasionally; perhaps there's some other difference here that makes this a nonissue. But it sure looks like it has the same set of problems.
It depends on whether or not you view "oops, that shouldn't be nil right now" as a categorically different problem than "oops, this integer shouldn't be greater than 10 right now" problems. Or "oops, this array shouldn't have an odd number of elements right now" problems.
Yes, it's nice to have the type system catch problems. And yes, in the context of memory-unsafe languages null or wild pointers are a big problem. But I've found that you'd need a combinatoric explosion of data constructors and abstract interfaces to enforce the interesting invariants of my programs. A nil pointer (which panics at runtime with a good stack trace!) tends to be among the easiest problems I have to solve when my programs violate invariants.
I'm not arguing that sum-types are a bad idea. Only that dependent products (with or without enforcement, static or dynamic) are an under appreciated technique. Sum-types are really just one special case of dependent products.
I called out Clojure specifically because of the culture around this specific issue and because of the dispatch design of the multimethods that are present in the standard library and widely favored. Nothing stops you from making dispatch tables or simply switching on a key in JavaScript.
Go embraces the common insight that you can encode a sum as a dependent product. That is, here, you have a discriminated union of atoms and cons cells, but the discriminant is not a specific tag value, as it would be in Haskell or ML or similar, but rather the nil/non-nil state of these fields. Of course, there is no static enforcement of this property, as there would be in contemporary languages with sum types, including Rust's enums.
The quintessential example of this in Go is result/error multi-value returns. The result is valid if the error is nil. If your goal is to save bits (which is rarely the case for Go programs), as you note, avoiding an explicit discriminant (such as an interface pointer) means that you're encoding the discriminant's information in to less space, utilizing the specific domain knowledge of mutual exclusion.
Stepping further from Go, this same principle is at play in Clojure, which favors open maps for information. In Clojure, multimethods can dispatch on arbitrary functions of values. This means that any field can easily be used as a discriminant. Now, saving bytes is certainly not the purpose of this in Clojure, but it's interesting to think about. Forcing dispatch in to a privileged discriminant tag means that data needs to be transformed/parsed in order to re-arrange information in to the tag for dispatch.
Alexis King wrote an excellent article about this sort of transformation: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
As excellent as that article is, I think the truth lies in some balance. Both parsing and validation are useful techniques. Contemporary typed languages tend to push you down a path towards parsing instead of validation, which is probably the way the pendulum should swing for many use cases. However, there is one language that stands in stark contrast: TypeScript. Because it needs to support existing JavaScript idioms, it has grown a type system powerful enough to enable reasonable type safety with a validation based approach. Tools like control-flow sensitive type checking, literal types, and type-guards make that possible. You can use an arbitrary key in some JS object with a literal type value as a discriminant and the type system will do the right thing with union types.
There is one other area where Go is interesting here: zero values. Go zero-initializes all freshly allocated memory and encourages a style that embraces that. These freshly allocated objects are called "zero values" and often they are useful right out of the gate. You're encouraged to make code no-op safely with zero values, or apply some sane defaults. This makes an important distinction between `EnableFoo bool` and `DisableFoo bool`. Looping zero times is a perfectly valid thing to do. Silently skipping nil values is a perfectly valid thing to do in some cases. Etc. Clojure is similar with nil punning. While not without it's downsides, this is an interesting point in the design space that seems totally ignored by theoreticians and totally under-appreciated by working programmers, even those who work with Go and Clojure regularly. I'd really like to see that change, as I've found my programs have generally improved as I make judicious use of these techniques.