Comment by Mawr
1 day ago
> I find myself wishing for Optional[T] quite often.
Well, so long as you don't care about compatibility with the broad ecosystem, you can write a perfectly fine Optional yourself:
type Optional[Value any] struct {
value Value
exists bool
}
// New empty.
func New[Value any]() Optional[Value] {}
// New of value.
func Of[Value any](value Value) Optional[Value] {}
// New of pointer.
func OfPointer[Value any](value *Value) Optional[Value] {}
// Only general way to get the value.
func (o Optional[Value]) Get() (Value, bool) {}
// Get value or panic.
func (o Optional[Value]) MustGet() Value {}
// Get value or default.
func (o Optional[Value]) GetOrElse(defaultValue Value) Value {}
// JSON support.
func (o Optional[Value]) MarshalJSON() ([]byte, error) {}
func (o *Optional[Value]) UnmarshalJSON(data []byte) error {}
// DB support.
func (o *Optional[Value]) Scan(value any) error {}
func (o Optional[Value]) Value() (driver.Value, error) {}
But you probably do care about compatibility with everyone else, so... yeah it really sucks that the Go way of dealing with optionality is slinging pointers around.
You can write `Optional`, sure, but you can't un-write `nil`, which is what I really want. I use `Optional<T>` in Java as much as I can, and it hasn't saved me from NullPointerException.
You're not being very precise about your exact issues. `nil` isn't anywhere as much of an issue in Go as it is in Java because not everything is a reference to an object. A struct cannot be nil, etc. In Java you can literally just `return null` instead of an `Optional<T>`, not so in Go.
There aren't many possibilities for nil errors in Go once you eliminate the self-harm of abusing pointers to represent optionality.
There's some other issues, too.
For JSON, you can't encode Optional[T] as nothing at all. It has to encode to something, which usually means null. But when you decode, the absence of the field means UnmarshalJSON doesn't get called at all. This typically results in the default value, which of course you would then re-encode as null. So if you round-trip your JSON, you get a materially different output than input (this matters for some other languages/libraries). Maybe the new encoding/json/v2 library fixes this, I haven't looked yet.
Also, I would usually want Optional[T]{value:nil,exists:true} to be impossible regardless of T. But Go's type system is too limited to express this restriction, or even to express a way for a function to enforce this restriction, without resorting to reflection, and reflection has a type erasure problem making it hard to get right even then! So you'd have to write a bunch of different constructors: one for all primitive types and strings; one each for pointers, maps, and slices; three for channels (chan T, <-chan T, chan<- T); and finally one for interfaces, which has to use reflection.
For JSON I just marshall to/from:
For nil, that's interesting. I've never ran into issues there, so I never considered it.
Ideally, I would want Optional[T] to encode the same as T when a value is present, and to encode in a configurable way when the value is absent. Admittedly, the nothing to null problem exists with *T too, and even with *T and `json:",omitempty"`, you get the opposite problem (null turns to nothing). I didn't think about that at the time, so it's really more of an issue with encoding/json rather than Optional[T] per se. However, you can't implement MarshalJSON and output nothing as far as I know.