Gripes with Go

Up: Go

Just a few random thoughts.

No explicit interface

Can’t explicitly state that a struct needs to implement an interface. This leads to odd compiler errors when the struct doesn’t implement an interface by accident (e.g. due to a typo). For example, rather than an error “this struct needs to implement the Reader interface,” you get an error (where the struct is used) “you can only pass a Reader to thing thing.”

Zero-initialized values

Zero-initialized values means adding a new value in a struct does not require updating a constructor, and that can lead to wrong behavior. Probably why people write dedicated constructor functions and have structs with at least one private field.

See e.g. this issue in sentry-go’s client.go:

The default error event sample rate for all SDKs is 1.0 (send all).

In Go, the zero value (default) for float64 is 0.0, which means that constructing a client with NewClient(ClientOptions{}), or, equivalently, initializing the SDK with Init(ClientOptions{}) without an explicit SampleRate would drop all events.

To retain the desired default behavior, we exceptionally flip SampleRate from 0.0 to 1.0 here. Setting the sample rate to 0.0 is not very useful anyway, and the same end result can be achieved in many other ways like not initializing the SDK, setting the DSN to the empty string or using an event processor that always returns nil.

An alternative API could be such that default options don’t need to be the same as Go’s zero values, for example using the Functional Options pattern. That would either require a breaking change if we want to reuse the obvious NewClient name, or a new function as an alternative constructor.

if options.SampleRate == 0.0 {
	options.SampleRate = 1.0
}

Make vs. new

The difference is rather obscure.

Note last edited December 2024.
Incoming links: Go.