I would love sum types (which would allow for real enums), too. Russ Cox has commented on Reddit why the Go teams considers them incompatible with Go [1].
The first challenge is zero values: For a sum type like (int, float), there's no natural zero value. I think that sum types would have to follow the same design as Go's interfaces; a sum type value has to be nillable. This is unfortunate, but the Go team burned some bridges when it decided to support nils, and they now have to deal with that problem.
Secondly, there's the matter of what a sum type of interfaces means. I think this is solvable, too, and I don't agree that it presents a conflict. Sum types express the range of allowed values. What you do with a variable, once it holds a value, is no different than today:
type Source sum { io.Reader, io.ReaderAt }
var src Source
switch t := src.(type) {
case io.Reader:
case io.ReaderAt:
}
This is no different from:
var src interface{}
switch t := src.(type) {
case io.Reader:
case io.ReaderAt:
}
> Russ Cox has commented on Reddit why the Go teams considers them incompatible with Go [1].
I wish people would stop beating around the bush and own up to these being design mistakes of the language. If you can't have nice things because earlier poor decisions (e.g., supporting nils) has walled you into a corner, it's a problem. It's not an example of "refreshing simplicity", it's a shortcoming of the language. Period. Every language has some warts, just acknowledge them!
This is not a sum type, it's something similar but different, sometimes called a union type, because the point is to store a closed set of different types under a single umbrella.
A sum type has variants with associated data, but the type of that associated data is orthogonal to the variance, you can have multiple variants with no associated data, or the same associated data type (both are very common use cases), which would simply be impossible to express here.
With a proper sum type, this is not an issue because the Reader and ReaderAt associated data would be stored in explicitly and specifically different variants. In Rust parlance:
> The first challenge is zero values: For a sum type like (int, float), there's no natural zero value. I think that sum types would have to follow the same design as Go's interfaces; a sum type value has to be nillable. This is unfortunate, but the Go team burned some bridges when it decided to support nils, and they now have to deal with that problem.
The proper way to fix this would of course be the same one C# took: every C# type used to be default-able (although to their credit it was not generally implicit). With the introduction of non-nullable reference types, they had to change the language to handle cases where types would not be default-able.
Of course for Go that has wider-ranging implications e.g. currently I don't think there's any validity tracking because `var i <type>` tacks on an implicit zeroing of the value, it has no concept of "uninitialised values" whereas C# had that even before non-defaultable types. It also breaks their assumption and assertion that you should be able to add new fields to a structure and that'd get automatically filled with garbage without the caller being aware (also a terrible idea).
My understanding is that there's no real distinction between "sum type" and "union type" as such.
I may be wrong, but whether you have an indirection through a type constructor or not doesn't alter the meaning of the union. In the fictional Go syntax, you could also have the same indirection:
type Reader struct { R io.Reader }
type ReaderAt struct { R io.ReaderAt }
type Source sum { Reader, ReaderAt }
The difference is that Reader and ReaderAt are completely separate types, not type constructors, and that Rust has a shorthand for essentially valueless type constructors, allowing Rust's enums to act both like classic C enums and like sum types. But in Rust, as I understand it, enum variants aren't actually types. In your example, you can't have:
let r: source::Reader;
Of course, you can do the same thing in Rust, at the cost of readability:
> My understanding is that there's no real distinction between "sum type" and "union type" as such.
There’s a difference in the ambiguity — or lack thereof — which is the purported reason why go could not have sum types.
> But in Rust, as I understand it, enum variants aren't actually types.
That’s correct. They’re just constructors for values if the enum type.
> It's just too late for Go to do anything here, I think. Zero values permeate the language. For example, consider structs. It's normal to do
It’s not too late for anything. Existing types do not have to change and don’t prevent adding new non-default-able types (which would be transitive).
Those types, with those new guarantees, would not allow for the magical zeroing and zero-extending of existing types but they would not break anything.
The first challenge is zero values: For a sum type like (int, float), there's no natural zero value. I think that sum types would have to follow the same design as Go's interfaces; a sum type value has to be nillable. This is unfortunate, but the Go team burned some bridges when it decided to support nils, and they now have to deal with that problem.
Secondly, there's the matter of what a sum type of interfaces means. I think this is solvable, too, and I don't agree that it presents a conflict. Sum types express the range of allowed values. What you do with a variable, once it holds a value, is no different than today:
This is no different from: [1] https://www.reddit.com/r/golang/comments/46bd5h/ama_we_are_t...