13 Shades of Golang – Things you need to know before starting development in Go


When I first encountered with the Golang, the first thing came to my mind is why a company like Google created a programming language which just skips lots of the cool OOP terms we are using. Does not it just back to the past in the era of structural programming?

Well, after trying to find the reason and working with the language, my answer is – simply no, some points it actually back to future, ( old CSP style concurrent programming ).

In this post, I would like to point out some points which I think a newcomer to the realm of Golang will find a little bit awkward and make bad assumptions on the language. And even some cases, you may disappoint after reading the article. But to be more precise, everything has both good-parts and bad-parts, and sometimes you need to understand the reason for choosing a tool, why a new language is come in alive.

“There is no elixir to rescue from every problem, you need to be optimistic on choosing the technology and sacrifice some parts which are not important.”

Shade 1: There is no class in Golang, only struct is real

  • Go does not provide class but it supports struct
  • struct is a value type (created on the stack)  and can implement methods to add the behaviors
  • In this point, Go can be treated as a light version of Object Oriented Programming Language (OOP)
  • Yes it does have encapsulation and type member functions
  • Go playground: https://play.golang.org/p/_2O8GFtlaZe
package main

// Author: mainul098@gmail.com
import "fmt"

// User struct. Only fields that define the state
type User struct {
  Name string
  Email string
}

// Method declaration. User is the recciver type for the method
func (u User) Notify() error {
  // Do stuff to send notification
  fmt.Printf("Send email to : %s", u.Email)
  return nil
}

func main() {
  // Declare a variable of type user set to its zero value.
  var u User

  // Display the value.
  fmt.Printf("Default user value: %+v\n", u)

  u.Name = "Mainul"
  u.Email = "mainul098@gmail.com"

  // Display the value.
  fmt.Printf("Value after assigned : %+v\n", u)

  // Call the Notify method
  u.Notify()
}
Default user value: {Name: Email:}
Value after assigned : {Name:Mainul Email:mainul098@gmail.com}
Send email to : mainul098@gmail.com

Shade 2: No constructor

  • Golang does not provide any contractor to create an instance.
  • In Go perspective, constructor hides the creation of an instance.

Shade 3: No inheritance, only composition

Shade 4: No function overloading, you ain’t gonna need it

  • Function overloading (also method overloading) is a programming concept that allows programmers to define two or more functions with the same name and in the same scope.
  • Golang takes the integrity and consistency issue in a different level.
  • One function should only do one thing and the signature should focus on the functionality it contains.
  • If there is anything that changed the functionality, it should be explicitly defined by the function signature.

Shade 5: No private or public identifier, use the naming convention to enforce data encapsulation

  • In Go, the case of the first letter for variables, structs, fields, functions, etc. determines the access specification.
  • Use a capital letter and it’s public, use a lowercase letter and it’s private.

Shade 6: No abstract struct (and no class), only interface

  • Go does not provide an abstract struct type, the only way to make abstraction is to use interface
  • The interface in golang is powerful yet simple to declare.
  • Only method signatures are permittable in the interface declaration.

Shade 7: Interfaces are satisfied implicitly, rather than explicitly

  • Go is unique when it comes to how we implement the interfaces we want our types to support.
  • There are no traditional keywords like implements in Golang.
  • Go does not require us to explicitly state that our types implement an interface.
  • If every method that belongs to an interface’s method set is implemented by our type, then our type is said to implement the interface.
  • Go playground: https://play.golang.org/p/0EwsqIn3TTi
// _Interfaces_ are named collections of method signatures.

package main

import "fmt"
import "math"

// Here's a basic interface for geometric shapes.
type geometry interface {
  area() float64
  perim() float64
}

// For our example we'll implement this interface on `rect` and `circle` types.

type rect struct {
  width, height float64
}

type circle struct {
  radius float64 
}

// To implement an interface in Go, we just need to
// implement all the methods in the interface. Here we
// implement `geometry` on `rect`s.
func (r rect) area() float64 {
  return r.width * r.height
}

func (r rect) perim() float64 {
  return2*r.width +2*r.height
}

// The implementation for `circle`s.
func (c circle) area() float64 {
  return math.Pi * c.radius * c.radius
}

func (c circle) perim() float64 {
  return2* math.Pi * c.radius
}

// If a variable has an interface type, then we can call
// methods that are in the named interface. Here's a
// generic `measure` function taking advantage of this
// to work on any `geometry`.
func measure(g geometry) {
  fmt.Println(g)
  fmt.Println(g.area())
  fmt.Println(g.perim())
}

func main() {
  r:= rect{width: 3, height: 4}
  c:= circle{radius: 5}
  // The `circle` and `rect` struct types both
  // implement the `geometry` interface so we can use
  // instances of
  // these structs as arguments to `measure`.
  measure(r)
  measure(c)
}
{3 4}
12
14
{5}
78.53981633974483
31.41592653589793

According to Go specification: The semantics of interfaces is one of the main reasons for Go’s nimble, lightweight feel.

Shade 8: Go has both functions and methods

type User struct {
    Name string
    Email string
}

// Function declaration
func Notify() error { 
 // To stuff to send notification
 return nil
}

// Method declaration. User is the recciver type for the method
func (u User) Notify() error {
  // To stuff to send notification
  return nil
}

Shade 9: There are both value and pointer semantics, but everything is passed by value

  • Go support both value and pointer semantics.
  • Value semantics will create a new copy of the variables on scope switching.
  • Pointer semantics will copy the address of the variable and shared the address (pointer) on scope switching.
  • But strictly speaking, there is only one way to pass parameters in Go – by value.
  • Every time a variable is passed as a parameter, a new copy of the variable is created and passed to called function or method. The copy is allocated at a different memory address.
  • In case a variable is passed by pointer, a new copy of the pointer to the same memory address is created.

https://play.golang.org/p/G8YH2NQSX6V

package main

import "fmt"

func incrementSendValue(num int) {
  num++
  // Value is copyed and a new memory is allocated and a new memory is allocated in the Stack
  fmt.Println("incrementSendValue:\tValue Of[", num, "]\tAddr Of[", &num, "]")
}

func incrementSendPointer(num *int) {
  *num++
  // Value of the address is copyed and a new memory is allocated in the Stack
  fmt.Println("incrementSendPointer:\tValue Of[", num, "]\tAddr Of[", &num, "]\t Value at the Addr[", *num, "]")
}

func main() {
  num := 10
  incrementSendValue(num)
  fmt.Println("incrementSendValue:\tValue Of[", num, "]\tAddr Of[", &num, "]")

  incrementSendPointer(&num)
  fmt.Println("incrementSendPointer:\tValue Of[", num, "]\tAddr Of[", &num, "]")
}
incrementSendValue:  Value Of[ 11 ]  Addr Of[ 0x10414024 ]
incrementSendValue: Value Of[ 10 ]  Addr Of[ 0x10414020 ]
incrementSendPointer:   Value Of[ 0x10414020 ]  Addr Of[ 0x1040c130 ]    Value at the Addr[ 11 ]
incrementSendPointer:   Value Of[ 11 ]  Addr Of[ 0x10414020 ]

 Shade 10: No while or do..while loop, only for loop

  • In the specification, Golang makes the decision to be consistent and simple.
  • There is no while or do.. while to loop through the collections.
  • NOTE: for loop provides both value and pointer semantics. 
  • Go playground: https://play.golang.org/p/7uIvPaNrkQ4
package main

// Author: mainul098@gmail.com

import (
  "fmt"
)

func main() {
  nums := []int{1, 3, 4, 5, 6}
  // Value semantics. Value will be copied to a new variables each time
  for i, v := range nums {
    fmt.Println("Value Semantics:\tValue Of v : ", v, "\tAddr Of v : [", &v, "]\tAddr Of nums[", i, "] : [", &nums[i], "]")
  }

  // Pointer semantics.
  for i := 0; i < len(nums); i++ {
    fmt.Println("Pointer Semantics:\tValue Of nums[", i, "] : ", nums[i], "\tAddr Of nums[", i, "] : [", &nums[i], "]")
  }
}

Shade 11: No generics

  • This is arguably the most controversial design decision of Go.
  • Generics are convenient but they come at a cost in complexity in the type system and run-time.
  • Go designers decided to just skip it.
  • Further Reading: https://golang.org/doc/faq#generics

Shade 12: No try/catch or exception handling, programmers are responsible to make a reliable software

  • Be careful about exception handling vs error handling, exception handling is introduced in other languages (i.e. Java, C#..)  to make application reliable.
  • But it introduced complexity and sometimes hide the future issues.
  • Exception handling also makes a bypass for the function and make a different flow for the uncertain state.
  • Golang’s error handling relies on explicit status codes. To separate status from the actual result of a function, Go supports multiple return values from a function.
  • Golang has also “error” interface to make exception handling a regular flow of functions.
package main

import (
  "fmt"
  "errors"
)

func div(a, b float64) (float64, error) {
  if b == 0 {
    return 0, errors.New(fmt.Sprintf("Can't divide %f by zero", a))
  }
  return a / b, nil  
}

func main() {
  result, err := div(8, 4)
  if err != nil {
    fmt.Println("Oh-oh, something went wrong. " + err.Error())
  } else {
    fmt.Println(result)
  }

  result, err = div(5, 0)
  if err != nil {
   fmt.Println("Oh-oh, something went wrong. " + err.Error())
  } else {
    fmt.Println(result)
  }
}
2
Oh-oh, something went wrong. Can't divide 5.000000 by zero

Shade 13: Declared named variables must be used in the scope

If you assign the error to a variable and don’t check it, Go will get upset.

func main() {
  e := bar()
}
main.go:15: e declared and not used

There are ways around it. You can just not assign the error at all:

func main() {
  bar()
}

Or you can assign it to the underscore:

func main() {
  _ = bar()
}

In Conclusion

This article is intended to provide a better understanding of the topics a new Golang developer may be encountered and may misunderstand the parts.

Go took the best parts of OOP, left out the rest and gave us a better way to write polymorphic code. – William Kennedy

Go Further

Leave a comment