paint-brush
Time to Go! Learning Golang through Examplesby@omergoldberg
17,241 reads
17,241 reads

Time to Go! Learning Golang through Examples

by Omer GoldbergJuly 21st, 2018
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

I’ve recently taken interest in the blockchain open source community. The first thing that I’ve noticed is that the majority of projects I have encountered are written in Go. Go has been on my radar for a long time, as the community is quite excited about it and sings it high praises.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Time to Go! Learning Golang through Examples
Omer Goldberg HackerNoon profile picture

I’ve recently taken interest in the blockchain open source community. The first thing that I’ve noticed is that the majority of projects I have encountered are written in Go. Go has been on my radar for a long time, as the community is quite excited about it and sings it high praises.

Why Go?

Go is a statically typed, compiled language like C++ and Java. And it follows the simplicity of ‘C’ in syntax and semantics with some additional features.

goroutines

In comparison to older programming languages (Python, Java etc.) Go was released very recently in 2009. Go was built with concurrency in mind and the ability to utilize multi processors efficiently. Rob Pike (Go creator) has an in depth talk about this. The end result is running concurrent go routines is much cheaper than threading in other languages like Java. This allows us to run thousands of goroutines concurrently (even on a single processor!) with no worries, and makes it a perfect solution for problems that require scale.

performance of code

Go runs direct on underlying hardware. Go is not executed on VMs which means that our code is directly compiled to a binary to be run on our processor. Eliminating the middleman VM (for example the JVM in Javas case ) means that perf is about as low level as can get, and we have the ability to fine tune and optimize our code to get the most bang for our buck, by removing unnecessary abstractions and performance hits.

functions are first class citizens

In Go, functions are treated like any other data type. This means functions can returns functions, accept functions and so on. This enables us to utilize functional programming paradigms (which I love!!), and write cleaner, clearer code.

maintainability

Go syntax is concise. In addition it has an excellent formatter that formats code in a single way. This may sound like a pain but actually solves a lot of headaches around coding conventions etc. Go does away with a lot of language features that (IMO) make code harder to follow such as inheritance, constructors, annotation, generics and more.

packages

Go allows to group related code in packages. When compiled, packages share a namespace. Therefore, you can write your code in separate files, but still have the benefit of calling code that lives in different directories or files. As your project grows in complexity you can create numerous packages, each representing a different logical unit of your application. However, there is a special name we use for a package to tell Go that we want it to be turned into a file that can be executed. That name is “main”.

So naming a package “main” tells Go that we want it to be executable. Once Go knows that this file is executable it’s going to require we define a function called “main”. This main function will be invoked automatically when the program runs.

Go has a very robust standard library. In order to access we simply add import statements beneath the package declaration. Getting started should look something like this:

package main

import (

"fmt"  

)

func main() {

fmt.Println("Hello world") // Amazing logic here

}

variables

Working with variables in Go is familiar, yet different. Let’s examine variable assignment, and later we’ll take a look at working with variables by value vs. by reference. This is the vanilla way to initialize a variable in Go.

var currentDay string = “Saturday”

In an example it would look like this:

But that is pretty verbose. Go allows condensing variable declaration and assignment to simply:

currentDay := “Saturday”

Go infers here that `currentDay` is a string, so there is no need to be explicit about it. Likewise, Go understands that following is an `Integer` declaration.

luckyNumber := 3

So to bring this together, the following two are equivalent ways of declaring and initializing a Go variable.

luckyNumber := 3

var luckyNumber int = 3 // oops! This will cause an error!

Be careful though! In Go, we can only initialize a variable once. Therefore, the above code snippet will fail to compile!

functions

Go is a statically typed programming language, so we need to annotate the type of return value for each function. The following code won’t compile because we haven’t annotated the return type:

func greetUser() { // function declaration is missing return type!

return “Hey friend!”

}

To fix this err, we just add a return type to the function declaration.

Invoking this function from within the main method looks like this:

Cool. Let’s run through an example where we tie up everything we’ve seen so far. Let’s declare greetUser in a different file, but with the same package and call it. Recall, that files in the same package share a namespace, and can call each other freely.

we build the above with

go run utils.go main.go

We get the following:

from our stdout

slices and for loops

In Go, arrays are static and their lengths need to be specified when created. If you want to work with a dynamically sized array you’re going to want to use a slice. Declaring a slice is simple enough:

fruits := []string{“Mango”, “Cherry”, “Apple”}

Note, that slices can only have one type of value in it. Therefore, declaring a slice with an int as well as a string would not compile.

Now let’s do a basic iteration over the fruits slice and log each element.

Don’t forget to build with go run main.go

A gotcha is that Go requires we use all declared variables in our code. If we didn’t want the index, we could tell Go to ignore it by naming it “underscore” like follows:

How slices work under the hood

How does Go handle creating these dynamic slices? Go’s approach is quite interesting. When a slice is created two data structures are created under the hood. The first is an array, and the second is a structure that records the length of the slice, the capacity of the slice, and a reference to that array.

Is Go an Object Oriented Language?

According to the docs Yes and No (yay, what a clear answer!). Go takes a different approach to try and achieve similar results. In Go, there are no “classes”. Instead Go allows us to declare “receiver functions” for specific data types. The easiest way to understand this is to look at some code.

So although we can’t create a Day class (like we would in a classic OOP language — think Java, Python or even JS nowadays) we can still easily declare new data types and methods for those types.

So let’s bridge Go terminology and compare it with something from classic OOP.

By creating a new type with a function that has a receiver we are adding a ‘method’ to any value of that type. (Stephen Grider)

multiple return values

A feature I really like in Go is the ability to return multiple values from a function. Let’s see this in practice:

structs

Go’s structs are typed collections of mutable fields. If you’re coming from JS, python or Java it’s essentially an object. They’re useful for grouping data together to form records.

addresses and pointers

Go differentiates between two different data types.

value types — types that contain the actual value of a primitive data type. Think about int, float, strings, etc.

reference type — any data type that contains a reference to the actual underlying list of records.

This is where the conversation starts to get a bit more interesting. By default when passing params types — such as int, float, string or struct into a function, Go copies each argument and passes it by value. With that knowledge, the following examples passes by value. What do you expect it to print?

Because Go copies “value types”, and doesn’t send a reference to them the above outputs:

We just saw that variables in Go are passed by value. Like we mentioned earlier, when slices are created, an additional data structure with meta data on the slice is created. This includes a reference to the slice. That reference is used and referred to whenever we talk about slices. Therefore the following example will successfully mutate the contents of the slice.

But what happens if we want to mutate struct data sent to a function? We need to pass it by reference. Go is a bit low level with this concept, requiring us to pass addresses of variables and dereference pointers.

Aside: A pointer is simply the address of variable in computer memory. If we send the address of the variable and not the value, we can directly manipulate it.

get the address of a variable

We can always grab the address of a variable by using the & operator.

This outputs the actual address of the variable in physical memory.

Now that we have a reference to the variable (it’s address) we can easily change its value by dereferencing the pointer and assigning something new.

Aside: Dereferencing is the act of extracting a value from an address. We dereference an address with the * operator.

So if we wanted our function to receive a pointer to the type of day (our struct from the previous example) and then log its value we would try something like this:

Let’s look at an example of passing by reference when using a receiver function.

So why does this work now? We’ve changed the updateDaySuccess receiver function to accept a pointer to type day.

Important: When calling the method Go recognizes that it’s expecting a pointer, and implicitly sends a reference to the value for us (Thanks Go!) Then, updateDaySuccess receives the pointer, dereferences it with * operator, and assigns a new value.

maps

One of the most useful data structures is the hash table. They offer fast lookups, adds and deletes. Go provides a built-in map type that implements the hash table. In Go, keys and values can be of several different types with an empty interface. In this Go playground link, a map with several different types of keys is declared. Besides that they work pretty similarly to how you’d expect them too.

interfaces

Interfaces in Go make the code more flexible. Instead of requiring a particular type, interfaces allow us to specify that some behavior is needed. In Go, interfaces are satisfied implicitly. That means that we don’t need to write things like implements blahBlah (I’m looking at you Java ppl). When we say that a type satisfies an interface we mean that the type implements all of the functions contained in that interface definition. Let’s look at a basic example:

So, here we would say that the day struct satisfies the dayDetails interface because it defines a receiver functions getDay that returns a string. So if you’ve encountered interfaces in any other language the rationale and benefits carry over to Go.

goroutine

goroutine are functions (receiver or normal) that run concurrently with other functions or methods. A goroutine is basically a thread, but with smaller overhead. Because goroutines are lighter than threads it is common for go programs to have thousands of routines running concurrently. Starting a goroutine is simple! Just prepend the go keyword and you automagically get a go routine running concurrently. Although it is simple to start a go routine there are a couple of gotchas. Let’s look at this example:

What do you expect the output to be?

Why didn’t it output “Saturday”?

When a new go routine is created, the call returns immediately. Unlike functions the control does not wait for the goroutine to finish executing. So in this case, the flow of the program continues and ends, the main program terminates itself and the goroutine it owns . Let’s overcome this by sleeping our program. This will give our goroutine enough time to finish executing.

This works but is super hacky and not a practice we should adopt. Channels let us do this better.

channels

So we’ve already seen that it is easy to spawn go routines. Channels are the tools that allow goroutines to communicate. Channels allow us to pipe data from one goroutine to another (even using the pipe operator!). Each channel has a data type associated with it. The type of data associated with the channel defines which data we can pipe through it. For example the following channel allows can send and receive boolean values:

boolChannel := make(chan bool)

Sending and receiving data via channels are blocking actions, therefore we can easily rewrite the program above, omitting the sleep hackiness.

Let’s look at rewriting our program!

Conclusion

This has been a quick overview of Go functionality. Clearly, this is the tip of the iceberg and there’s a lot left to discover :) My original impression of Go is that it is concise and powerful. As of late, I am enjoying statically typed languages a lot more, as it allows for developing faster, no runtime errors and in eliminates a whole class of time-consuming bugs! I’m interested in learning Go for blockchain related projects, servers and anything that requires concurrency as it seems fun and powerful!

My next posts will explore blockchain and Go, so stay tuned!

If this post was helpful, please subscribe and click the clap 👏 button below to show your support! ⬇⬇

You can follow me on Instagram, Linkedin and Medium.