Gyh's Braindump

Go Syntax

tags
Programming Languages, Go
source
Tour of Go

1 Types

1.1 Pointer

  • zreo value is nil
  • no arithmetic operations
  • * to obintain value, & to get address of a variable
i, j := 42, 2701
p := &i   // point to i, or =var p *int = &i=
*p = 21
p = &j
*p = *p / 37

1.2 Struct

  • colelction of fields
  • pointer to struct can access field without dereference: (*p).X -> p.X
  • initialization can use named field: v = Vertex{X: 1}
type Vertex struct {
	X int
	Y int
}
func main() {
	v := &Vertex{1, 2}
	v.X = 4
}

1.3 Array

  • var a[10]int declare an int array
  • array can not be resized
var a[2]string
a[0] = "Hello"
a[1] = "World"
primes := [6]int{2, 3, 5, 7, 11, 13}
// or primes := [...]int{2, 3, 5, 7, 11, 13}

1.4 Slice

  • zero value is nil

  • declare: letters := []string{"a", "b", "c", "d"}

  • can created by func make([]T, len, cap) []T, cap is optional, default same with len

      var s []byte
      s = make([]byte, 5, 5)
      // s == []byte{0, 0, 0, 0, 0}
    
  • slice can be “slicing” of a slice or an array: a[1:3], half open a slice doesn’t store data, only a reference to orginal array, change data will affect array

  • capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice

      func main() {
        s := []int{2, 3, 5, 7, 11, 13}
        printSlice(s)                              // len=6 cap=6 [2 3 5 7 11 13]
    
        // Slice the slice to give it zero length.
        s = s[:0]
        printSlice(s)                              // len=0 cap=6 []
    
        // Extend its length.
        s = s[1:4]
        printSlice(s)                              // len=3 cap=5 [3 5 7]
    
        // Drop its first two values.
        s = s[2:]
        printSlice(s)                              // len=1 cap=3 [7]
      }
    
      func printSlice(s []int) {
        fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
      }
    
  • Growing capacity

    1. manually

      1. create a new slice with new capacity
      2. copy elements one by one
           t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
           for i := range s {
               t[i] = s[i]
           }
           s = t
      
    2. use func copy(dst, src []T) int

           t := make([]byte, len(s), (cap(s)+1)*2)
           copy(t, s)
           s = t
      
  • Append data use func append(s []T, x ...T) []T append adds data to a slice and increase capacity if necessary

      a := make([]int, 1)  // a == []int{0}
      a = append(a, 1, 2, 3)  // a == []int{0, 1, 2, 3}
      aa := []string{"John", "Paul"}
      b := []string{"George", "Ringo", "Pete"}
      aa = append(aa, b...) // equivalent to "append(aa, b[0], b[1], b[2])"
      // aa == []string{"John", "Paul", "George", "Ringo", "Pete"}
    
  • Some usecases

    • use in passing arguments, to avoid coping whole array

    • filter, declare an empty slice and append data to it

          // Filter returns a new slice holding only the elements of s that satisfy fn()
          func Filter(s []int, fn func(int) bool) []int {
              var p []int // == nil
              for _, v := range s {
                  if fn(v) {
                      p = append(p, v)
                  }
              }
              return p
          }
      
    • replace array to save memory

          func CopyDigits(filename string) []byte {
              b, _ := ioutil.ReadFile(filename)
              c := []byte
              c = append(c, digitRegexp.Find(b))
              return c
          }
      

      If just return digitRegexp.Find(b), the array b will not be released.

1.5 Range

iterate over a slice or map

  • slice two values are returned: index and a copy of element, the second can be omitted
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
	v += 1
	fmt.Printf("2**%d = %d\n", i, v)
}

1.6 Map

map keys to values

  • zero value is nil, no keys and nor can keys be added
  • create
    1. use make to create a map

           type Vertex struct {
             Lat, Long float64
           }
      
           var m map[string]Vertex
      
           func main() {
             m = make(map[string]Vertex)
             m["Bell Labs"] = Vertex{
                 40.68433, -74.39967,
             }
             fmt.Println(m["Bell Labs"])
           }
      
    2. or use literal to create a map

           var m = map[string]Vertex{
             "Bell Labs": Vertex{40.68433, -74.39967,},
             "Google": {37.42202, -122.08408,},
           }
      
           func main() {
             m["1"] = Vertex{1, 2}
             fmt.Println(m)
           }
      
  • operations
    • insert or update: m[key] = elem
    • retrieve: elem = m[key], if not exist, return key type’s zero value
    • test if exist: elem, exist := m[key], if exist, exist==true
    • delete: delete(m, key)

1.7 Function Values

functions are also values which can be passed as arguments or assined to variables

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

1.8 Function Closure

A closure is a function value that references variables from outside its body

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

1.9 Methods

  • Go doesn’t has classes, but we can define methods on types.
  • A method is a function with a spectial receiver argument.
  • Only types defined in the same package can have methods, which excludes build-in types and types defined in other packages.
  • Define methods of built-in types: type NewType float64
type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}

This is equal to:

func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
  • Use pointer receivers *T passing reference of the same object to functions to directly modify the same object

  • Methods with pointer receivers can eithered be called with a variable or a pointer

      var v Vertex
      v.Scale(5)  // OK
      p := &v
      p.Scale(10) // OK
    
  • Methods with value receivers also take either a value or a pointer

1.10 Interfaces

  • A set of method signatures
  • A interface value can hold any type value thant implements those methods Interface vlaue can be considered as (value, type), holding a value of a specific concrete type
  • Not like methods, pointer and value are not automatically converted in interface
type I interface {
	M()
}

type T struct {
	S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I = T{"hello"}
	i.M()
}
  • Interface value can have nil underlying value and can call methods, concrete type’s nil value can not

      type I interface {
        M()
      }
    
      type T struct {
        S string
      }
    
      func (t *T) M() {
        if t == nil {
            fmt.Println("<nil>")
            return
        }
        fmt.Println(t.S)
      }
    
      func main() {
        var i I
    
        var t *T
        i = t
        i.M()      // output: <nil>
        // T.M()   // compile error
      }
    
  • Call a nil interface value will cause runtime error

  • Empty Interface An empty interface can hold values of any type Usually used where need to handle values of unknown type

      func main() {
        var i interface{}
        describe(i)
    
        i = 42
        describe(i)
    
        i = "hello"
        describe(i)
      }
    
      func describe(i interface{}) {
        fmt.Printf("(%v, %T)\n", i, i)
      }
    
  • Type assertions

    • t := i.(T)
      • if i holds a T, return that value
      • if not, panic
    • t, ok := i.(T)
      • if i holds a T, return that value and true
      • if not, return zero value and false
  • Type switch

      func do(i interface{}) {
        switch v := i.(type) {
        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)
        }
      }
    
      func main() {
        do(21)
        do("hello")
        do(true)
      }
    

Stringers

defined by fmt package

type Stringer interface {
    String() string
}

type implements String method can print custom strings by fmt.Println

1.11 Errors

  • error is a built-in interface
type error interface {
    Error() string
}
  • nil error denotes success; non-nil denotes failure
type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", float64(e))
}

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}
	...
	return z, nil
}

1.12 Readers

io.Reader interface has a Read method: func (T) Read(b []byte) (n int, err error) it will return io.EOF when stream ends

1.13 Goroutines

A goroutine is a lightweight thread, running in the same address space go f(x, y, z) starts a new goroutine

  • evalution of arguments are completed in the current goroutine excution happens in the new goroutine

1.14 Channels

Like pipe, receive and send values use channel operator <-

  • Created before use: ch := make(chan int, <buffer size>)

  • By default, receive and send block until the other side is ready usually used to sync routines

      // get sum of a array using two routines
      func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
            sum += v
        }
        c <- sum // send sum to c
      }
    
      func main() {
        s := []int{7, 2, 8, -9, 4, 0}
    
        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c // receive from c
    
        fmt.Println(x, y, x+y)
      }
    
  • Channel can be closed by sender Receiver can get channel status by v, ok := <- ch

  • Loop for i:= range c receives values until c is closed

      func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
            c <- x
            x, y = y, x+y
        }
        close(c)
      }
    
      func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        for i := range c {
            fmt.Println(i)
        }
      }
    
  • Select lets a goroutine execute a case which is ready, or pick one randomly if all cases are ready if default case exists, Select will execute it when no channels are ready

      func fibonacci(c, quit chan int) {
        x, y := 0, 1
        for {
            select {
            case c <- x:
                x, y = y, x+y
            case <-quit:
                fmt.Println("quit")
                return
            }
        }
      }
    
      func main() {
        c := make(chan int)
        quit := make(chan int)
        go func() {
            for i := 0; i < 10; i++ {
                fmt.Println(<-c)
            }
            quit <- 0
        }()
        fibonacci(c, quit)
      }
    

1.15 Mutex

  • Lock and Unlock in sync.Mutex
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
	c.mux.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	c.v[key]++
	c.mux.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock so only one goroutine at a time can access the map c.v.
	defer c.mux.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

2 Flow Control

2.1 For

  • {} is always required
  • init and post statements are optional
for i := 0; i < 10; i++ {
	sum += i
}
for ; sum < 1000; {
	sum += sum
}
for sum < 1000 {
	sum += sum
}
for {
}

2.2 If

  • {} is always required
  • support one statement before the condition
if v := math.Pow(x, n); v < lim {
    return v
} else {
    fmt.Printf("%g >= %g\n", v, lim)
}

2.3 switch

  • break is not needed in each case
  • case can be any value
  • case evaluated from top to bottom
  • can without a condition, equivalent to switch true
switch os := runtime.GOOS; os {
case "darwin":
    fmt.Println("OS X.")
case "linux":
    fmt.Println("Linux.")
default:
    // freebsd, openbsd, plan9, windows...
    fmt.Printf("%s.\n", os)
}

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.")
}

2.4 defer

  • defer excuation of a function call until the surrounding function ends
  • arguments in the function call are evaluated immediately
  • deferred functions are pushed into a stack, and execuated in a first-in-last-out order
func main() {
	defer fmt.Println("2. world")
	defer fmt.Println("1.  ")

	fmt.Println("0. hello")
}
func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)  // print as 9\n 8\n ...., 0
	}

	fmt.Println("done")
}