“Go” from nothing to something

I feel like I’m hitting myself with the name of these “nothing to something” stories but so be it. I don’t know Go and I have to start somewhere. Let’s do it together.

It is assumed that you know programming basics since I will not be going into detail on types, programming fundamentals and such other than to show the syntax differences and how they compare to someone coming from a Java/C# background. Also it would be great if you have Git, VS Code (You can use any other editor if you want) preinstalled.

So Lets GO, No?, Ok.

  • Created at 2012 to take advantage of multi-core processors
  • Multithreading, concurrency and networking easier to implement — Geth, which is a Go implementation of Ethereum blockchain gateway and Youtube makes heavy use of Go.
  • Best of both worlds: Garbage collectors and compiled language
  • Supposedly easier learning curve (We shall see)
  • OS independent

Also as a side note, Go language is just called that, not Golang. The name got stuck because website go.org at the time was unavailable. More details here.

You could bypass creating your dev env but ultimately there will come a point where you have to set it up, but in the mean time you could use go playground to test small bits of code.

You can use the finalized repo if you prefer.

Start by installing Go for your OS. After your installation, typing go in your cmd should show something like below;

Create a folder named go-tests (or whatever name you want). Now open VS Code and open folder. If you are creating from scratch, you should be seeing a whole lot of nothing.

great success

Now create a folder, first-run, then create a file named test1.go under it with below content. We will talk about the content soon.

package main
import "fmt"
func main() {
fmt.Println("Mandatory hello world")
}

So VS Code immediately tells you that you can install additional extensions for your dev env.

Clicking Show Recommendation gets the following which we want

It then complains about gopls (go please) missing. From the description it seems nifty so lets get that too.

And then the below ones;

When we click run, you should get something like the following.

But clicking the terminal and moving to the folder the go file is in. We can just say go run test1.go which works.

Ok

Now lets try to use some of the Go commands listed below

result of: go help

We’ve already seen go run being used. go mod init creates the go.mod file which defines the go folder we have as root and creates a module from there (we will get back to that). So, running;

go mod init go-tests/first-run

creates a go.mod file like below.

gopls requires a decent module structure, lets ignore that for now

Now running go build, we get an executable first-run.exe

So if we add the following bold lines

package mainimport (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println("Mandatory hello world")
fmt.Println(quote.Go())
}

VSCode is complaining that the package does not exist. We are using both fmt and rsc.io/quote packages in our code.

this is where go mod tidy is useful. It handles missing dependencies sort of like gradle. Also the go.mod file is sort of like build.gradle I suppose.

ok then

“go mod tidy” seems to have gathered our dependencies dependencies as well.

Our new go.mod file

After doing a build, another file called go.sum has appeared though. This file apparently keeps the hashes of all dependencies in a module so future downloads always get the same files. It is recommended to push both go.mod and go.sum to source control.

Using go mod graph and go list -m all brings module dependencies (and sub dependencies as well) list has more options but lets not get into that yet.

So one thing we did wrong is that we didn’t set the GOPATH env and hence when we check for dependencies it goes to the default directory. Below is our dependency, nowhere related to our project, which may be something you want. Same as gradle with its cache I suppose.

go env shows the list of env related to go, GOPATH and GOROOT are directly influencing us right now.

go install compiles and places a binary file (and dependencies)to the path shown by GOBIN env var. In our case we have to select the go file like go install test1.go and an exe would drop in the same folder like go build.

First, we create a file called hello_again.go under a unit-tests folder

package whatarethepointofthesefunc Hello() string {
return "so, hi I guess"
}

Then create hello_again_test.go (yes, wonderful name) with the below content(Original is from go docs).

package whatarethepointoftheseimport "testing"func TestHello(t *testing.T) {
want := "so, hi I guess"
if got := Hello(); got != want {
t.Errorf("Hello() = %q, want %q", got, want)
}
}

Now if we run the below command, a go mod file will appear.

go mod init go-tests/unit-tests

What happened here was we created a namespace where our module will reside. It could be domain/repo or something else. After this, we can run our test with go test

fantastic coding

Go works in the following sense, for the func (methods) at least;

func Name(parameters) (nameOfReturnVal type, err error)

Looking at below function, doc shows that it takes unlimited number of any type of parameters and returns any error and number of bytes written. Notice multiple return values from the method, noice.

fmt.Println(modules1.HelloWithQuotes())
func Println(a ...interface{}) (n int, err error)

if we wanted to capture the errors we could write;

n, e := fmt.Println(modules1.HelloWithQuotes())
fmt.Println(n,e)

Also some weird thing, if you were to omit printing e, compiler would throw error so we have to put _ instead to show we don’t want the value.

n, _ := fmt.Println(modules1.HelloWithQuotes())
fmt.Println(n) //no e here creates a compilation error

Something also neat with VS Code is it auto formats every save.

Go has all the standard types and uses a concept called identities which we will come back to later.

Let’s try it out. Create a folder called variables under project root, and paste the following code to a go file.

package vartrialsimport "fmt"global_short_decl_var := 42   //compile error
var regular_global_var_decl=22
var something_else string // declare with type
func vartrials() {
some_var := 42
some_undeclared_other_var = 11 //compile error, not declared
var some_other_other_var = 122
fmt.Println(some_var, some_undeclared_other_var, some_other_other_var)
this_var_is_valid := "some text here"
also_this_string_is_valid := `"some" interesting
asignment
`
}

fmt.Println(this_var_is_valid)
fmt.Println(also_this_string_is_valid)
//scoped var, lives only within the curly braces
{
scopedVar := 1
fmt.Println(scopedVar)
}
fmt.Println(scopedVar) //this throws an error because it doesn't exıst
}

Go uses the short declaration operator := to declare a var and assign it a value. Like in many other languages an undeclared var, some_undeclared_other_var creates an error while the next var declared explicitly with var and assigned a value is fine.

Additionally, using standard var declaration works fine for global variables (outside of a func) but fails using short declaration which is interesting in my opinion.

Also string assignments work with both “” for one liners and ``which covers multiple lines.

There's also the context of scoped variables, similar to other languages in the general sense but also can be explicitly stated with the curly braces. This could work in interesting ways when in Go, even functions are types.

Besides the standard types, we can create our own types like below. Paste the below code to another go file in the same folder and run it with go run filename. fmt.Printf we used below prints the type of our variable with %T format.

package mainimport "fmt"func main() {
type new_type_who_dis string
var this_works new_type_who_dis = "Hello?"
fmt.Println(this_works)
fmt.Printf("%T \n", this_works)
}

Add the bits below and see what happens;

var stringy string = "hi?"
this_works = stringy
ok, reasonable

So nothing nuclear but we have to cast to be able to do such assignments like below; Go people call casting, conversion.

this_works = new_type_who_dis(stringy)

While talking about types, lets talk about ones that are different than most others. One being rune which is an alias for int32 and byte which is an alias for uint8.

There is also iota. Create another go file under the same dir and type the following and run it.

package mainimport "fmt"const (
one = "ONE"
other = iota
other_second = iota
how
)
func main() {
fmt.Println(one)
fmt.Println(other)
fmt.Println(other_second)
fmt.Println(how)
}

Here we created 4 constant package (global) variables but the different thing is the ones with type iota and the last one. When you run the file you get something like below

Within a constant declaration, the predeclared identifier iota represents successive untyped integer constants. Its value is the index of the respective ConstSpec in that constant declaration, starting at zero. It can be used to construct a set of related constants:

I was thinking for an example where this might be great for but at this moment can’t think of anything.

No while in loops. Yup, and the general for looks like below; 3 versions, first is the standard loop, second is the while of Go, 3rd is the do-while. The conditionals are the same.

package mainimport "fmt"func main() {
var i int = 0
fmt.Println("loop1")
for i = 0; i < 2; i++ {
fmt.Println(i)
}
//or
fmt.Println("loop2")
for i < 3 {
fmt.Println(i)
i++
}
//or
fmt.Println("loop3")
for {
//stuff
if i < 5 {
fmt.Println("breaking")
break
}
fmt.Println("continuing")
continue
}
if i := 4; i == 4 {
fmt.Println("i is 4")
}

Seems not so different except for the last condition. Here, we can see the implicit “;” that exists after every statement explicitly written to stuff multiple statements to the if clause. This seems to be used to limit scope of the var in condition.

There's also the case with switch statements where you usually run every case after the one that is hit until you break out. In Go, this doesn't happen and if you want that behavior on the hit item you have to add fallthrough to the condition as below;

switch i { //function i var which is 3
case 1:
fmt.Println("i 1")
case 3,52,88:
fmt.Println("i 3")
fallthrough //this makes switch work like in c# and java
case 4:
fmt.Println("i 4")
case 5:
fmt.Println("i 5")
}

fallthrough allows the next case statement to be executed as well. Unless that statement also has fallthrough, that will be considered like a break and switch will be exited. We can also have a default case like other languages.

Go suggests Slices instead of Arrays. Groups values of the same type. So If I understand correctly this is like a dynamic list.

package mainimport "fmt"func main() {
var myyarr [4]int //array
myyarr[1] = 2
myyarr[3] = 33
for i, values := range myyarr {
fmt.Println("index:", i, "val:", values)
}
myslices := []int{2, 3, 4, 4, 42, 2} //slice
for i, values := range myslices {
fmt.Println("index:", i, "val:", values)
}
}

We can loop like in the other languages, this time with range keyword. Looping through array or slices doesn’t make a difference.

We can also append or remove from a slice like below; We can append another slice by opening it with and/or add values directly. To remove the bold values from our slice, we count to the index of the value we want to remove (3), next we put the index of the value we want to keep(5). Surprisingly hostile to deletion from a slice.

// APPEND
other_slice := []int{4, 322}
myslices = append(myslices, 22) //add values directly
myslices = append(myslices, other_slice...) // add from another slice
fmt.Println("\nmodified ")
fmt.Println(myslices)
//REMOVE
// {2, 3, 4, 4, 42, 2, 22, 4, 322}
//to remove a value/set of values from slice we create another slice from it
//without that value. To remove 4 and 42 for example;
fmt.Println("\nremoved vals")
myslices = append(myslices[:3], myslices[5:]...)
fmt.Println(myslices)

One interesting thing to note with slices is, we can make a predetermined size slice (like array?) and set a final size capacity.

myslices := make([]int,5,10)

With this statement, we created an int slice of 5 values (all zero by default) and set a max capacity of 10. Interesting bit is we can append to this array to increase the number of values up to 10, and then the slice capacity increases to double the original to 20. Capacity seems to be added by default, we will get back to that later.

Additionally, there's Maps in Go as well. We defined a map with the map keyword, then add the type of the key within [], finally the type of the value field is added. In our case, keys are string, values are int. Works as expected intuitively.

//maps
my_map := map[string]int{
"testkey": 11,
"testkey2": 22,
}
fmt.Println("\nmap")
fmt.Println(my_map)
fmt.Println(my_map["testkey2"])

Adding to a map is very easy as well,

//adding to a mapmy_map["some_key_that_didnt_exist"] = 55
for key, val := range my_map {
fmt.Println(key, val)
}

So I was dreading the deletion of a Map item but, again surprisingly unlike slices, it is easy to delete.

delete(my_map,"some_key_that_didnt_exist")
well…

Now lets move on to Aggregate Data Types. We created a custom type from a primitive before like below;

type new_type_who_dis string

We can also create custom types with different fields within them with struct.

package mainimport (
"fmt"
)
func main() {type custom_type struct {
field1 string
field2 int
field3 string
}
var so_custom custom_type = custom_type{
field1: "this is a val",
field2: 42,
field3: "other prop",
}
fmt.Println(so_custom)
}

Nothing groundbreaking, but check this out; We can add a field of another composite type to our custom type, in this case child_type. We can give a name to the field or not, doesn’t matter. How does this not get insanely unreadable very quickly I don’t know.

type child_type struct {
uniq_field string
custom_type
master_prop custom_type
}
var so_custom custom_type = custom_type{
field1: "this is a val",
field2: 42,
field3: "other prop",
}
var children child_type = child_type{
uniq_field: "wow, much unique",
custom_type: custom_type{
field1: " a value",
field2: 51,
field3: "a prop",
},
master_prop: custom_type{
field1: "1",
field2: 33,
field3: "44",
},
}
fmt.Println(so_custom)
fmt.Println(children)

In a case we needed to use some complex object for a function and not use it elesewhere, we can use anonymous structs.

anon_val := struct {
some_field string
another_field int
}{
some_field: "well, this is awkward",
another_field: 22,
}
fmt.Println(anon_val)

Allrighty then.

Beside from the obvious main which is our entry point, functions work in Go as in below; notice the swapped name and type in both return and par. Also we can return multiple values.

func (typeName type) FuncName (parameterName typeOfParam, ...) (returnValName typeOfReturnVal, ...) {
...
}

I created a functions folder with a functions.go file to try out the code below. Also a small note, everything passed to functions is passed by value, unless we use like …(parameterName *typeName) in which case we pass the value of the address of the variable. We will get back to this.

package mainimport (
"errors"
"fmt"
)
func main() {
parameterlessFunc()
funcWithOneParameter(22)
funcWithOneSliceParameterLikeFmtPrintln("hello", "several", "itemsToPrint")
data, errorVal := funcWithOneParameterAndSeveralReturnVals(44)
fmt.Println(data, "\n", errorVal.Error())
func(par int) {
fmt.Println("this is from an anon func, pass par:", par)
}(44)
//using functions like a var
someFunc := func(par int) {
fmt.Println("this is from a func as val, pass par:", par)
}
someFunc(4123)
total := add(1, 2, 3, 4)
fmt.Println(total)
total = addEven(add, 33, 1, 2, 3, 4)
fmt.Println(total)
}func parameterlessFunc() { //this { has to be here.
// Go impilicity places ; after statements
fmt.Println("Hello from parameterless")
}
func funcWithOneParameter(par int) {
fmt.Println("Hello with parameter:", par)
}
func funcWithOneSliceParameterLikeFmtPrintln(par ...string) {
fmt.Println("Hello with parameter:", par)
}
func funcWithOneParameterAndSeveralReturnVals(par int) (data string, genericError error) {
fmt.Println("Hello with parameter:", par)
return "This is returned from func", errors.New("this is an error")
}
func add(valListToAdd ...int) int {
total := 0
for _, val := range valListToAdd {
total += val
}
fmt.Println("total:", total)
return total
}
func addEven(addfunc func(valListToAdd ...int) int, valListToAdd ...int) int {
var evenNums []int
for _, val := range valListToAdd {
if val%2 == 0 {
evenNums = append(evenNums, val)
}
}
return addfunc(evenNums...)
}

Lets go over this since it’s a bit long. First function is just a standard function without a parameter and the next one has a single param to use. Third function accepts a slice of strings just like fmt.Println function.

parameterlessFunc()
funcWithOneParameter(22)
funcWithOneSliceParameterLikeFmtPrintln("hello", "several", "itemsToPrint")

Next function called funcWithOneParameterAndSeveralReturnVals accepts and prints a parameter and returns two values, namely a string and an error message just for the sake of it.

Now the next 2 functions are interesting because the first one (func(par int)…) is called an anonymous function and used with a parameter, int for our case, immediately. The next one creates and assigns the anon function to a variable somefunc which we then use to execute the function.

The next 2 functions are related to each other in the sense that they implement function callback. First function add just adds any number of int values given to it and returns the total. The next one, addEven though has the below function signature.

func addEven(addfunc func(valListToAdd ...int) int, valListToAdd ...int) int {

It will accept a function and a list of values to work. That function requires a list of values itself and returns an int too so how does this work? Lets check out the code of addEven. Here we create a slice called evenNums, loop through the given values coming to the function and via mod, find only even numbers and append them to our slice. Finally, we use the add method we gave as an argument to sum up the numbers in the slice. Notice that we had the unfurl the slice and didn’t just pass it as is.

{
var evenNums []int
for _, val := range valListToAdd {
if val%2 == 0 {
evenNums = append(evenNums, val)
}
}
return addfunc(evenNums...)
}

Now lets create a new file, I used func_magic.go as a name, to see the real magic with the functions

package mainimport (
"fmt"
)
type someCustomTypeName struct {
field1 string
field2 int
}
type someOtherCustomTypeName struct {
field3 string
field4 int
}
//a value can be of multiple types
//since we attached someCustomTypeName to typeActionFunc,
// through the interface below, they are also of type someInterfaceName
type someInterfaceName interface {
typeActionFunc()
}
func main() {
normalFunc()
defer closeFunc()
normalActionFunc()
someData := someCustomTypeName{
field1: "ook",
field2: 22,
}
someOtherData := someOtherCustomTypeName{
field3: "not ok",
field4: 32,
}
someData.typeActionFunc()
someOtherData.typeActionFunc()
interfaceFunc(someData)
interfaceFunc(someOtherData)
interfaceFunc(&someData) //works with pointer values as well.
} // defer keyword waits until the end of parent func to runfunc normalFunc() {
fmt.Println("opening stuff")
}
func normalActionFunc() {
fmt.Println("doing stuff")
}
//this func attaches itself to all someCustomTypeName types
func (customval someCustomTypeName) typeActionFunc() {
fmt.Printf("type:%T - ", customval)
fmt.Println("field1: ", customval.field1, "\n", "field2: ", customval.field2)
}
//this func attaches itself to all someOtherCustomTypeName types
func (customval someOtherCustomTypeName) typeActionFunc() {
fmt.Printf("type:%T - ", customval)
fmt.Println("field3: ", customval.field3, "\n", "field4: ", customval.field4)
}
//this func is used via the interface connections
func interfaceFunc(interfacedType someInterfaceName) {
fmt.Printf("In interfaceFunc with type:%T\n", interfacedType)
switch interfacedType.(type) {
case someCustomTypeName:
fmt.Println("field1: ", interfacedType.(someCustomTypeName).field1, "\n", "field2: ", interfacedType.(someCustomTypeName).field2)
case someOtherCustomTypeName:
fmt.Println("field3: ", interfacedType.(someOtherCustomTypeName).field3, "\n", "field4: ", interfacedType.(someOtherCustomTypeName).field4)
default:
fmt.Println(interfacedType)
}
}
func closeFunc() {
fmt.Println("closing stuff")
}

You’ll immediately notice 2 custom types and an interface at the top but let’s just leave those for now and focus on the first 3 lines in main().

 normalFunc()
defer closeFunc()
normalActionFunc()

Here we are using the defer keyword on a function right in the middle. Normally what would happen is they would run in order but with defer, closeFunc() is run when the parent function finishes (by success or failure). Exactly like a finally block.

In the next parts within main, we are populating our custom types and using the below methods. Notice the bit in bold.

someData := someCustomTypeName{
field1: "ook",
field2: 22,
}
someOtherData := someOtherCustomTypeName{
field3: "not ok",
field4: 32,
}
...
func (customval someCustomTypeName) typeActionFunc() {
fmt.Printf("type:%T - ", customval)
fmt.Println("field1: ", customval.field1, "\n", "field2: ", customval.field2)
}
//this func attaches itself to all someOtherCustomTypeName types
func (customval someOtherCustomTypeName) typeActionFunc() {
fmt.Printf("type:%T - ", customval)
fmt.Println("field3: ", customval.field3, "\n", "field4: ", customval.field4)

These are called methods where by this usage attaches the function to the types that are referred. For example, customval of type someCustomTypeName can now make use of the typeActionFunc function, which is exactly what is done in the next few lines

someData.typeActionFunc()
someOtherData.typeActionFunc()

After that is the more fun part. Check out this interface and the function related to it. In Go, when we include a function within an interface (which is also a type) the method relations it has also apply. In our case, both someData and someOtherData which are of different types are, via relation to the typeActionFunc, now also of type someInterfaceName as well.

//a value can be of multiple types
//since we attached someCustomTypeName to typeActionFunc,
// through the interface below, they are also of type someInterfaceName
type someInterfaceName interface {
typeActionFunc()
}
...
//this func is used via the interface connections
func interfaceFunc(interfacedType someInterfaceName) {
fmt.Printf("In interfaceFunc with type:%T\n", interfacedType)
switch interfacedType.(type) {
case someCustomTypeName:
fmt.Println("field1: ", interfacedType.(someCustomTypeName).field1, "\n", "field2: ", interfacedType.(someCustomTypeName).field2)
case someOtherCustomTypeName:
fmt.Println("field3: ", interfacedType.(someOtherCustomTypeName).field3, "\n", "field4: ", interfacedType.(someOtherCustomTypeName).field4)
default:
fmt.Println(interfacedType)
}
}

Hence, when we make use of the interfaceFunc function like below, we can use the two previously initialized variables.

interfaceFunc(someData)
interfaceFunc(someOtherData)

We can check for the type with a fabulously complex method aVariable.(type) and cast via an equally complex way of aVariable.(targetType)…

Also as a side note for now, if you start a type or a func with capital letters `func LikeThis ()…` it can be accessed outside of the file. Basically make it public. How nice that they have implied accessibility built in. I can see world peace forming already.

Yay, the horror of pointers have returned.

When we talked about variables as parameters to functions, we said that it was all passed as call by value. This applies for the address of the variable we store in our memory. Consider the below example, which is within the pointers folder.

var x int 
...
x = 11
fmt.Println(x)
fmt.Println(&x)
fmt.Printf("%T\n", x)
fmt.Printf("%T\n", &x)

We create and assign an int value and print its contents. To print the address as well, we use the & notation. Run this and check out the types printed, specifically of the one belonging to the address of x;

*int? ok

So, to pass this address as call by value we can use its type like below,

checkWhatsInAddr(&x)
...
func checkWhatsInAddr(addressGiven *int) {
fmt.Println(*addressGiven)
}

Also notice how we actually make use of the address.

Brings back memories…

To top it further, we can assign a new value via the * notation. Add the following lines within checkWhatsInAddr function and run it.

...
changedVal := *addressGiven + 10
*addressGiven = 44
fmt.Println(changedVal)
fmt.Println(x)
...

The first 4 lines are from main. We have our x which is a global var, and we sent its address to our function. Here we first printed the value in the addr previously but now at first we modified the value in the address and assigned it to changedVal and then we modified the value pointed to by our parameter addressGiven and changed that directly. Using our original x, we saw that the core value had indeed changed. Essentially, if you have the addr of a var, you can modify it as you wish from anywhere.

I think I finally understand when Go people state that they don’t have pass by ref in function pars because you actually transfer the addr of the variable as a value but in practical sense, this does indeed exist in a different fashion since what we essentially did was change the value of a global var from a func.

Previously we attached functions to types (via receivers) and created interface types using those functions to attach related types together. Basic logic was like below;

type A struct{
...
}
type B struct{
...
}
func (aval A)commonfunc(){
...
}
func (bval B) commonfunc(){
...
}
type CoreType interface{
commonFunc()
}
func someFuncUsingCoreType(val CoreType){
...
}
func main(){
aTypeVal:=A{
...
}
bTypeVal:=B{
...
}
someFuncUsingCoreType(aTypeVal)
someFuncUsingCoreType(bTypeVal)

}

So, these types of receivers are called non-pointer receivers and they work on both pointer and non-pointer values.

In our previous func_magic.go file, we didn’t talk about the last line in main,

interfaceFunc(&someData) //works with pointer values as well.

Here we essentially send the address of someData var to our interface function which works.

//this func is used via the interface connections
func interfaceFunc(interfacedType someInterfaceName) {
fmt.Printf("In interfaceFunc with type:%T\n", interfacedType)
switch interfacedType.(type) {
case someCustomTypeName:
fmt.Println("field1: ", interfacedType.(someCustomTypeName).field1, "\n", "field2: ", interfacedType.(someCustomTypeName).field2)
case someOtherCustomTypeName:
fmt.Println("field3: ", interfacedType.(someOtherCustomTypeName).field3, "\n", "field4: ", interfacedType.(someOtherCustomTypeName).field4)
default:
fmt.Println(interfacedType)
}
}

Notice the type printer in our interface function additionally, we don’t hit any of the types in switches so we drop to default which just prints the address of this var.

If we were to add a * check to reach the values, we could use them as before,

case *someCustomTypeName:
fmt.Println("field1: ", interfacedType.(*someCustomTypeName).field1....

If we were to modify the receiver of commonfunc to use pointer receivers though, our interface would only accept pointer values so that's something to keep in mind.

In Java or C#, when we had to convert JSON data to byte arr or vice-versa we called it serialization and deserialization. In Go, we call it Marshalling and unmarshalling. Lets check out the example of Marshaling in pkg.go.

I’ve created a folder called json with a file in it named json.go to test the code out.

...type ColorGroup struct {
ID int
Name string
Colors []string
}
func main() {
marshal()
unMarshall()
}
func marshal() {
reds := ColorGroup{
ID: 1,
Name: "Reds",
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
byteVal, err := json.Marshal(reds)
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("\n%+v", reds)
fmt.Println(reds.Colors)
fmt.Println(reds.Colors[1])
os.Stdout.Write(byteVal)
}
func unMarshall() {
var jsonBlob = []byte(`
{
"ID":2,
"Name":"Whites",
"Colors": ["Beige","Gray","Ivory"]
}
`)
var whites ColorGroup
err := json.Unmarshal(jsonBlob, &whites)
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("\n%+v", whites)
fmt.Println(whites.Colors)
fmt.Println(whites.Colors[1])
}

You’ll notice we have some slight changes to the official example code. Basically we are marshaling a ColorGroup type variable to JSON format. I’ve written extra Println statements just to make sure data is correct. To unmarshall, in other language terms deserialize from JSON, we use the clearly named command but the core difference is we give the address of the ColorGroup type variable we created beforehand called whites.

Some pointers I believe to be important with this as stated in the doc;

  • []byte encodes as base64 string
  • If you give a field “-” JSON tag, its discarded.
  • If any field has JSON tags, only those with tags will be taken into account.
  • Pointer values encode as the value pointed.
  • Interface values as the value contained in the interface
  • Unmarshal method needs an address of the var we are unmarshalling to.
  • Tries to match fields exactly but will fallback to case insensitive matches.
  • Will try its best to keep unmarshalling fields after a field with a wrong value. But no guarantees?

So we’ve talked about most of the other fun bits (skipped a lot too) but now let’s look at one of the main attractions of Go. Before we dive in to the code though, a small terminology mix-up must be cleared and what better way to do so than with an image.

In the above image we can see the difference of Concurrency (top) and Parallelism (bottom). In short;

Concurrency: Running multiple tasks at the same time utilizing CPU computing algorithms. Uses same resource and is run together with every other job running concurrently.

Parallelism: Running multiple tasks at the same time using different CPU resources.

Now, lets Go Code (hehe). I created a new file called threading.go under threads folder with below content.

func main() {
fmt.Printf("cpu: %v\n", runtime.NumCPU())
fmt.Printf("go routines: %v\n", runtime.NumGoroutine())
go shout() //concurrency
yell()
fmt.Printf("go routines: %v\n", runtime.NumGoroutine())
}
func shout() {
for i := 0; i < 15; i++ {
fmt.Printf("shouting Hi, %v times\n", i)
}
}
func yell() {
for i := 0; i < 15; i++ {
fmt.Printf("YELLING HI, %v times\n", i)
}
}

Kept it simple, we print the number of CPUs we have and the number of go routines which pass for threads in Go. At the beginning there is only 1 routine, the main function running. When we use go in front of a function call, that creates another routine so both functions can run concurrently.

However, since the app runs so fast, even though getting a go routine running takes so little time our main app finished and exited so we didn’t even see shout function’s output.

There is something called WaitGroups which waits for go routines to finish.

We can Add a counter which will release all routines blocked on wait. We can signal Done at our methods to chip away at the counter. Also to create a waiting point we use Wait. In our previous code we can add the following lines.

...var waitGroup sync.WaitGroupfunc main() {
...
waitGroup.Add(1)
go shout() //concurrency
yell()
fmt.Printf("go routines: %v\n", runtime.NumGoroutine())
waitGroup.Wait()
}
func shout() {
...
waitGroup.Done()
}
func yell() {
...
}

When we run with above additions, main routine waits for shout routine to finish and then exits.

This is nice and all but when writing code with concurrency, usually we have to design around race conditions but in go;

Go’s approach to concurrency differs from the traditional use of threads and shared memory. Philosophically, it can be summarized:

Don’t communicate by sharing memory; share memory by communicating.

Channels allow you to pass references to data structures between goroutines. If you consider this as passing around ownership of the data (the ability to read and write it), they become a powerful and expressive synchronization mechanism.

That's actually really nice, lets try out these channels though.

If I’m in a new routine, it means I’m in a function but how would I get a return value from a function running that way? Let’s create a new go file called channels.go and add below code.

var waitGroup2 sync.WaitGroup
var channelInt = make(chan int)
//var channelInt = make(chan int,2) //if you want to create a buffered channel
func main() {waitGroup2.Add(2)
go generateNum()
go yell2()
fmt.Printf("rand: %v \n", <-channelInt)
close(channelInt)
waitGroup2.Wait()
fmt.Printf("%v go routines finished\n", runtime.NumGoroutine())
}
func generateNum() {
defer waitGroup2.Done()
rand.Seed(time.Now().UnixNano())
channelInt <- rand.Intn(100-10) + 10
}
func yell2() {
defer waitGroup2.Done()
for i := 0; i < 3; i++ {
fmt.Printf("YELLING HI, %v times\n", i)
}
}

There is some fantastic stuff there in the code, for example as a global var, we created an int channel with make(chan int). In our main, we created a waitgroup with value 2 so we need to listen for done twice.

As functions we have the old yell function with a shorter loop this time and a random number generator. Since we launch both functions in their own go routine, yell has a done call with defer which works just fine as expected (runs after routine ended and generateNum function makes use of done as well but only after stuffing a random int value to our channel.

There is a wait right before final line printing and notice the previous 2 lines ending with close(channelInt) there. If we don’t read from the channel we will get the below error.

fatal error: all goroutines are asleep — deadlock!

That's because we didn’t specify a buffer size when creating the channel. You can’t read and write from channels unless you prepare a buffer. Write to the channel blocks until the value written (or the number of values you specified in buffer) is read so we have to read and close the channel to proceed with the main function itself. Using somevar <-channelInt we can dump all data within to a var.

But why use channels when we could’ve just assigned values within the routine functions?

Remember, Go doesn’t like memory sharing between routines and recommends channels. Besides, we would’ve had to make use of mutexes and locks so this is way simpler in my opinion.

Channels can be multi-directional by default and one directional if you want.

Lets modify our code a bit. Below we added a new func called stuffToChannel which places numbers 0 to 10 to our previous channel, waiting for a second and printing a message it’s doing so. While this is happening, our main routine does not stop and listens to the channel for data and prints them. So in short, we have a sub routine placing data on a channel and another routine reading from it until the channel is closed. Neat. The important bit here is that main routine cannot end without our channel being closed.

go stuffToChannel()for val := range channelInt {
fmt.Printf("in range reading channel:%v\n", val)
}
...
func stuffToChannel() {
for i := 0; i < 10; i++ {
channelInt <- i
fmt.Printf("stuffing %v to channel\n ", i)
time.Sleep(1 * time.Second)
}
close(channelInt)
}

Ok, now we are ready for the SELECT statement. I’m doing an intro because, just have a read of the explanation.

The select statement lets a goroutine wait on multiple communication operations.

A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

Wow,

Now let’s try it out. I created a select.go file and used below code. Here, we have almost the same code to our previous example but with minor differences. For example, we have 2 channels, one of which take looped numbers from 1to 10 as before but right before it’s end, it sends a 0 value to another channel called channelQuit. While this happens within the routine, in our main routine we have an infinite loop with a select statement including cases for our channels. Within these cases we look for data coming from either of these channels and if channelQuit receives data, application terminates. So, I’m getting some Kafka vibes here because channels are sounding more like topics to me.

func main() { var channelNumInt = make(chan int)
var channelQuit = make(chan int)
go stuffToChannelSelect(channelNumInt, channelQuit)
for {
select {
case data := <-channelNumInt:
fmt.Printf("in range reading channel:%v\n", data)
case data := <-channelQuit:
fmt.Printf("Exiting:%v\n", data)
return
}
}
}
func stuffToChannelSelect(channelNumInt chan int, channelQuit chan int) {
for i := 0; i < 10; i++ {
channelNumInt <- i
fmt.Printf("stuffing %v to channel\n ", i)
time.Sleep(1 * time.Second)
}
channelQuit <- 0
}

There are further patterns of using channels like Fan-in, which combine several channel to a single one and operate on that and there is Fan-out which creates a routine for a range of items and manages their end with wait groups as we did before. We won’t go into detail on those because we have another tough nut to crack.

I was going to glance over contexts but seeing that they are used in most tracing (and other stuff) related with Go, apparently we have to understand this as well. Let’s go over with the code below. I created another file called contexts.go.

func main() {
contextCancel, cancel := context.WithCancel(context.Background())
contextCancel = context.WithValue(contextCancel, "myData", 42)

fmt.Println("****\nprocessing cancelly request")
go workThatIsCancelled(contextCancel)
time.Sleep(time.Second * 3)
fmt.Println("cancelling context")
cancel()
//this is needed so main doesn't destroy itself until
//we see the ctx done message
time.Sleep(time.Second * 1)
fmt.Println("****\nprocessing deadline request")
contextDeadline, cancelDeadline := context.WithDeadline(context.Background(), time.Now().Add(time.Second*2))
go deadlineBreakingWork(contextDeadline)
time.Sleep(time.Second * 4)
if errors.Is(contextDeadline.Err(), context.DeadlineExceeded) {
fmt.Println("Deadline exceeded, context cancelled at deadline")
}
cancelDeadline()
}
func workThatIsCancelled(contextCancel context.Context) {
for i := 0; i >= 0; i++ {
select {
default:
fmt.Printf("processing index:%v\n", i)
time.Sleep(1 * time.Second)
if i == 10 { //finish context completion at an arbitrary logic
contextCancel.Done()
}
case <-contextCancel.Done():
fmt.Printf("request cancelled. Value at context myData:%v\n", contextCancel.Value("myData"))
return
}
}
}
func deadlineBreakingWork(contextDeadline context.Context) {
fmt.Println("doing lots of work")
time.Sleep(time.Second * 3) //just over the deadline of 2 secs so we drop to timeout
contextDeadline.Done() //normally you would exit stating you are done
}

What happens here is we create two contexts to showcase what can be done. First, in main we create a new context which is cancellable (has the cancel function as a return) and choose background context as parent, which is the main function. Right after that, we add the value 42 with the identifier myData to our context.

Afterwards, we launch a new goroutine for func workThatIsCancelled, wait 3 seconds and hit the cancel function of our created context. In the mean time, we are in an infinite loop every 1 sec within our func unless loop counter reaches 10 or somehow the context itself is “done”. Done, in this case, means that either the context finished gracefully by loop counter reaching 10 or our cancel method worked, which is the actual case.

The next part is much simpler, we create another context, this time with a deadline of 2 seconds later, launch a routine and wait for 3 seconds. In main, we wait for 4 seconds just to be sure (and not write extra code to PoC this) and when we check context error, it’s a deadline exceeded error. Same would apply to WithTimeout of context control.

errors.Is(contextDeadline.Err(), context.DeadlineExceeded)

Notice, also the casual use of context value like;

contextCancel.Value("myData")

There is not much to see here. You return error, you log the error to wherever you want (via .SetOutput…) with the log package. It includes the date in each line and prints to stderr stream by default. Below is the gist of it. Now write this to every level of your app in every function logic. Truly the epitome of software indeed.

import "log"

func main() {
err:=someMethod()
if err!=nil {
log.Println("Hello world!")
}
}

One bit that's fun is the panic mode. In other apps you generally crash when you have something unforeseen but in Go, you can panic and control graceful closure or recovery of your app. Deferred function work order is from function to main in Panic. Below code exists in panic.go. What happens here is we have a little divide function that, you guessed it, divides two numbers x and y. There is also a deferred anonymous function which calls recover and if the value is not null it prints the error and returns the handle to main which proceeds with the regular flow. Panic must be handled within the panicking function, in a deferred func.

To make the app go in panic mode we send a division by zero. Under normal circumstances, if we had not handled the panic the app would just exit.

func main() {
divide(1, 0)
}
func divide(x int, y int) {
//this bit runs at the end regardless and
//if we are recovering from something
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovering. error is: %v \n", r)
}
}()
result := x / y
fmt.Printf("Division=%v \n", result)
}

Now we get controlled panic. This could be nifty when you can’t check for every outcome for possible errors and you can just panic out.

noice

So that’s about it. Basically we’ve covered most aspects of Go to get anyone with previous programming experience going. Of course we are missing reflection, testing and benchmark capabilities of Go and possibly so much more but that would mean we would have to go deeper but it should be your choice to go deeper wherever you want within Go and not be dragged around in a tedious tutorial. Also my chrome is starting to freeze due to length I think.

Additionally package topic is missing as well. We can use local packages by giving relative path in our imports but what about using some package in another project. This is where for some reason Go resisted my efforts so I’m going to try it out in another wall of text.

Thanks for reading and learning with me.

Multiple inline assignments can be done in one line

val1, val2 = 33, 55

Slightly side tracking trying out sort but came across below explanation.

what?

When using multiple routines working around the same data, race condition might occur. Go has a built-in function to detect race conditions, which can be used like below. See go help build for info.

go run channels.go --race

Check out the official doc about exceptions.

We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.

Go takes a different approach.

My initial reaction was LOL but come to think of it, I do like that we can return multiple values and one can be error but how does this result in clean code. We get lots of error control statements and manual error bubble ups. We tried out out those mentioned in the below explanation, but I’m not convinced yet.

Go also has a couple of built-in functions to signal and recover from truly exceptional conditions. The recovery mechanism is executed only as part of a function’s state being torn down after an error, which is sufficient to handle catastrophe but requires no extra control structures and, when used well, can result in clean error-handling code.

See the Defer, Panic, and Recover article for details.

Also, Go supporters say that specifically defer and error handling is not supported by other developers because its different and innovative; a specific LOL to that as well.

Let’s talk devops, automation and architectures, everyday, all day long. https://www.linkedin.com/in/yigitirez/

Let’s talk devops, automation and architectures, everyday, all day long. https://www.linkedin.com/in/yigitirez/