In this Go part 2 series, we’re going to focus on OOP and a cool feature Go has which is the defer keyword and use-cases of it.
If we start saying it straight from the start — Go isn’t an OOP language.
That implies that Go doesn’t have objects, inheritance, polymorphism, and the entire toolset of cool features coming with OOP.
The reason for that as far as I’ve understood from the Go community and from Go itself is again for having simplicity.
OOP in itself is an art if you ask me.
I saw many people and myself implementing cool designs that sometimes could give the opposite meaning from what it meant in the first place.
After experiencing Go, in my opinion, I think they’ve made something great that takes every time the best of every aspect, and I think they did that as well in this aspect which we’re going to see right now.
No OOP kind of?…
In Go, we have like in C/C++ the struct type.
Struct allows us to define under a sole entity a group of variables and no more.
Wait.. what? What about methods?
Well, we can’t code methods inside a struct, but we do can extend the struct behavior like this.
So the best practice is to create a Go file like person.go and place the struct definition with the functions extensions inside of it, cool right?
But you might be in terror right now and saying where are our big known 3 — polymorphism, inheritance, and encapsulation?!
Well, we don’t have those but we do have composition and also the empty interface which we will see in a bit.
Composition & empty interface
Let’s say we want to build a hierarchy in which we have a class Employee and a person, and of course, as in any programming language, we want Employee to use the functions which Person already has, due to being relative to it.
In Go, we can put inside a struct a definition of another struct as a property. This way we composite the Person as a property inside the Employee.
As you can see it almost acts as the known struct or objects we have in C++, Java, or any other OOP languages.
As well, we can see that for each function we have the instance reference of the calling object. This way we have reference to the relative object that called this function and the relative properties of it.
Another important aspect regarding polymorphism while we are at it is the empty interface, which is declared as follows:
As you can see from the above example, the empty interface allows us some kind of abstraction regarding the data we hold and how we pass it on inside our application.
Defer keyword
Something cool I’ve found at the start when I started reading Go code is the defer keyword.
It looked like a really a mystical or magical word that does something and I don’t understand.
In order to describe how it works, let’s use the famous mutex entity.
For the ones who don’t know about it, mutex allows sharing between multiple threads an entity that allows locking a section of code.
That lock helps to make a code section be executed only one at a time from start until the end, so there wouldn’t be cases of accessing a list and writing to it simultaneously for example.
Usually, the use of mutex is done by calling Lock() on the start and Unlock() on when the critical code section has ended.
What if you would have seen this? What is your first response?
I guess a scene from a movie when seeing magic for the first time, would be the most accurate one.
Basically defer is a built-in keyword in Go that helps us to execute commands in reverse sequential flow, after the function has ended.
Has ended? reverse sequential flow?! Huh?!
A little reminder, when a function exists it basically goes back in the stack to the place it was called in.
When that operation is happening, we also call the defer commands, but we call them in the flow of LAST-IN-FIRST-OUT so as you can understand, each defer commands gets pushed to a stack-like structure and pops out one after one when the function finished.
You can think of the defer commands like another function that holds all of them, and that function is executed once the main function has ended, so it basically allows us to do clean-up work in a cleaner way for our open resources — files, sockets, mutex and more…
Do you remember the time, we started working with files or sockets, and we started to have like 5 times Open() on a file, buffered-file, and many many more abstractions when at the end needed a Close() function?
For cases like these and similar to them, we have the new kid of the block which is deferred.
Recovery over exception
As you already might guess, in Go we don’t have the infamous try-catch clause.
We do have the option to execute a defer function at the start of our function and do as follows:
The use of recover function is actually allowing us to receive the thrown panic inside a function, in order for us to keep our program to smoothly keep executing and properly logging about a problem if needed.
As you can see, it’s not a try-catch but it’s some kind of alternative to use when panic is called, so if for example, you wish to “throw an exception” you can call panic and catch the value of it, which afterward we can wrap it in a new value to return like error codes which we’ve done in C.
In conclusion
As you can see, Go has pretty cool features that resemble OOP in some cases.
As well we saw the defer keyword which is useful in cleaning up resources, and a use case for using it as a way of dealing with errors in a more elegant way.
If you had a great time reading this piece then I’m very happy about that, and I can recommend you reading the №3 series if you want.
The next chapter is talking about the concurrency model of Go and how to communicate between threads.