Things we hardly know but they made it into Go

Sam Myres
3 min readFeb 10, 2024

There are a whole bunch of things that on the face of it, Go as a programming language seems to be lacking

1. enums      -> iota
2. set -> map[type]struct{}{}
3. override -> ... type Option func (c *config)
4. implements -> var _ interface = &myType{}

Starting with enumGo lacks the type but gives you the ability to set constant types with simple addition or shifting with iota. This lacks a lot of the type safety, so there are packages like https://github.com/dmarkham/enumer which give you code-generated custom types with methods to enforce and decode types to strings, etc. Full disclosure, that is my CTO’s repository. He’s picked up maintenance from other contributors and we use it at work.

Next, Go doesn’t appear on the face of things to have a set data type for when you need unique lists. Instead, the designers answered this need by optimizing a form of map[key]struct{}{} where the value type is specified to be an empty struct, which ends up truncating the heap from the map, making it much faster as it’s just an index. And go gives a second optional value on map reads value, ok := map['index'] ok which specifies whether the value exists or not. For this reason I don’t think Go codebases should have seen := make(map[indexType]bool) though I see them often enough.

Third, Go doesn’t do overloading. I think this was a correct choice for a friendly language, as it can be very confusing when you find a method or function with the same name that can’t have run from your calling code because the parameters don’t match. Given, you can define your overloads immediately after the original version or whatever, but Go has an even more powerful construct in terms of simplicity in my opinion: functional options. Okay, maybe it’s not that simple. You have some configuration your functions declare and/or use and you want to optionally modify the behavior of a function, say func GetHTTPRequest(c config){... . Under the override model you might copy and paste this whole function, but in Go you can pass in a variadic func GetHTTPRequest(c config, opts ...option){ in this pattern an option is type option func(c config) . In GetHTTPRequest you loop over the _, o := range opts allowing the function passed in to modify c your config o(c) . Pulling it all together:

type config struct {
customUA
}

type option func(config)

func withCustomUA(cua string) option {
return func(c config){
c.customUA = cua
}
}

func GetHTTPRequest(c config, opts ...option) (io.Reader, err) {
for o := range opts {
o(c)
}
if c.customUA != "" {
// write a header line or something
}
// ... do the actual request logic
}

func main() {
c := config{}
GetHTTPRequest(c, withCustomUA("dumbot/3.0"))
}

Granted, it’s not the best but it has a certain elegance when you put a dutch tilt on it.

Finally, Go uses “duck typing” where if a type fulfills an interfaces’s method signatures you can use it as a value for parameters of that interface. What if you want you tooling to ensure that you have the correct methods on a type to match an interface though, like implements or extends (or whatever the fancy Object-Oriented language for it is) ?

The pattern above, var _ interface = &myType{} is how I learned the notation but this stack overflow answer goes into more detail about variations on this pattern.

That’s just about it, or at least that’s the short list of things I didn’t expect that I’ve found in Go over time. I hope you’ve learned something, or at least I hope you’ve enjoyed reading, and thanks!

--

--

I'm a Software Engineer, FAA 107 Drone Pilot and Radio Amateur. I write about things related to SWE and Tech and my own projects.