August 14th, 2024 08:11 UTC · 1 month ago

Golang

Relearning Go: Day 3

Maps to Strings & Runes

Maps

gobyexample.com/maps

If the key doesn’t exist, the zero value of the value type is returned.

This rubs me up the wrong way. What’s the zero value for a file handle or a database connection?

I think zero values were a bad call in general.

There’s mention later of the optional second return value which says if the value was actually present or not. Better, but in the event that value was not found, Go still has to allocate that zero value which will, presumably, get thrown away. Maybe the compiler can optimise that away?

🤔 Is that optional second return value special magic, or can I use it in my code? Put another way: can I write a function that returns either 1 or 2 values depending on how the caller deconstructs the return value?

🤔 I am curious if it’s possible to use a struct, say, as a key.

Otherwise, nothing particularly surprising. Nice and simple.

Range

gobyexample.com/range

Briefly onwards, range appears to be special for-loop syntax to iterate over arrays, slices, maps, and strings. There’s no general notion of iteration (yet; I think it’s coming) so this has to suffice.

🤔 Can I create a type that works with range, i.e. where for i in range valueOfMyType means something?

Functions & Multiple Return Values & Variadic Arguments

gobyexample.com/functions
gobyexample.com/multiple-return-values
gobyexample.com/variadic-functions

These are unsurprising. What’s offered is a subset of that of many other mainstream languages – which is no bad thing, especially since keeping the language small is a theme of Go.

Closures & Recursion & Pointers

Having done a lot of Rust recently, the simplicity of Go’s closures is refreshing. Garbage collection takes care of a lot.

Recursion is also unsurprising.

Pointers provide some more interest because we learn that arguments are copied unless passed via a pointer. This could be a bit of a performance foot-gun when arguments are large. However, since the argument can also be mutated when passed as a pointer, the default to copy is likely to be less surprising, less likely to introduce bugs.

In Python I became accustomed to pass by reference. This simple and uniform mental model was a big part of why Python was easy to learn. But I did grow weary of not being able to say “look but don’t touch”; there’s no way to be sure that some function or one of the functions that it calls won’t mutate any given data structure.

🤔 Can one pass large arguments, e.g. a large array, by pointer in order to avoid the copy, but prevent the function from mutating the array too?

Strings and Runes

Strings and runes are familiar. I like that Go’s rune is actually a Unicode code point, and that range works with them. I bet that this has side-stepped a huge stream of bugs from dealing with non-English texts. Got to love Go for doing that.

But this is Go. Can’t have a good choice without something to balance it out. The blog post Strings, bytes, runes and characters in Go says:

“Code point” is a bit of a mouthful, so Go introduces a shorter term for the concept: rune. The term appears in the libraries and source code, and means exactly the same as “code point”, with one interesting addition.

Minor niggle: I don’t think “code point” is a mouthful, plus it does align with Unicode’s nomenclature. “Rune” is cute but not helpful.

The Go language defines the word rune as an alias for the type int32, so programs can be clear when an integer value represents a code point.

Ugh 🤦 Programs can be clear… and they can also use rune for signed arithmetic, or pass code points into mathematical functions. Type safety has been diluted here. Instead, make rune opaque and not interchangeable with integers. Even if one must use an integer, why int32 and not an unsigned variant? This has the feel of a rushed API that didn’t receive enough thought until it had to be retrospectively justified.


Sorry to leave it on a downer, but that’s enough for today. Next time I’ll pick up with structs.