In practice, people use a zero value to indicate that a value is missing, but this only works if the zero value isn't also a valid input.
Hopefully the coming generics will resolve this pain point.
In practice, people use a zero value to indicate that a value is missing, but this only works if the zero value isn't also a valid input.
Hopefully the coming generics will resolve this pain point.
There won't be overloading * for getting the value inside and overloading bool conversion to check if the value is there. Still, it's an improvement.
``` var map1 map[int]int = nil var map2 map[int]int = &map1 var map3 map[int]int = nil ```
Interface types also can be nil even if they're not explicitly pointers because heap pointers are needed under the hood due to the size of the value not being knowable at compile time. You can pretty quickly get into some hairy situations trying to ensure that a value isn't nil when combining pointers or maps/slices with interfaces.
I really wish that separating the concerns of present versus not present from value versus reference was more mainstream. I understand why this was traditionally the case for lower-level languages, but even higher-level ones like Java are mostly based on the paradigm that only references can be "not present". This irks me in a similar way to how earlier versions of Java wouldn't allow default implementations of interface methods and instead requiring abstract classes, which classes could not extend from more than one of.
This is one of those things which sounds great in practice, and survives approximately 17 seconds of working on a real codebase.
This is obviously not possible, since you can imagine lots of complex objects are all integers or themselves composed of integers, and for this entire class of objects, the zero value usually makes sense.
For example, a 3-vector (in the physics sense) is a 3-tuple of integers `type Vec3 struct {x, y, z int}` , `Vec3 {0, 0, 0}` being the origin. How does a function specify that it can take an optional 3-vector? Even worse, you can imagine a struct for a potenitally 0-volume cube represented as 8 Vec3s, one for each vertex, whose 0 value is again a valid object.
Of course, you could gratuitously add an `IsValid bool` flag which must be true to the class, but the cube example shows how this quickly becomes annoying and bloated.
1. Take a *Vec3, using nil as the "optional is missing" value.
2. Take a Vec3, but consider some value as "invalid", often the 0-value as possible.
1 invokes exactly the issues in Hoare's essay. 2 doesn't work for all types, as some types don't have any bit pattern that are not useful, this is what I was trying to point out.
Additionally, a *Vec3 is more than `Vec3` or `null`. For example, the following code will behave very differently based on whether optionalVec is Vec3 or *Vec3.
optionalVec := Vec3{0, 0, 0}
foo(optionalVec)
fmt.Printf("theVec: %+v", optionalVec)
// would print {0, 0, 0} with an Optional[Vec3] type
// will actually print {2, 0, 0} with *Vec3
...
func foo(optionalVec *Vec3) {
if optionalVec != nil {
optionalVec.x += 2 //we use a different origin
}
[...]
}For my team, we would have loved to have something like C# (or even Java), with generics, exceptions, lambdas, LINQ/Streams etc., but with Go's low overhead and large ecosystem. Go the language was a major negative point, just not bad enough to outweigh the advantages of the runtime.
Go supports returning multiple results. Maybe I don't read enough Go code to understand what's actually popular, but AFAIU the idiomatic way to accomplish this in Go is something like:
if v, exists := lookup(key); exists {
do(v)
}
or if r, err != foo(); err != nil {
do(r)
}
The compiler, unfortunately, can't enforce proper conditionals. OTOH, unlike C compilers Go won't make any dangerous assumptions about dereferencing nil pointers, which is something.I'm not sure Generics can help here. What you really want is compiler enforcement of comprehensive condition checks. For example, via pattern matching switch statements. That's easiest to do with optional types as the unwrapping operation creates a simple point for static constraint checking, but in principle the same thing could be accomplished with annotations on multi-value return types that describe the association.
How would you write a function that takes a string, but can use some default if none is provided?
Common way:
func f(s string) {
if s == "" {
s = "default"
}
...
} func f(s *string) string {
ret := ""
if s == nil {
ret = "default"
} else {
ret = *s
}
return ret
}
func main() {
s := "foo"
fmt.Println("Hello, " + f(&s))
fmt.Println("Hello, " + f(nil))
}