Main package
By convention, the package name is the same as the last element of the import path. For instance, the “math/rand” package comprises files that begin with the statement package rand.
A name is exported if it begins with a capital letter, i.e. Pizza
func add(x int, y int) int {
return x + y
}
// or
func add(x, y int) int { // input params shortened
}
// multiple results
func swap(x, y string) (string, string) {
return y,x
}
// naked return, return named return values
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
// notice, return has empty clause, naked
}
Function Literal is an anonymous function
Closure is a function enclosing values outside of its scope.
func (input) output {}
// for example
import "http"
/*
This function converts a function fn to http.HandlerFunc.
I have a fn and want to convert to http.HandlerFunc.
*/
func makeHandler(fn func(w http.ResponseWriter, r *http.Request, extra string)) http.HandlerFunc {
return func (w http.ResponseWriter, r *http.Request) { // return is a function literal
exta := "123" // do some interception / AOP with exta
// is the construction of extra uses values outside of the Function Literal, then it is also a Closure.
fn(w, r, extra)
}
}
Variable can be at function package level.
package main
import {
"fmt"
}
var c, python, java = true, false, "No!"
func main() {
var i int = 1
fmt.Println(i, c, python, java)
}
// use := for short assignment declaration in side function inplace of var and no need for TYPE!
func main() {
var i, j int = 1, 2
k := 3
fmt.Println(i, j, k)
}
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
0 for numeric values
false for bool and
“” for string type
Need explicit conversion
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// Or, put more simply:
i := 42
f := float64(i)
u := uint(f) // need to have uint and float64, otherwise runtime error
Either by := or by var with initial value
One can also use new to create an instance of some type T
var someTypeInstance = new(T)
var someSlice []int = new([]int)
With const
Constants can NOT be initialized with :=
Numeric constants are high precision values
package main
const (
win = 100
good = 60
)
func someFunc() {
//xxx
}
func main() {
sum:=0
for i:=0;i<10;i++ {
sum += 1
}
fmt.Println(sum)
}
// initial value and step value are optional
for ;sum<1000; {
sum += sum
}
// While style for loop
for sum < 1000 {
sum += sum
}
// loop forever
for {
}
if x < 0 {
}
// If can take a short statement before condition check
if v:= math.Pow(x, n); v<lim {
return v // v in the statement scope only in the scope
}
return lim // v is no longer available.
switch os:=runtime.GOOS; os {
case "darwin": {
}
default: {
}
}
// no break needed, it will stop on a successful match
// switch no condition
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
Executed until surroundings are finished. (like finally)
Deferred function calls are pushed onto a stack.
var i *int
fmt.Println(*i) // dereferencing
*i = 21 // indirecting
a collection of fields
type Vertex struct {
X int
Y int
}
v:=Vertex{1, 2}
v.X
v := Vertex{1,2}
p := &v
(*p).X = 123
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
array is fixed size
[n]T // n values of type T
var a [10]int
primaries := [3]int {2, 3, 5}
Copy array would copy every element of the array.
So, if the element is value type, it copies the value, if the element is pointer, it copies the pointer (memory address)
// !! note the type of the array includes both the length and the data type of the array.
// So you cannot assign [5]int to [6]int
valueArray1 := [5]int{}
var valueArray2 [5]int = valueArray1
refArray1 := [...]*string{new(string), ...}
var refArray2 [5]int = refArray1
Pass array with function calls, use array pointer type instead of array type
// array type includes length, so must specify length in the
// array parameter definition
func arrayFunc(someArray *[1000]string) {
}
Slices are dynamically sized. Use built-in function make to create a slice
someSlice := make([]string, 5)
// or
someSlice2 := make([]string, 5, 6) // len and cap
// or slice literal, no value for len
someSlice3 := []string{"a", "b"}
arr := []int{1, 2, 3}
slice := arr[:] // create a slice from array
A slice does not store any data, it just describes a section of an underlying array.
Changing the elements of a slice modifies the corresponding elements of its underlying array.
Other slices that share the same underlying array will see those changes.
var a []T
a[low : high]
a[low : high: capacity_index]
// len = high - low
// cap = capacity_index - low
// Note capacity_index must be < capacity of a, otherwise runtime error
primaries := [6]T{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
// slice literals
q := []int{2, 3, 5, 7, 11, 13}
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
}
// slice defaults
a[0:10]
a[:10]
a[0:]
a[:]
A slice has both a length and a capacity.
len(s)
cap(s)
// if len is larger than capacity, it will be runtime error
s1 := []string{}
s2 := []string{}
append(s1, s2...)
// with ... you can append another slice to one slice.
The length of a slice is the number of elements it contains.
The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.
zero value of slice is nil, which as len and cap of 0 and no underlying array
var nilSlice []int
if nilSlice == nil {}
Built-in function make to create dynamic size arrays
a := make([]int, 5) // len(a) = 5
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b) = 5
b = b[1:] // len(b) = 4
Slices can contain any type including other slices
func append([]T, vs... T) []T
// remove last slice is
slice = slice[:len(slice)-1]
Range in a for loop will give index and copy of the value
:= is like in or enumerated() in python
var pow = [10]int{1, 2, 4, 8, 16}
for idx, val := range pow {
fmt.Printf("2**%d = %d\n",idx, val)
}
// other variations
for _, val := range pow {}
for idx, _ := range pow {}
for idx := range pow {}
Zero value of map is nil
var m map[string]Vertex
var m = make(map[string]Vertex)
// initialize a map of given type ready to use
// map literal
var mapLiteral = map[string]Vertex {
"bell labs": Vertex {123.5, 234.6},
"Google": Vertex {11.3, 22.3},
}
// you can omit the type literal if top level type is just a type name
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
m[key] = elem
elem = m[key]
delete(m, key)
elem, ok = m[key]
// if key not in m, elem will be nil and ok = false
// if not declared
elem, ok := m[key]
Functions are values too. They can be passed around just like other values.
Function values may be used as function arguments and return values.
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
// input param fn is of type func(float64, float64) float64
Go functions can be closures
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return b
}
}
Go does not have classes. However, you can define methods on types.
A method is a function with a special receiver argument.
The receiver appears in its own argument list between the func keyword and the method name.
import (
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
// Abs() is a method on Vertext instance
v:=Vertex{3, 4}
v.Abs()
// Method is just a function with receiver argument
You can declare a method on non-struct types, too.
You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float(f)
}
Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
! Methods with value receiver can NOT modify the value.
So, with value type, the object is copied, but if the method needs to mutate the object, it has to be type pointer
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
Function with pointer only takes pointer parameter (value input will cause error)
Method with pointer can take both pointer and value parameter
However, if a method is defined on value receiver, the receiver is ALWAYS copied
but if the receiver is a pointer, the receiver is shared (no copy).
func (v *Vertex) Scale(factor int) {
// takes both pointer and value
// if called with value, Go interpreter will call it with pointer (&v).Scale(factor)
}
func Scale(v *Vertex, factor int) { // receiver is shared
// only takes
}
Similarly, a method’s receiver is value, it can be called with either value or pointer; a function called on a value can only be called on value (if called on pointer, it will be runtime error)
Choosing pointer over value is to
An interface type is a set of method signatures
A value of interface type can hold any value that implements those methods.
type Abser interface {
Abs() float64
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// Both MyFloat and *Vertex implements Abser interface
Method set is defined as a set of methods associated with type of values or pointers. The type of receiver will determine whether this method is associated with value, pointer or both.
Method Receivers | Interface Value or the type value |
---|---|
(t T) | T and * T |
(t *T) | * T |
So, if you define interface method on value type, then both value and pointer types have it.
However, if the interface method is on pointer type, only the pointer type has it, you cannot pass the interface value parameter with a value type but only a pointer type
type MyType struct {
}
type MyInterface interface{
func someMethod() string
}
func (i *MyType) someMethod() string {}
func someCallsite(mi MyInterface) {}
// you cannot call someCallsite with MyType
// you can only call someCallsite with *MyType
// i.e. someMethod(&myTypeValue)
No “implements” keyword, so no explicit declaration of intent.
Under the hood, interface values can be thought of as a tuple of a value and a concrete type:
(value, type)
Nil receiver of interface value will degrade gracefully
However, if an interface of nil (not interface value, aka the concrete type implementing the interface) is called on a method, it trigger runtime error.
type I interface {
M()
}
type V struct {
S string
}
type (v *V) M() {
fmt.Println(v.S)
}
func main() {
var i I
describe(i)
i.M() // runtime error
i = &V{"some_string"}
i.M() // valid
var j *V
j.M() // ok
var k V
k.M() // error, cause V does not implement I
}
interface {}
An empty interface may hold values of any type
Type assertion provide access to interface value’s underlying concrete value.
Note that the variable i must be interface type, not value type; otherwise it will be error.
t := i.(T)
// if i is not of type T, it will trigger a panic
// to avoid this.
t, ok := i.(T) // ok is assertion success
// if ok == false, t will be zero value of type T
This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.
func do(i interface{}) {
switch v := i.(type) { // use type keyword instead of T
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
One of the most ubiquitous interfaces is Stringer defined by the fmt package.
type Stringer interface {
String() string
}
A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values.
Raw string literal is between (back quotes)
var rawString string = `"this is ", 'raw', "string literal"`
built in error type, similar to Stringer
type error interface {
Error() string
}
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
This is to add functions, properties to an existing type to 1. reuse properties and 2. override behaviors. It works like super class with overridden methods
func ()
type user struct { // inner type
Name string
Email string
}
type admin struct { // outer type
user // embeded type
Level string
}
ad := admin{xxx}
ad.user.notify()
Implementation of the inner type is promoted to the outer type if outer type does not implement the interface itself.
However, if outer type implements the interface, if outer type value is passed to a function, the outer type implementation will be used instead of inner type’s. In this case, (similar to subclass overriding), the inner type implementation is not promoted.
Apply visibility rules
In addition to atomic and sync lib, you can use channel for synchronization
atomic.LoadInt64, atomic.storeInt64, atomic.addInt64 from “sync/atomic”
mutex = sync.Mutex from “sync” with mutex.Lock() and mutex.Unlock()
runtime.Goshed() to schedule back to the queue.
someBufferedChannel := make(chan string, buffersize)
someUnbufferedChannel := make(chan int)
// sending to channel (non-blocking)
someBufferedChannel <- "someStarterString"
// sending to channel (blocking)
someUnbufferedChannel <- 10
// unbuffered channel is waiting(blocked) for the shared variable
someInt := <- someUnbufferedChannel
// buffered channel does not block unless empty
someString := <- someBufferedChannel
close(someBufferedChannel)
close(someUnbufferedChannel)
When close a channel, no more sending but only receiving is allowed.
If a channel is closed and empty, it will immediately return zero-value of the shared variable and state of false
select {
case <- someUnbufferedChannel: {xxx}
case <- someBufferedChannel: {xxx}
default: {xxx}
}
select statement lets a gorouting wait on multiple communication operations and blocks until one of the cases can run. If there are multiple, it will choose one randomly.
If select clause has default, then unbuffered channel is not blocking. If unbuffered channel is empty or not ready, it will immediately use default statement.
Buffered Channel is not blocking usually, so if there is a valid item in the channel or the channel is closed, it will immediately return. However, if channel is not closed and it’s empty, it will be blocking and wait. In this case, with default clause, it will not wait and use default statement immediately.
For both buffered and unbuffered channels, if default clause is there, open empty channels will always go to default immediately.
For both buffered and unbuffered channels, if channel is close, it will not go to default and ok is false.
select {
case _, ok := <- bufferedChan:
if ok {
// open, non-empty
} else {
// closed
}
default:
// open empty
// if no default clause for open empty, it will be blocking!!
}
select {
case _, ok := <- unbufferedChann:
if ok {
// open, non-empty
} else {
// closed
}
}
default:
// open, empty
use := range ch to iterate over a channel. If channel is nil, it will block forever. Once channel is closed, it will return; otherwise, the for loop will block indefinitely.
for item := range someChannel {
// will block if channel is empty
}
// will return when channel is closed.
sync.WaitGroup is to hold the program until all the go routines are finished, by waitGroup.Add() and waitGroup.Done() and waitGroup.Wait()
We can achieve the same behavior with a buffered Channel if we have a known fixed number of goroutines.
func testBufferedChannelWaitGroup(t *testing.T) {
var maxGoroutineCount = 5
waitGroupChannel := make(chan int, maxGoroutineCount)
for i := 0; i < maxGoroutineCount; i++ {
go func(tag int) {
fmt.Println("Starting work with tag: ", strconv.Itoa(tag))
Wait(100, 200)
waitGroupChannel <- 1
fmt.Println("Finished work with tag: ", strconv.Itoa(tag))
}(i)
}
for i := 0; i < maxGoroutineCount; i++ {
<-waitGroupChannel
fmt.Println("[Channel] did drain one channel item")
}
fmt.Println("WaitGroupChannel work all DONE!")
}
use panic and receover to throw a panic and catch it at runtime without crashing the application.
Note that recover is only allowed in deferred function
func Do() {
panic("Something went wrong!")
}
func SafelyDo() {
defer func() {
if err := receover(); err != nil {
log("something went wrong: %v", err)
}
}()
Do()
}
len does not work for slice pointer, it only works for array, array pointer and slice but not slice pointer.
arr := [...]int{1, 2, 3} // [...] is only for array literals, not function argument
sli := make([]int, 2, 3)
l = len(arr) // ok
l = len(&arr) // ok
l = len(sli) // ok
l = len(&sli) // panic!
!! Make sure to use pointer receiver for writing methods (changing fields)
Receiver of a method is like a parameter that’s passed by value.
So, if a writing method changes the field/object property, it only changes the copy of the object if passed by value, not the original object!
Only pointer receiver can modify the object it points to. (value receiver cannot)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Printf("v is %v \n", v)
}
type A struct {
a string
}
type B struct {
A
b string
}
bb := B{a: "a", b: "b"} // ERROR: a is embedded, so cannot use literal
// must be
bb := B{b: "b"}
bb.a = "a"
reflect.IsNil() only works for chan, func, interface, map or slice or pointer. It does NOT work for i.e. string, int or other custom object. (it will cause panic.)
Named return is usually used in defer clause where you want to return something else but you cannot really put “return” because it will just return the goroutine.
Note:
func SomeFunc() (string, error) {
var someKey string
var someError error
defer func() {
if r:=recover(); r!=nil {
someKey = ""
someError = r.(error)
}
}()
return // must have return here even with named return
}
# must be in the package directory
> go test packagename # this is to test all the test files in the package.
> go test some_test.go # run test again
> go test # test all the test files in the current dir (??)
> go test -run '' # Run all tests.
> go test -run Foo # Run top-level tests matching "Foo", such as "TestFooBar".
> # This is to run one specific test function.
> go test -run Foo/A= # For top-level tests matching "Foo", run subtests matching "A=".
> go test -run /A=1 # For all top-level tests, run subtests matching "A=1".
if Main function is specified, it will be run instead of other tests.
func TestMain(m *testing.M) {
// do set up and tear down
t.Run("group", func(t *testing.T) {
t.Run("test1", parallelTest1)
t.Run("test2", parallelTest1)
t.Run("test3", parallelTest1)
})
}
func new(Type) *Type
Loop:
for i:=0;i<3;i++ {
switch {
case 1:
xxx // no need for break
case 2:
xxx
break Loop // <- break the outer loop
}
}
func SomeFunc(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err==nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
// here, it will return n and err (named)
}
p := new(SyncedBuffer) // p type *SyncedBuffer
var v SyncedBuffer // v type SyncedBuffer, not pointer
make is only for map, slice and channels
It returns an initialized instance. i.e. slice, zero value is nil as slices need all 1. type, 2. len and 3. cap to be fully initialized.
s := new([]int) // return nil, zero-valued
ss := make([]int, 10, 10) // returns empty slice
switch(a.(type)) {
default: // as go needs no break
case int:
case string:
case *bool:
}
happens beforeis a partial order of execution of memory operations in a Go program.
a = 1
b = 2
// you may expect b = 2 called before a = 1
Goroutine Creation
The go statement that starts a new goroutine happens before the goroutine’s execution begins. (So is the next operation of the goroutine creation)
var a string
func Do() {
print(a)
}
func main() {
a = "helloworld!"
go Do()
}
! The print function will happen sometime in the future, possibly after the program returns
Goroutine Destruction
The exit of a goroutine is not guaranteed to happen before any event in the program
var a string
func hello() {
go func() { a = "hello" }()
print(a)
}
The goroutine assignment may not happen before print. Some aggressive compiler may even
Channel Communication
ch := make(chan int, 10)
var a string
func Do() {
a = "hello world!"
ch <- 1
}
func main() {
go Do()
<- ch // !! this is blocking
print(a) // guaranteed to print hello world!
}
ch:=make(chan int)
var a string
func Do() {
a = "hello world!"
<- ch
}
func main() {
go Do()
ch <- 1
print(a) // guaranteed to print hello world!
}
More generally, The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes.
Locks
for both sync.Lock and sync.RWLock, if n < m, then n_th Unlock() happens beforem_th Lock() returns
var l sync.Mutex
var a string
func Do() {
a = "hello world!"
l.Unlock()
}
func main() {
l.Lock()
go Do()
l.Lock()
print(a) // guaranteed to print hello world!
}
More generally, n_th Unlock() happens before (n+1)_th Lock()
Once
A single call from Once happens (returns) before any call of Once returns.
var a string
var once sync.Once
func setup() {
a = "hello!"
}
func doprint() {
once.Do(setup)
print(a)
}
func main() {
go doprint()
go doprint()
}
Assignment is in Once and happens before print (in one goroutine), so all Once happens before print, so it will print hello! twice!
General Rule Of Thumb
Without anything from sync or sync/atomic, there is no particular guaranteed ordering between different goroutines!
Use, Mutex, Condition, Once, and preferred Channel to ensure synchronization!
Incorrect Synchronization
var a, b int
func f() {
a = 1
b = 2
}
func g() {
print(b)
print(a)
}
func main() {
go f() // write
g() // read
}
There is no particular ordering (even in f(), b=2 can happen before a=1), so it’s possible to print 2 then 0 (or two 0’s or 1, 2 or 2, 1, or 1, 0 or 2, 0 or 0, 1 or 0, 2)
var a string
var done bool
func setup() {
a = "hello, world"
done = true // these two can change order
}
func doprint() {
if !done { // 1
once.Do(setup) // 2, when one doprint reaches here, the other can be at 1, 2, or 3
}
print(a) // 3
}
func twoprint() {
go doprint()
go doprint()
}
Since there is no guarantee in the order of the two assingments in setup(), it can print one empty string and one “hello world!”
(one does a setup with done=true first and before a is set, the other goroutine checks done and does not do setup and prints a before the first goroutine finishes setting a)
var a string
var done bool
func setup() {
a = "hello, world"
done = true
}
func main() {
go setup()
for !done {
}
print(a)
}
Again, since the ordering in setup() is not guaranteed, so it may print empty string. In some extreme case, compiler may remove done=true in setup because there is no synchronization between the two goroutines, main may never finish.
type T struct {
msg string
}
var g *T
func setup() {
t := new(T)
t.msg = "hello, world"
g = t
}
func main() {
go setup()
for g == nil {
}
print(g.msg)
}
More practically
type T struct {
msg string
}
var g *T
func setup() {
t := new(T)
t.msg = "hello, world"
g = t
}
func main() {
go setup()
for g == nil {
}
print(g.msg)
}
In setup, g assignment and t.msg assignment are not guaranteed to be in this order. So, it’s possible to print empty string!
Conclusion, when multiple Goroutines communicate with each other (having dependency, sharing objects, passing messages), it’s better to use synchronization from sync package than using if/for with flag checks (which has no guarantee of synchronization).
Reference
Specs
https://golang.org/ref/spec#Introduction