My First Week of Go
May 03, 2020 - 6 minute read
Well the last few months have certainly been interesting. One unexpected positive is that I have ended up with much more time on my hands! So I have:
- Taken another crack at Rust
- Started working through Crafting Interpreters (which has been fantastic, both in the content and writing style)
- Taken an actual look at Svelte (not bad, cool approach, so much animation built in)
- Tried out Go
Until this week, I hadnāt written a single line of Go, and had only seen it when briefly checking out the languageās website. I had heard a lot of praise regarding the simplicity and ease of learning, but a few things turned me away:
- Coming from JavaScript/TypeScript, I wanted to change it up with something powerful and low level, and between C++, Go and Rust, I had initially gone with Rust
- Go has no generics, and I took that alongside the āsimple to learnā and basically assumed that it was lacking the base level of type complexity to do things effectively (while Iāve found this isnāt true, simplicity is definitely a choice Go has made, and it comes at a cost)
Anyways, fast forward a few weeks and working with Rust was bumming me out. Primarily, the dev tools just werenāt working for me, and a total lack of hinting when learning a new language was brutal. On top of that, I was starting to realize that while I wanted something lower level than JavaScript, my use case (web apps) didnāt require something as heavy duty and āsafeā as Rust.
Iāve since learned in a Hacker News post that rust-analyzer is much better than RLS, so maybe Iāll give it a try again sometime. I donāt want to bash Rust, still super excited about it, and using it as WebAssembly might be the most applicable use case for me.
With no use case to help me learn Rust and a general frustration with the developer experience, I decided to give Go aā¦ go. Now that I have, I thought Iād write on some of the feelings I had regarding this first week, especially when compared to JavaScript/TypeScript.
Tabs vs Spaces
Starting off with a heavy hitter. I honestly never thought Iād say this, but Go has shown me that tabs are better than spaces for indentation. You can set the tab width to anything you want, so every dev can see the code the way they want to. If you like 4 spaces you can have it:
func main() {
fmt.Println("Hello, World!")
}
Or, as I am even more surprised to admit since I use 2 space indentation in my JavaScript/TypeScript code, you can do what I do and set it to 8 spaces like a lot of Go programmers (and the docs):
func main() {
fmt.Println("Hello, World!")
}
I canāt even explain it, Go code just looks good with 8 space indentation. And nesting seems to be limited in Go so itās not a big issue to have that large of an indent.
The Language
Now for the serious stuff. Iāll just start off with some things that I like:
- Real types! Coming from the TypeScript world where sometimes it can feel like a loosely-coupled type layer, this is really nice. The fact that you can assert on types, including custom types, is pretty great.
- Itās fast! Man is it fast, especially when you start using goroutines to divvy up the work. To be honest this one is smaller, since JavaScript is so optimized now, but I guess this point is more about concurrency and multithreading than just upfront speed.
- Readability. Itās very clear what is going on, mostly due to the language design. It can also be interpreted as verbosity sometimes, but itās really grown on me.
- The type system is really fantastic. Adding methods onto custom types (not just structs but custom basic types like string or a slice) is really great. To be honest I have only scratched the surface of that, since Iām only a week in, but it seems really powerful.
Other than that Iāve found it really easy to start being productive with, especially when contrasted with Rust. I even find it quicker to write than TypeScript sometimes.
Most importantly, Iām building web apps within a week, and I actually have confidence in the code Iām writing. With Rust, I was always questioning if the approach I took was the best way.
Now for some of the things I found strange:
-
The verbosity. No generics is pretty brutal. If you want to build out a
Sum
function for summing together any kind of number, thatās tough. Why not build one for each type?// SumF64 sums float64 slices. Now do this for every type. func SumF64(input []float64) (sum float64) { for _, val := range input { sum += val } return }
People seem to be on both sides of it. I think the concern is that generics will introduce complexity, and people like the simplicity of Go. I guess thatās true, but I also think that they would be a nice addition as long as you only used them for things like the above. Itās not just generics though, thereās also lots of repetition with error handling and writing out every type in a function even when itās matching an interface. This turns out to actually be a positive for readability, but I definitely found it strange at first.
- Thereās no
null
. Every type just has a zero value which is still a valid value of that type. Thereāsnil
, but thatās just the zero value for some things like slice, error or pointer. But the zero value obviously canāt represent an absence of a value, because forint
the zero value is0
, and that could be a legitimateint
value. So people end up taking pointers for optional values, and then you passnil
to them rather than a valid pointer if the value is missing. For example, the ORM gorm takes pointers to any fields that map to a nullable database column, or various other libraries which set you up to return pointers from functions so that you can returnnil
if there is an error. I get it, zero values mean that a type is always valid for that type, but it does seem strange coming from the JavaScript side of things, even a little bad, like itās a workaround to not allowing optional function arguments (I realize maybe this is just a totally normal way to do things in pointer world, but Iām coming from JavaScript where pointers are only talked about in hushed whispers). It also means you have to be careful and check if a pointer isnil
before dereferencing it, or youāll get runtime errors. Obviously, this makes the language a lot less safe than Rust. - Packages and modules stuff. This tripped me up, embarrassingly. Once I got Go modules working it was a lot better. Naming your packages on the top of the file, packages must share a directory, importing from different packages but not having to from your own. Itās all very alien to JavaScript (but I guess similar to Java ā luckily Iād done a refresher when working through Crafting Interpreters).
Developer Experience
When you first install the go dev tools in VSCode, you get absolutely bombarded by install prompts, but once thatās all over the experience is smooth. Iāve been using the gopls language server, and it has been fantastic. No other language Iāve tried has been able to match the dev experience of TypeScript on VSCode, but this comes really close. If youāre interested, this is my go-specific settings in VSCode:
{
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"editor.tabSize": 8, // Personal preference
"editor.renderWhitespace": "selection" // Personal preference
},
"[go.mod]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"gopls": {
"usePlaceholders": true,
"staticcheck": false
}
}
Summary
All in all, Iāve really liked Go. Iām going to continue building out my project in it, and it might even become my defacto back-end language. There are even more things to like about it, like compiling to machine code, or the absolutely insane start up speed. Overall, it has been a fantastic language to learn, and Iām excited to see what I can build with Go.