Couple of years ago, one of my friends wanted me to look at golang. Back then, golang
was a shiny language and there was a lot of hype around it. I briefly look at the language and I had two primary concerns back then:
- Lack of generics: No longer exists now as generics were added in
go 1.18
1. - Better ergonomic error handling: This problem even exists at the time of writing. Most of the times I see a common pattern mentioned below:
// common pattern
result, err := fetch_value()
if err != nil {
return nil, err
}
I wish there was a way2 in which I can return an error if I don’t want to deal with it. Something along the lines of:
// Inspired from rust
result := fetch_value()?
?
at the end is not actual go code. This is only for functions which returnerror
as the last argument and if there are any other arguments they will be set to their “zero” values.
Overall
Go
hits a sweet spot between feeling like a dynamic language and at the same time being a statically compiled language that is fast enough for most of the applications. This language competes with java
rather than trying to be a substitute for c
, c++
or rust
. For the remainder of this post, I will focus on how the generation of static binaries by golang
languages eliminates a whole lot of issues and I will discuss golang
features in future posts.
Static Binaries
I can’t emphasize how important is the generation of static binaries3 in today’s cloud environment and it’s nice that go
makes it very easy to generate static binaries for supported platforms.
Static binaries provide the following advantages:
-
Dependency Management and Deployment:
- No need to manage dependencies. Makes it easy to dockerize the application by just including the binary.
- Binary artifacts usually take less space4, thereby making builds easier and faster to deploy.
-
Saving Costs
- In the long run, we would cut down on our storage costs as these artifacts consume less space.
- If you are using something like lambda functions, you save money as
go
applications don’t have to spend timeJITing
the bytecode which typically happens inJVM
andruby
world. Although,JITing
the bytecode may produce more efficient code5 than static binaries in some scenarios, this process always consumes both memory and cpu cycles on the machine on which the application runs.- If your application or code runs for a very short time, then all the
JIT
complication is wasted. - Every time your applications restarts, the
JIT
compilation process needs to be repeated. - If you deploy 100 instances of these microservices, then
JIT
compilation process needs to be performed at all of these 100 instances.
- If your application or code runs for a very short time, then all the
-
This even plagues today in many of the libraries written in go before 1.18. Take for example god’s library arraylist implementation, we can create an arraylist holding different types which completely bypasses static checking. ↩︎
-
However, I can live with a little bit of syntactic sugar that can make error handling less verbose. ↩︎
-
This is applicable to any languages that can generate static binaries like
c
,c++
,rust
,crystal
etc. Recently,java
has been making strides in native compilation usinggraalvm
. ↩︎ -
For java applications, shipping all the jar files along with java runtime results in large file sizes. ↩︎
-
Several arguments made in favour of
JIT
:- Runtime can examine hot code paths and only JIT these hot paths aggressively
- Runtimes can possibly generate better assembly code as it is aware of all capabilities and features of underlying hardware.