Myths of Golang

Kuroun Seung
7 min readJul 20, 2020

--

from rubyist perspective

Everything in go pass by value!!

Go variable without defining

  • if it is integer it will be zero by default
  • if it is boolean it will be false by default
  • if it is string it will be empty string

Naked return

  • Normal return
func Hello() (string) {
hello := "hello"
return hello
}
  • Naked return
func Hello() (hello string) {
hello = "hello"
return
}

Variable with Initializer

We can omit the type if declaring variable with initializer var i, j = 1, 2

Short Variable Declaration

s := ”foo”is not available outside function

Multiple Variables

var a, b, c string can be declared with factored block

Type Conversion

Need to be in this expression T(v), otherwise it will raise error

Constant

Normally starts with small letter. The capital letter variable usually is for its visibility. To export constant, start with Capital letter.

**Cannot be declared with short declaration :=

Function Packages

Go package controls function visibility. The function starts with capital letter is available outside the package. Otherwise, it is available inside the package only.

Dependency Management

Go module — go.mod

To create mod file:

go mod init

Command to cache package locally:

go mod tidy

Go does not support OVERLOADINGGGG. That makes sense!

Conditional Statements

  • for loop and if statement has no parentheses
  • for loop init and post expression is option, so it will become while loop technically
  • Variable can be declared in if statement, like ruby but its scope is within if statement
  • Switch without condition is like switch true

defer

defer is like a final keyword in other languages — is executed after all surrounding functions are execute. Multiple defer is like the stack — execute from bottom to the top.

defer always run before panic

A use case: use defer recovery() to catch the panic or error.

Pointer

  • pointer instantiate: var p *int
  • pointer passing to a function func foo(p *int){...}
  • print address: fmt.Println(p)
  • print value: fmt.Println(*p)
package mainimport (
"fmt"
)
func main() {
x := 1
var p *int
p = &x
fmt.Println("value:", x)
fmt.Println("address:", &x)
fmt.Println("address:", p)
foo(&x)
}
func foo(x *int){
fmt.Println("print value inside function:", *x)
fmt.Println("print address inside function:", x)
}value: 1
address: 0xc000094010
address: 0xc000094010
print value inside function: 1
print address inside function: 0xc000094010

with variable a, define the value the following ways are equivalent the same:

a = 10 
*(&a) = 10

Struct Embedding — No Inheritance!

Go has no concept of inheritance, but it has struct embedding (type embedded)

Pointer to Struct

Be cautious on how we change attributes of object as struct type in method — go is a “pass by value language”, The method will modify the copy of object value, not the original object. To get the desired effect, we needs to add * in receiver initializer. For NOT working example:

package main

type writer struct{
first string
last string
}

func (a writer) fullName() string{
return a.first + " " + a.last
}

// func (a *writer) changeName(first, last string){
func (a writer) changeName(first, last string){
a.first = first
a.last = last
}

func main(){
a:= writer{
first: "Jon",
last: "Dew",
}
fmt.Println(a.fullName())
a.changeName("Jonny", "Dew")
fmt.Println(a.fullName())

}

Reference: LinkedIn Learning — Hands-On Introduction: Go

To access field of struct via pointer, we don’t have to have *

Slice

  • When slice is passed to a function, it is passed as copy of the slice. any modification in the function does not affect original slide.
  • To modify of the slice in the function, it need to pass slice pointer instead
  • nil slice is not empty slice. Empty slice can grow, but nil slice cannot grow
  • string is a readonly slice of bytes. This way of design, it is very efficient to have substring from a string. They both safely point to same array

Array and Slice

  • Array has fix size, but slice has dynamic size
  • Slice is the reference to underlying array. If changes the slice value, the underlying value will change too
  • Slice or array value can be struct too
  • Slice length and capacity are different. Slice length is slice length. Slice capacity is underlying array length

CAP rule

Slice can be sliced these rule:

  • slice from lower bound, capacity will be decrease
  • slice from upper bound, capacity will be the same
  • with append function, cap is not reliable. It depends on new allocated underlying array length. It could never be equal to length of slice

Range

  • Range form iteration over slice or map
  • for with range, the first element is index of slice. The second element is value of slice

Map

  • Map declare with var is not usable. it is a nil map. The usable map is declared with make
  • comma ok style _,ok := is used to lookup key of a map

Function value

Function can be a value too. It can pass to another function as parameter

Closure

  • Function can return a closure. Closure is a function value (function) that references variable outside its body

Method

  • Method is a special function with receiver as a type (struct)

Pointer receiver

use pointer receiver when:

  • You want to modify receiver in the method
  • More efficient not to copy value on each method call in the case large struct

Function and Pointer

  • Function with pointer argument must take pointer otherwise will return error.
  • Function with value argument must take value otherwise will return error.

Method and Receiver

Method with pointer or value receiver can take both value or pointer as receiver when they are called.

Interface

  • Interface is a set of method signatures. Each method can have multiple receivers
  • Types are required to implement all the methods in the interface — no partial implementing. For example, if there are 3 methods in the interface. Type has to implement all the 3 methods, otherwise, it will raise error.

Type assertion

— To assume interface hold value of specific type. If it is not what we assume, it will raise error.

Function argument as empty interface has no type information. function can accept anything.

func aFunctionName(val interface{}){
}

To define variable as empty interface, we can use any keyword.

val.(type) is used for type assertion — type can be string for example.

var i any = "hello"
fmt.Println(i.(string))
// output => hello

Go Concurrency

  • Goroutines: functions that is executed concurrently. function to execute independently from main thread. Be aware of: if main thread is stopped. The routines are stopped too.
  • Channels: what to be shared/accessed between routines
  • Channel Direction: the restriction of channel to send/receive value. By default channel has bi-direction. for example:
    restrict to only send value (write only): c chan<- int
    restrict to only receive value (read only): c <-chan int
    can both send/receive: c chan int
  • Select: switch case for channel
  • Unbuffered vs Buffered Channels: the key difference is that unbuffered channels provide synchronous communication, blocking until the sender and receiver are ready, while buffered channels provide a degree of asynchrony by allowing a certain number of values to be in transit before blocking occurs. The choice between buffered and unbuffered channels depends on the synchronization requirements of your program.
  • If we don’t close buffer channel when max range is reached, it will cause error as main thread fall asleep waiting channel to pass value
  • Select statement usage with Channel is used to select chanthat passes the value to main thread first
package main

import (
"fmt"
"time"
)

func main() {
// Create two channels
ch1 := make(chan string)
ch2 := make(chan string)

// Goroutine 1: Sends a message to ch1 after 2 seconds
go func() {
time.Sleep(2 * time.Second)
ch1 <- "Hello from Goroutine 1!"
}()

// Goroutine 2: Sends a message to ch2 after 4 seconds
go func() {
time.Sleep(4 * time.Second)
ch2 <- "Greetings from Goroutine 2!"
}()

// Use select with default case to check for messages without blocking
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
fmt.Println("No message received yet.")
time.Sleep(1 * time.Second)
}
}

// Output:
// No message received yet.
// Hello from Goroutine 1!
// No message received yet.
// Greetings from Goroutine 2!
}

String vs []byte

  • string is immutable — more memory storing
  • []byte is mutable — more efficient, in-place modifying

ioutil.ReadAll

avoid using it reading request body. It may cause a memory leak with a large request body. Instead, use a decoder that filters out data and it is more efficient and concise.

Rune type

Rune is like a character data type in other language. However it is the integer represents the unicode code point.

References

--

--