First of all, I would like to make a disclaimer. I am not an expert in Go. I started to study it a few weeks ago, thus the statements here are kind of first impressions. I may be wrong in some of the subjective areas of this article. Perhaps I will write some time a review of this one later. But until then, here it is and if you are a Java programmer, you are welcome to see my feelings and experiences and the same time you are more than welcome to comment and correct me if I am wrong in some statements.

1. Golang is impressive

As opposed to Java, Go is compiled to machine code and is executed directly. Much like C. Because this is not a VM machine it is very much different from Java. It is object-oriented and the same time functional to some extent thus it is not only a new C with some automated garbage collection. It is somewhere between C and C++ if we think the world of programming languages is on a single line, which it is not. Using a Java programmer’s eyes some things are so much different that learning them is challenging and may give a deeper understanding on programming language structures and how objects, classes and all these things are …​ even in Java.

I mean that if you understand how OO is implemented in Go, you may understand also some of the reasons why Java has them different.

In short, if you are impatient: do not let yourself freak out by the seemingly weird structure of the language. Learn it and it will add to your knowledge and understanding even if you do not have a project to be developed in Go.

2. GC and not GC

Memory management is a crucial point in programming languages. Assembly lets you do all things. Or rather it requires you to do it all. In case of C there are some support functions in the standard library but it is still up to you to free all memory that you have allocated before calling malloc. Automated memory management starts somewhere along C++, Python, Swift, and Java. Golang is also in this category.

Python and Swift use reference counting. When there is a reference to an object the object itself holds a counter that counts the number of references that point to it. There are no pointers or references backward, but when a new reference gets the value and starts to reference an object the counter increases and when a reference becomes null/nil whatever or references another object the counter goes down. So it is known when the counter is zero, there are no references to the object and it can be discarded. The problem with this approach is that an object still may be unreachable while the counter is positive. There can be object circles referencing each other and when the last object in this circle is released from the static, local and otherwise reachable references then the circle starts to float in the memory like bubbles in water: the counters are all positive but the objects are unreachable. The Swift tutorial has a very good explanation of this behavior and how to avoid it. But the point is still there: you have to care about memory management somewhat.

In case of Java, other JVM languages (including the JVM implementation of Python) the memory is managed by the JVM. There is a full-blown garbage collection that runs from time to time in one or more threads, parallel with the working threads or sometimes stopping those (a.k.a. stop the world) marking the unreachable objects, sweeping them and compacting the presumably scattered memory. All you have to worry about is the performance if at all.

Golang is also in this category with a small little, tiny exception. It does not have references. It has pointers. The difference is crucial. It can be integrated with external C code and for performance reasons, there is nothing like a reference registry in the run-time. The actual pointers are not known to the execution system. The memory allocated can still be analyzed to gather reachability information and the unused "objects" can still be marked and swept off, but memory cannot be moved around to do the compacting. This was not obvious for me some time from the documentation and as I understood the pointer handling I was seeking for the magic that Golang wizards implemented to do the compacting. I was sorry to learn, they simply did not. There is no magic.

Golang has a garbage collection but this is not a full GC as in Java. There is no memory compaction. It is not necessarily bad. It can go a long way running servers very long time and still not have the memory fragmented. Some of the JVM garbage collectors also skip the compacting steps to decrease GC pause when cleaning old generations and do the compacting only as a last resort. This last resort step in Go is missing and it may cause some problem is rare cases. You are not likely to face the problem while learning the language.

3. Local variables

Local variables (and sometimes objects in newer versions) are stored on the stack in Java language. So are in C, C++ and in other languages where call-stack as such is implemented. Golang related to local variables is no exception, except…​

Except that you can simply return a pointer to a local variable from a function. That was a fatal mistake in C. In case of Go the compiler recognizes that the allocated "object" (I will explain later why I use quotes) is escaping the method and allocates it accordingly so that survives the return of the function and the pointer will not point to an already abandoned memory location where there is no reliable data.

So this is absolutely legal to write:

package main

import (
	"fmt"
)

type Record struct {
	i int
}

func returnLocalVariableAddress() *Record {
	return &Record{1}
}

func main() {
	r := returnLocalVariableAddress()
	fmt.Printf("%d", r.i)
}

4. Closures

What is more, you can write functions inside functions and you can return functions just like in a functional language (Go is a kind of functional language) and the local variables around it serve as variables in a closure.

package main

import (
	"fmt"
)

func CounterFactory(j int) func() int {
	i := j
	return func() int {
		i++
		return i
	}
}

func main() {
	r := CounterFactory(13)
	fmt.Printf("%d\n", r())
	fmt.Printf("%d\n", r())
	fmt.Printf("%d\n", r())
}

5. Function return values

Functions can return not only one single value, but multiple values. This seems to be a bad practice if not used properly. Python does that. Perl does that. It can be of good use. It is mainly used to return a value and a 'nil' or error code. This way the old habit of encoding the error into the returned type (usually returning -1 as error code and some non-negative value in case there is some meaningful return value as in C std library calls) is replaced with something much more readable.

Multiple values on the sides of an assignment is not only to functions. To swap two values you can write:

  a,b = b,a

6. Object Orientation

With closures and functions being first-class citizens Go is at least object-oriented as JavaScript. But it is actually more than that. Go lang has interfaces and structs. But they are not really classes. They are value types. They are passed by value and wherever they are stored in memory the data there is only the pure data and no object header or anything like that. `struct`s in Go are very much like they are in C. They can contain fields, but they can not extend each other and they can not contain methods. Object orientation is approached a bit different.

Instead of stuffing the methods into the class definition you can specify when you define the method itself which struct it applies to. Structs can also contain other structs and in case there is no name for the field you can reference it by the type of it, which becomes its name implicitly. Or you can just reference a field or method as they belonged to the top struct.

For example:

package main

import (
	"fmt"
)

type A struct {
	a int
}

func (a *A) Printa() {
	fmt.Printf("%d\n", a.a)
}

type B struct {
	A
	n string
}

func main() {
	b := B{}
	b.Printa()
	b.A.a = 5
	fmt.Printf("%d\n", b.a)
}

This is almost or a kind of inheritance.

When you specify the struct on which the method can be invoked you can specify the struct itself or a pointer to it. If the method is applied to the struct then the method will access a copy of the caller struct (this struct is passed by value). If the method is applied to a pointer to the struct then the pointer will be passed (passed by reference kind of). In the latter case the method can also modify the struct (in this sense the structs are not value types since value types are immutable). Either can be used to fulfill the requirement of an interface. In case of the example above Printa is applied to a pointer to the struct A. Go says that A is the receiver of the method.

Go syntax is also a bit lenient about structs and pointers to it. In C you can have a struct and you can write b.a to access the field of the struct. In case of a pointer to the structure in C you have to write b→a to access the same field. In case of a pointer b.a is a syntax error. Go says that writing b→a is pointless (you can interpret this literally). Why litter the code with operators when the dot operator can be overloaded. Field access in case of struct and, well field access through pointers. Very logical.

Because the pointer is as good as the struct itself (to some extent) you can write:

package main

import (
	"fmt"
)

type A struct {
	a int
}

func (a *A) Printa() {
	if a == nil {
		fmt.Println("a is nil")
	} else {
		fmt.Printf("%d\n", a.a)
	}
}

func main() {
	var a *A = nil
	a.Printa()
}

Yes, this is the point as a true-hearted Java programmer you should not freak out. We did call a method on a nil pointer! How can that happen?

7. Type in in the variable and not the object

This is why I was using quotes writing "object". When Go stores a struct it is a piece of memory. It does not have an object header (though it may, since it is a matter of implementation and not the language definition, but it reasonably does not). It is the variable that holds the type of the value. If the variable type is a struct then it is known already at compile time. If this is an interface then the variable will point to the value and the same time it will also reference the actual type it is having the value for.

If the variable a is an interface and not a pointer to a struct you can not do the same: you get runtime error. (Addition: As Theo pointed out in his comment this is because the pointer variable does not have the type and Go runtime does not know which implementation of the polymorphic method to call. However, you can have an interface variable being nil and still holding the reference to a specific type as theo shows in the example.)

8. Implementing interfaces

Interfaces are very simple in Go, and the same time very complex, or at least different from what they are in Java. Interfaces declare a bunch of functions that structs should implement if they want to be compliant with the interface. The inheritance is done the same way as in case of structs. The strange thing is that you need not specify in case of a struct that it implements an interface if it does. After all, it is really not the struct that implements the interface, but rather the set of functions that use the struct or a pointer to the struct as a receiver. If all the functions are implemented then the struct does implement the interface. If some of them are missing then the implementation is not complete.

Why do we need the 'implements' keyword in Java and not in Go? Go does not need it because it is fully compiled and there is nothing like a classloader that loads separately compiled code during run-time. If a struct is supposed to implement an interface but it does not then this will be discovered at compile time without explicitly classifying that the struct does implement the interface. You can overcome this and cause a run-time error if you use reflection (that Go has) but the 'implements' declaration would not help that anyway.

9. Go is compact

Go code is compact and not forgiving. In other languages, there are characters that are simply useless. We got used to them during the last 40 years since C was invented and all other languages followed the syntax, but it does not necessarily mean that it is the best way to follow. After all we all know since C that the 'trailing else' problem is best addressed using the { and } around the code branches in the 'if' statement. (Maybe Perl was the first mainstream C-like syntax language that requested that.) However, if we must have the braces there is no point to enclose the condition in parentheses. As you could see in the code above:

...
	if a == nil {
		fmt.Println("a is nil")
	} else {
		fmt.Printf("%d\n", a.a)
	}
...

there is no need and Go does not even allow it. You may also notice that there are no semicolons. You can use them, but you need not. Inserting them is a preprocessing step on the source code and it is very effective. Most of the time they are clutter anyway.

You can use ':=' to declare a new variable and assign a value to it. On the right hand side the expression defines the type usually, so there is no need to write ‘var x typeOfX = expression’. On the other hand, if you import a package, assign a variable that you do not use afterward: it is a bug. Since it can be detected during compile time it is a code error, compilation fails. Very smart. (Sometimes annoying when I import a package that I intend to use, and before referencing it I save the code and IntelliJ intelligently removes the import, just to help me.)

10. Threads and queues

Threads and queues are built into the language. They are called goroutines and channels. To start a goroutine you only have to write go functioncall() and the function will be started in a different thread. Although there are methods/functions in the standard Go library to lock "objects" the native multi-thread programming is using channels. Channel is a built-in type in Go that is a fixed size FIFO channel of any other type. You can push a value into a channel and a goroutine can pull it off. If the channel is full pushing blocks and in case the channel is empty the pull is blocking.

11. There are errors, no exceptions. Panic!

Go does have exception handling but this is not supposed to be used like in Java. Exception is called 'panic' and this is really to be used when there is some real panic in the code. In Java term, it is similar to some throwable that ends with '…​Error'. When there is some exceptional case, some error that can be handled this state is returned by the system call and the application functions are expected to follow a similar pattern. For example

package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.Open("filename.ext")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
}

the function 'Open' returns the file handler and nil, or nil and the error code. If you execute it on the Go Playground (click on the link above) you get the error displayed.

This is not really fitting the practice we got used to when programming in Java. It is easy to miss some error condition and write

package main

import (
	"os"
)

func main() {
	f , _ := os.Open("filename.ext")
	defer f.Close()
}

that just ignores the error. It is also cumbersome to check the possibility of error at each and every system or application call that may return error when we are interested in a longer chain of commands if any of those produced error and we do not really care which one.

12. No finally, defer instead

Closely coupled with the exception handling is the feature that Java implements with the try/catch/finally feature. In Java, you can have code that is executed in a finally code no matter what. Go provides the keyword 'defer' that lets you specify a function call that will be invoked before the method returns even if there is/was a panic. This is a solution to the problem that gives you fewer options to abuse. You can not write arbitrary code to be executed deferred only a function call. In Java, you can even have a return statement in the finally block or see a mess trying to handle the situation when the code to be executed in the finally block may also throw an exception. Go is prone to that. I like that.

13. Other things…​

that also may seem weird at first are like

  • public functions and variables are capitalized, there are no keywords like 'public', 'private'

  • source code of libraries are to be imported into the source of the project (I am not sure I understood that properly)

  • lack of generics

  • code generation support built into the language in forms of comment directives (this is really a wtf)

In general, Go is an interesting language. It is not a replacement for Java even on a language level. They are not supposed to serve the same type of tasks. Java is enterprise development language, Go is a system programming language. Go, just as well as Java, is continuously developing so we may see some change in that in the future.

Comments imported from Wordpress

notRealThing 2016-04-27 21:58:40

Great article!

gijensen 2016-05-10 14:35:21

Goroutines are more like coroutines than threads. Yes, you will get maximal performance writing for the amount of cores there are in the system (provided you’re actually generating the # of threads equivilant to your cores, which I’m not sure can be guaranteed). However because goroutines are so cheap, you don’t have to feel guilty for using them liberally, in fact it’s actually idiomatic.

In most real world scenarios you’ll probably get better performance just using goroutines instead of writing the many many extra required to eliminate them, and achieve performance gains. Golang has allowed me to attempt models I never would have considered trying with threads.

Also attempting to limit your goroutine use, you’d be excluding yourself from most third party libraries anyways.

theo 2016-05-03 09:03:40

"If the variable a is an interface and not a pointer to a struct you can not do the same: you get runtime error."

Not strictly true. You set the interface to nil, which means it has no concrete type; that is why the error is generated.

If you set the concrete type like this: https://play.golang.org/p/OWzqjBpjEa it works just fine.

Mickey Barboi 2016-05-05 12:06:35

"It is also cumbersome to check the possibility of error at each and every"

I read that as "it is cumbersome to write correct code." As someone who codes from the hip a lot, I love the rigid way golang handles things like this :)

Jose Luis Vazquez 2016-05-10 11:59:37

Cool to know! Also these "fibers" they seem to perform really well. Still this is a library, would prefer the JVM to "bite the bullet" and admit this abstraction is superior to plain OS threads in most cases, providing support from the runtime.

Jose Luis Vazquez 2016-05-05 19:28:11

"Golang has a garbage collection but this is not a full GC as in Java, there is no memory compaction."

That might be true today, but might not be so tomorrow. Go already does "move memory" changing pointers. It does so since the move to contiguous stacks.

As said before goroutines are basically lightweight contexts managed by the go runtime on top of OS Threads. Each goroutine has a stack that starts small (and many times may not grow much).

When the 2K are too little, the go runtime kicks in and: 1) Gets a new stack of double the size from the heap. 2) Copies the old stack to the new stack BY FIXING the pointers within it, thanks to the fact that the GC is precise in Go, so it knows any piece of memory in the stack whether it is a pointer or not. 3) Releases the old stuck from the heap (or waits for GC to do it probably) 4) The goroutine continues execution on the new stack and ALL the pointers in it have been moved properly.

Peter Verhas 2016-05-03 09:06:35

Thanks! I did not know that. But does not surprise me. It is coherent with the style of the language.

Jose Luis Vazquez 2016-05-05 19:15:40

I would not compare goroutines to threads the same way I would not compare threads to processes.

Goroutines are VERY lightweight, they start at 2K. You can have literally tenths of thousands or more without exhausting your laptop memory. And all those on top of just a handful of OS threads, maybe just about the same number of cores your machine has. Behind the scenes the Go runtime does a lot of epoll & goroutine context switching for you.

With goroutines the debate on the thread pool size is over for most projects. (Still goroutines are cheap but not free, so it always pays off to think a bit to use them wisely instead of falling into overuse.)

Timo Reimann 2016-05-06 21:41:28

"It is easy to miss some error condition and write [code] that just ignores the error."

Quite the contrary, the code example you’ve given is not even valid: It won’t compile since you missed to store the second return value of the Open() call in a variable. This is Go’s way of telling you that you must either handle all return values or make a deliberate decision to discard some by using the blank identifier "_". Have a look yourself: https://play.golang.org/p/YmwzOjA3I9

I recommend reading Dave Cheney’s blog posts on error handling in Go, starting with http://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right .

Peter Verhas 2016-05-07 11:36:56

You are right. I fixed that example not to give the reader a syntactically wrong sample. I admit I fixed it there, it was wrong. Thanks for that.

As for the article you referenced. This is a well written article that I can summary: Exceptions are not perfect in C++ and in Java therefore better we do not use anything like that in Go.

Without starting arguing on one side or the other: there are lots of articles about Go having no exceptions. People got used to it programming in Java, C++, Swift (after 2.0), Python, C# and in other languages. Despite of all imperfections it proved to be a useful construct and programmers miss it. Lack of exceptions in Go (in addition to generics) is actually a great barrier stopping programmers getting closer with Go.

Peter Verhas 2016-05-05 20:19:23

Thanks for the additions. I was not aware that goroutines are not equivalent to threads. What I experienced though that a computeintensive multi-gorutine application performed the best when the number of go routines were the same as the number of cores in the machine.

Adam 2016-05-05 22:40:36

Lightweight threads in Java: http://docs.paralleluniverse.co/quasar/

Adriano Fabris 2018-02-05 16:14:46

I don’t understand people who thinks that writing code like this is readable.

NOT READABLE AND CONFUSING:

"if a == nil { fmt.Println("a is nil") } else { fmt.Printf("%d\n", a.a) }"

READABLE AND LOGICAL:

if a == nil {fmt.Println("a is nil")} else {fmt.Printf("%d\n", a.a)}

Same people who think first code is right way to do are people who don’t see how languages like C++ are overcomplicated.

Peter Verhas 2018-02-05 16:59:20

What is readable and what is not readable very much depends on the reader. We have different brains, we have different experiences. The code that is readable for one person may not be readable for the other.

As the industry stands these days the style you presented as "not readable and confusing" is the defacto standard and it means that really many people think that for them it is readable and not confusing. You can still, however, code in a different style. The consequence is that fewer number of people will be able to easily read your code and thus the maintenance cost will be higher (something that is rare costs higher). At the same time, you may also face finding fewer job possibilities because of your personal style diverting from the industry standard.

If you raised your concerns at the start of the 1970s then perhaps the coding standard would be different today. It is similar to QWERTY keyboard. They may not be optimal if we started from scratch, but I do not think I could learn to type on something else after 35 years of using it.

Anurag 2018-04-12 14:46:53

Hi Peter, Thanks for this great article. To be honest with you I have not read your article but I want to ask you about golang security. I understand that golang is easier than java but Is golang as secure as java or not? For example in case of socket programming and multiplayer online game.

Please give your valuable thoughts and opinion.


Comments

Please leave your comments using Disqus, or just press one of the happy faces. If for any reason you do not want to leave a comment here, you can still create a Github ticket.