Myths of Golang
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
chan
that 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.