Get Going with Go! : Part 1
Mandar Samant
Head Customer Engineering, Digital Natives- Google Cloud India Pvt.
Get Going
It is about 33 years since I first dabbled with BBC Basic on a BBC Micro computer, adding about 8 more programming languages to the list since then, with some serious projects in C++ and C#. Now that I have given away my age, I might as well declare that it is bit tough for me to learn a new language, programming or otherwise. So when I started to tinker around with Go (or Golang) it was to get a 10k feet view and then move on to the next shiny thing. But after few sketchy programs later I grew, well, Curiouser and Curiouser and decided to dive into the Golang rabbit hole. Ability to build tight standalone executables, web assembly support, command line programs + websites/APIs was too shiny to ignore.
This 2 part series is a short collection of my notes, surprises and some resources I referred to as I burrowed in. These cover the nuances of the programming language and the unlearning one needs to do to understand the Golang philosophy and the syntax, esp. when you come in from 'C++ type' language background.
Note that the articles are not intended to be Golang tutorials, more of a pre-read before diving into Golang. So read on if you are a programmer considering to adopt Go or a solution architect considering Go to be part of your polyglot programming strategy.
OOPs!! What happened here..
The first surprise came along when I realized that Golang had no class construct, in fact the the language vocabulary has no 'class' keyword. Coming from the traditional OOPs background this sounded like... sacrilege. In traditional OOPs paradigm, the 'Object' relates to both state and behavior, but Golang seemed to have taken a radically different approach by separating behavior from state at the time of declaration . This 'surprise' pushed me into drilling down deeper, 'How can an object oriented language, which recently celebrated 12 years of open source popularity, not just survive but thrive without supporting classes'?
Go takes a very different approach and gets the job done using good old (C style) structs and interfaces. This is probably the biggest 'recalibration' you need to do coming in from classic C++/Java/C# world. But doesn't this lack of class construct further imply no class inheritance, no private/public object state, no statics, and what about polymorphism?
The authors of Golang (?Robert Griesemer,?Rob Pike, and?Ken Thompson ) decided to implement these constructs in their own unique way ( and yes, Ken Thompson is the Unix pioneer, if you are still wondering.). While structs encapsulate the state, behavior is added to the structs by attaching functions, from specific interfaces or standalone, to a struct. Functions 'receive' the struct, typically a pointer to the struct, on which it operates. Unlike C#/Java classes the standalone Golang struct syntax is unaware of interfaces it will implement when it is declared. Interface methods are attached by their signature and not via the interface they belong to.
Object inheritance is achieved via composition, both structs and interfaces can have base structs/interfaces as members. Golang structs allows anonymous members, the container struct/type 'inherit' the base/anonymous member type's interface. The code snippet below demonstrates the concepts.
/*
Base Vechicle interface and struct
*/
//Base struct
type Vehicle struct {
? ? VehicleType ? ?string
? ? IsALandVehicle bool
? ? IsAnAirVehicle bool
}
// Interface for Base struct to implement
type IVehicle interface {
? ? GetIfLandVehicle() bool
? ? GetIfAirVehicle() bool
}
//Base struct implementation if IVehicle:GetIfLandVehicle. No mention of IVehicle!
func (MyVehicle *Vehicle) GetIfLandVehicle() bool {
? ? return MyVehicle.IsALandVehicle
}
//Base struct implementation if IVehicle:GetIfAirVehicle
func (MyVehicle *Vehicle) GetIfAirVehicle() bool {
? ? return MyVehicle.IsAnAirVehicle
}
//======================
// DERIVED STRUCT
type Truck struct {
? ? Vehicle ? ? ? ? ? //Base struct as Anonymous member: base struct interface implementations are inherited by derived structs
? ? CommercialVehicle //Another anonymous member: Second base struct
? ? NoOfAxles ? ? ? ? int
? ? LoadCapacity ? ? ?int
}
/* Derived ITruck interface extends IVehicle via composition*/
type ITruck interface {
? ? IVehicle //anonymous member, refers to the base interface
? ? GetLoadCapacity() int
}
//struct implementation of ITruck:GetLoadCapacity
func (MyTruck *Truck) GetLoadCapacity() int {
? ? return MyTruck.LoadCapacity
}
//Derived class overriding via ITruck->IVehicle::GetIfLandVehicle(), method signature should match
func (MyTruck *Truck) GetIfLandVehicle() bool {
? ? return false
}
//Standalone Method attached to Truck
func (MyTruck *Truck) Honk() {
? ? fmt.Println("Truck is honking\n")
}
Lack of static members/constructors is bit of a challenge in Golang, as a workaround Go offers a 'wellknown' init() function in your packages ( which are rough equivalents of a namespaces ) and the runtime will invoke init() function just once, when the package is initialized as the program is executed. It acts like pseudo static constructor for the whole package. This feature needs to be used carefully as the Go compiler allows multiple init() functions in the same source file (!!!!) and the runtime executes all of them in sequence. Other option, when appropriate, is to use the sync.Once() to wrap a call to a function to initiate state, the wrapped function is called only once in the application's lifetime.
One major feature gap which has been called out for long is the missing support for 'generics' in Golang. This is about to get fixed in upcoming release (v1.18). Introduction of generics will have a large impact on existing third party open source packages and Golang standard libraries. In the code snippet below (from the referenced generics tutorial ) the SumIntOrFloats function sums the members of a 'map' collection of either int64 or float64 values. Without support for generics, this would have needed two separate functions, one to add up ints and other for floats.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
? ? var s V
? ? for _, v := range m {
? ? ? ? s += v
? ? }
? ? return s
}
Tutorial available at the link below introduces the new generics feature.
It is interesting to note that Golang has adopted the square brackets "[]" to represent the parameterized Type ( K and V above) instead of angle brackets "<>" adopted by Java, C# and C++ templates. Makes it difficult to visually parse the code between array definitions, map members and generic type parameter declarations.
Adopting Golang generics in your projects makes lot of sense going forward. I also expect many OSS packages will be rewritten for Go v1.18+ with generic support.
领英推荐
Given these differences in approach to OOP a broader question bothering me was around modeling popular OO design patterns in Golang and found the "Go Design Patterns" training Linked Learning, very useful.
Now, on to the next surprise.
*Pointers are back*
Like most modern languages, Golang programs execute in a memory managed/garbage collected environment, so it was a surprise to see explicit pointer support. When calling methods Go supports pass-by-value semantics i.e. a copy or the parameter is passed to the function. Basic types, including arrays, are passed by value i.e. modifications to the parameters in the function do not affect the original. Go supports 'pass-by-pointer' semantics where the caller sends in a 'pointer' to a variable to a function and the function can modify the parameter values. The pointer feature is pretty smart and it ensures that it doesn't get in the way of garbage collection or lead to orphan objects/memory. In Golang (like C) you can also just assign a variable to type, if you want the new allocated variable to be 'zeroed' you should use the new (T ) syntax which allocates zeroed storage for a new item of the type and returns its address. For slices ( which are 'windows' over classic arrays), maps and channels (more on this later) Go offers the make() function ( e.g. make([] int, 10, 100) which initiates these objects and returns a pointer. Like other managed runtimes there is no delete (ptr) to 'free' the allocated memory as the Go Garbage collector takes care of it.
Concurrency with Goroutines
Writing scalable, bug free multithreaded programs has always been a challenge. I recollect quite a few nightmares when debugging multithreaded 'server' code we had written in C++ (and even C#). The challenges arise due to steep conceptual learning curve and lack of simplified constructs in the programming language chosen. Golang addresses these problems in the form of 'Goroutines' . 'Goroutines' offer a simplified code syntax and are implemented as user level threads i.e. 'virtual/green threads' managed by the Golang scheduler which multiplexes them over OS threads. This light weight approach allows Go programs to fork multiple goroutines without overwhelming the OS/microprocessor.
Equally interesting is the syntax which makes writing ' Goroutines' easy to code and understand. A "go" prefix forks off a function as a goroutine, "channels" are used to signal between goroutines i.e. by sending and receiving data between routines. A 'select' construct, whose syntax resembles a 'switch' statement, is used to monitor multiple channels for incoming signals.
Controlling the Goroutine sprawl can be a challenge, so a standard 'Context' is offered which can be passed down to forked Goroutines, typically as the first parameter to the Goroutine, which enables the parent to cancel or set a execution timeout for the forked goroutines.
The code snippet below demonstrates the concepts above.
..
//Function to be called as goroutine
func RunPeriodic(ctx context.Context, pokeChan chan bool, msg string) {
? ? var ctr int = 0
? ? t := time.NewTicker(1 * time.Second) //timer to send us ticks every 1 second
? ? defer t.Stop()
? ? //Wait for any of the signals
? ? for {
? ? ? ? select {
? ? ? ? case <-t.C: // WAIT for signal from our ticker
? ? ? ? ? ? fmt.Printf(msg, ctr)
? ? ? ? ? ? ctr++
? ? ? ? case <-pokeChan: //OR message coming in from our pokeChannel- ignore the value
? ? ? ? ? ? fmt.Printf("Got Poked\n")
? ? ? ? case <-ctx.Done(): // OR when ctx is timed out
? ? ? ? ? ? fmt.Println("Timeout received, exiting.")
? ? ? ? ? ? return
? ? ? ? }
? ? }
}
func main() {
? ? //create a context with timeout
? ? ctx := context.Background()
? ? ctx, cancel := context.WithTimeout(ctx, 15*time.Second) // Give it 15 seconds
? ? defer cancel()
? ? //create a channel to poke go routine
? ? pokeChan := make(chan bool)
? ? go RunPeriodic(ctx, pokeChan, "Hello, World - %d!\n") // Call as a goroutine
? ? fmt.Scanln() ? ? // Press ENTER to poke the goRoutine
? ? pokeChan <- true //send 'true' over the channel will be picked by goroutine
? ? fmt.Scanln() ? ? //wait for key stroke to exit
}
If you want to go deeper into Goroutines the Go scheduler: Implementing language with lightweight concurrency talk by Dmitry Vyukov is a good reference to understand what goes behind the scenes.
Do note that while Golang makes things easier, it is no substitute for discipline needed while writing concurrent code.
End of Part 1
This brings us to end of Part 1. We covered some of the unlearning and learning one needs to do to orient oneself with Go's OOPs implementation and how Go makes multi-threaded programming simpler.
In the second part we will discuss other interesting language artefacts in Go as we burrow further into the rabbit hole! Keep learning!
[Disclaimer : The views expressed are my own. Some external links may be behind pay walls.]
Head Customer Engineering, Digital Natives- Google Cloud India Pvt.
2 年Case studies about Go adoption in Google's ecosystem https://opensource.googleblog.com/2020/08/new-case-studies-about-googles-use-of-go.html
Director-Solution Specialists @ Microsoft | Generative AI I Speaker | Cognitive |Cloud, Digital Transformation| Passionate Community Worker | Freelance Journalist | DEI and Women@ Leader
2 年And with that I just gave away my age too ;-)
Director-Solution Specialists @ Microsoft | Generative AI I Speaker | Cognitive |Cloud, Digital Transformation| Passionate Community Worker | Freelance Journalist | DEI and Women@ Leader
2 年I remember using BBC “box” with a TV :-) Simple programs and colours
Principal @ AWS | 8x AWS | 3x Kubernetes | TOGAF | PMP | ex-IBM,TCS,Wipro
2 年Thanks for sharing.very nice read and gave great insight on get Go... Ing