2 ms·
One impediment is that Go cannot prevent the construction of invalid values, because every type has an implicit zero value: type NonZeroInt struct { v in
by atombender 5y ago
One impediment is that Go cannot prevent the construction of invalid values, because every type has an implicit zero value:
type NonZeroInt struct {
v int
}
func (n NonZeroInt) Get() int {
return n.v
}
var n NonZeroInt // invalid!
You could of course validate on read:
func (n NonZeroInt) Get() (int, bool) {
if n.v == 0 {
return 0, false
}
return n.v
}
…but that's hardly good design.
You can improve on this marginally with an interface, because an interface's zero value is at least nil, which is, at least, explicit about whether there is a value or not. But it's not very elegant:
type NonZeroInt interface {
Get() int
}
type nonZeroInt struct {
v int
}
func (n nonZeroInt) Get() { return n.v }
func ToNonZeroInt(v int) (NonZeroInt, bool) {
if v == 0 {
return nil, false
}
return nonZeroInt{v}, true
}
The biggest problem with this approach is that it adds overhead. It can also force values to be heap-allocated, though I believe Go will still optimize single-word interface values to avoid this.
Note that since Go interfaces use structural typing, naming the method Get() can cause issues:
type AnyInt struct {
v int
}
func (n AnyInt) Get() int {
return n.v
}
Now AnyInt fulfills the NonZeroInt interface, which is of course not something we want. So for types like these, it's a good idea to name the method explicitly:
type NonZeroInt interface {
GetNonZero() int
}
- foldr 5y agoUsing interfaces here seems unnecessarily complex. If the default zero value were an issue in practice, I would just define type NonZeroInt *int OR type NonZeroInt struct { v *int } // (if you want it to be fully opaque) so that the program would panic on an attempt to do arithmetic on the nil value. That achieves the same effect as your code, but without the unnecessary interface definition and the resulting naming issues. (Though note that Go does make it possible to export an interface from a package that no-one outside the package can implement. All you need to do is add a dummy private method to the interface.) It's true of course that the pointer adds runtime overhead, but two points: (1) Go is not really advertised as a zero cost abstraction language (in contrast to e.g. C++, Rust). So yes, building nice abstractions in Go will sometimes have a runtime cost. If that is unacceptable in a given application, then Go is not the right tool for the job. I'm not a Go zealot. If you need to do this kind of thing all over the place in your code base, then sure, don't use Go. (2) If you are dealing with large arrays of non-zero ints (such that the overhead of boxing would be significant) then you could always define NonZeroIntArray.