Skip to content

Latest commit

 

History

History
3666 lines (2730 loc) · 92.5 KB

golang.org

File metadata and controls

3666 lines (2730 loc) · 92.5 KB

A tour of Go - Russ Cox

Go is a fast, fun and productive open source language built by Google.

package main

import "fmt"

func main() {
  fmt.Printf("Hello, world!")
}

Here, 🔝 the program is in the package named “main” We import the package named “fmt” via it’s path Finally, we declare a function named “main”

All go programs start in package “main” and the function “main”

Interfaces

A type implements a interface by defining the required methods. So, printf we saw earlier can be used like so:

fmt.Printf(“Hello, %s”, “world”)

The function Printf takes an argument that need not be a string. Printf defines that %s can be used to define any value that has a method named “String” with no arguments that returns a string

So:

package main

import "fmt"
type World struct{}

func (w *World) String() string {
  return "world"
}

fmt.Printf("Hello, %s", new(World))

How does this work? Package fmt defines an interface called Stringer. The interface says that any value that has a “String” method without any arguments, would cause %s to call that method to obtain the value to print on the screen.

The name suffix “er” is a convention - used when you have simple interfaces with one method, you get the interface name by taking the method name and adding an “er”

Eg: reader, marshaler etc

Another example:

type Office int
const (
    Boston Office = iota
    NewYork
)

func (o Office) String() string {
    return "Google, " + officePlace[o]
}

func main() {
    fmt.Printf("Hello, %s", Boston)
}

Here, Office is of type int. We define new constants called Boston, NewYork with values 0, 1 etc We also have an array officePlace which has the office names. Something like: [“Boston”, “New York”, ]

We can print weekdays like so:

package main
import (
    "fmt"
    "time"
)

func main() {
    day := time.Now().Weekday()
    fmt.Printf("Hello, %s (%d)", day, day)
}
// Hello, Saturday (6)

Here, day is an instance (?) Of Weekday which implements the “String” method

The actual code for Weekday:

package time
type Weekday int

const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

var days = [...]string {
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday",
}

func (d Weekday) String() string { return days[d] }

So, we have constants “Sunday” (which translates to 0) etc

Another example: time.Duration

See how nice it to write these constants since Duration has a String method

package time
type Duration int64

const (
    Nanosecond Duration = 1
    Microsecond = 1000 * Nanosecond
    Milisecond = 1000*Milisecond
)

// let's see it in action
package main

func main() {
    start = time.Now()
    fetch("http://google.com")
    fmt.Println(time.Since(start))
}

Here, since time.Since returns Duration which implements the String method, we can print it. Also, the String method of Duration has logic in it to return the unit of time as ms, ns, s, m, h (whatever is appropriate)

Note, these types (Office, Weekday, Duration etc) don’t include the “implements” declarations - nowhere they say that they implement the fmt.Stringer interface - they just do it - they might not even know it exists!

This also have the advantage that you can notice patterns and then write interfaces to describe them without having to go back in the package and annotate their types (like in Java, you have to include the “implements” keyword)

This is how many of the interfaces in the Go standard libarary - eg: io.Writer This interface, we can say from the convention is the interface with only one method - the Write method

// here, we declare the interface
type Writer interface {
    Write(p []byte) (n int, err error)
}

// example usage
fmt.Fprintf(os.Stdout, "Hello World!")

// You can also do this
h := crc32.NewIEEE()
fmt.Fprintf(h, "Hello World")
fmt.Printf(h.Sum32())

Here, the return value of crc32.NewIEEE() implements Write method and hence, we can write to it using Fprintf method

We can also implement writers that write writers Example: io.MultiWriter

package io

// MultiWriter creates a writer that duplicates its writes to all the provided writers, similar to Unix tee command
// here, the function name is MultiWriter, it takes many "writers" (0 or more) which are of type Writer and it returns a single Writer
func MultiWriter(writers ...Writer) Writer {
    return &multiWriter{writers}
}

type multiWriter struct {
    writers []Writer
}

// now, we have to implement MultiWriter's Write method so that it loops over the many writers in it's list and calls their individual Write method
func (t *multiWriter) Write(p []byte) (n int, err error) {
    for _, w := range t.writers {
        n, err = w.Write(p)
        if err != nil {
            return 
        }
        if n != len(p) {
            err = ErrShortWrite
            return 
        }
    }
    return len(p), nil
}

// example usage
h := crc32.NewIEEE()
w := io.MultiWriter(h, os.Stdout)
fmt.Fprintf(w, "Hello World!")

One more example of Writer.

We have package hex which provides a function Dumper - if you call Dumper with a Writer w, it returns a hex dump of all written data to w. The format of the dump matches the output of `hexdump -C` on the command line

func Dumper(w io.Writer) io.WriteCloser {

The function Dumper returns WriteCloser which writes output in hex to w. We also need the Closer method to indicate all data has been written

package main

import (
    "encoding/hex"
    "fmt"
    "os"
)

func main() {
    h := hex.Dumper(os.Stdout)
    // here, we queue a h.Close call to run when main returns
    defer h.Close()
    fmt.Printf(h, "Hello World")
}
// <prints the hex dump>

We also have a Reader interface. Folks who implement it - os.File, bufio.Reader, net.Conn, compress/gzip, crypto/tls, bytes.Buffer etc

We can also use “%v” to print “values” without implementing String explicitly

type Lang struct {
    Name string
    Year int
    URL string
}

func main() {
    lang := Lang{"Go", 2009, "http://golang.org"}
    fmt.Printf("%v", lang)
}
// {"Go", 2009, "http://golang.org"}


// note the braces to indicate the struct

How did this work? We did not implement the String method for the Lang struct type. This takes to our second topic - Reflection

Reflection

Go has support for reflection on types and values

Reflection means the go runtime gives you any type’s basic information and basic operations on those types available at runtime

Look at this example:

func main() {
    fmt.Print("Hello", 42, "\n")
    // Print is just like Printf but without the format string. It just uses %v for printing each argument

// Hello 42
}

Here, “Print”, how does it know that the first argument is a string and has to be printed as such and that the second argument is an int and has to be converted to string and printed - using Reflection!

We can write our own implementation of Print called myPrint

func main() {
    fmt.myPrint("Hello", 42, "\n")
}

func myPrint(args ...interfaces{}) {
    for _, arg := range args {
        switch v := reflect.ValueOf(arg); v.Kind() {
            case reflect.String:
                os.Stdout.WriteString(v.String())
            case reflect.Int:
                os.Stdout.WriteString(strconv.FormatInt(v.Int(), 10))
        }
    }
}

Here, we simply use reflect.ValueOf to get the type information and then do use the appropriate way to print the value of that variable

All this uses reflection. We can use reflection to print valid json as well: json.Marshal function

type Lang struct {
    Name string
    Year int
    URL string
}

func main() {
    lang := Lang{"Go", 2009, "http://golang.org"}
    data, err := json.Marshal(lang)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", data)
}

json.Marshal is essentially Printf, but it prints json notation and uses reflection to get the correct json encoding

Similarly, we also have xml.MarshalIndent

This makes the standard library code very nice - and so your programs can also be designed like that

We can also do JSON to Struct

func main() {
    input, err := os.Open("/tmp/foo.json")
    if err != nil {
        log.Fatal(err)
    }
    io.Copy(os.Stdout, input)
}
// io.Copy takes an io.Writer and an io.Reader and copies the bytes. It is like memcopy, but for io

So, we basically have “cat” right now We can convert this to struct as well

func main() {
    input, err := os.Open("/tmp/foo.json")
    if err != nil {
        log.Fatal(err)
    }
    dec := json.NewDecoder(input)
    for {
        var lang Lang
        err := dec.Decode(&lang) // this asks the decoder to parse the json and put it in the format of the Lang struct
        if err != nil {
            if err == io.EOF {
                break
            }
            log.Fatal(err)
        }
        fmt.Printf("%v", lang)
}

Here, we get ourselves a new json decoder (which takes in json and outputs the format we want) which loops over each line and decodes it as Lang struct

We can also go from json to struct to XML

func main() {
    input, err = os.Open('/tmp/lang.json')
    if err != nil {
        log.Fatal(err)
    }
    dec := json.NewDecoder(input)
    for {
        var lang Lang,
        err := dec.Decode(&lang)
        if err != nil {
            if err == io.EOF {
                break
            }
            log.Fatal(err)
        }
    }
    // now from Lang, we can go to XML
    xml_data, err := xml.MarshalIndent(lang, "", " ")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", data)
}

In golang, you can pass functions as arguments to another functions

Another example:

func main() {
    start := time.Now()
    do(func(lang Lang) { //here, the "do" function is something that we defined and which times the time it takes to load the page and the bytes loaded
        count(lang.Name, lang.URL)
   })
   fmt.Printf("%.2fs total\n", time.Since(start).Seconds())
}

// the count function
func count(name, url string) {
    start := time.Now()
    r, err := http.Get(url)
    if err != nil {
        fmt.Printf("%s: %s", name, err)
        return
    }
    n, _ := io.Copy(ioutil.Discard, r.Body) //we copy the body of the response to ioutil.Discard which is /dev/null of writers. But it returns the number of bytes copied, so we get it
    r.Body.Close()
    fmt.Printf("%s %d [%.2fs]\n", name, n, time.Since(start).Seconds())
}

// results:
// Python 19190 [0.52s]
// Ruby 19190 [2.22s]
// Scala 19190 [2.49s]
// Go 19190 [0.49s]
// 5.50s total

Here, we get a very high total time because we are synchronous (each individual page took 2s but total time is 5s)

We can do it in parallel, using concurrency

Concurrent

Parallelism - running multiple things simultaneously Concurrency - way to deal with multiple things simultaneously

Concurrency is the coordination of parallel computations

Go has goroutines to let you run multiple computations run simultaneously And goroutines create parallelism Go also provides channels to let you coordinate these parallel computations by explicit communication

To introduce parallelism, you need to add the word go before the “count” function call

func main() {
    start := time.Now()
    do(func(lang Lang) { //here, the "do" function is something that we defined and which times the time it takes to load the page and the bytes loaded
        go count(lang.Name, lang.URL) // now the call runs in it's own go routine
   })
   time.Sleep(10*time.Second)
   fmt.Printf("%.2fs total\n", time.Since(start).Seconds())
}

Here, we don’t know when the goroutines finish - we need to coordinate that - using channels

func main() {
    start := time.Now()
    c := make(chan string)
    n := 0 // we initialize the counter to 0
    do(func(lang Lang) {
        n++ // we increment the counter here for each language
        go count(lang.Name, lang.URL, c)
   })

   for i := 0; i < n; i++ { // we count upto the counter n to retreive that many messages
      fmt.Print(<-c)
   } // after this loop is done, we know that all the goroutines have completed

   fmt.Printf("%.2fs total\n", time.Since(start).Seconds())

}

func count(name, url string, c chan<- string) {
    start := time.Now()
    r, err := http.Get(url)
    if err != nil {
        c <- fmt.Sprintf("%s: %s", name, err)
        return
    }
    n, _ := io.Copy(ioutil.Discard, r.Body) //we copy the body of the response to ioutil.Discard which is /dev/null of writers. But it returns the number of bytes copied, so we get it
    r.Body.Close()
    c <- fmt.Sprintf("%s %d [%.2fs]\n", name, n, time.Since(start).Seconds())
}

// results:
// Python 19190 [0.52s]
// Ruby 19190 [2.22s]
// Scala 19190 [2.49s]
// Go 19190 [0.49s]
// 2.49s total

Here, we use channels in the count func Here, c is a parameter is a channel of type string The left arrow means that the only allowed operation is sending to the channel We also have Sprintf which returns a string, and we send it to the channel

If we want to give up after 1s and not continue the fetch, we can do use time.After function that takes in a Duration and gives a channel. It also promises that after the Duration has elapsed, it will send the current time to that channel.

So:

func main() {
    start := time.Now()
    c := make(chan string)
    n := 0 // we initialize the counter to 0
    do(func(lang Lang) {
        n++ // we increment the counter here for each language
        go count(lang.Name, lang.URL, c)
   })

   for i := 0; i < n; i++ { // we count upto the counter n to retreive that many messages
      select { // select is like the switch statement but the cases are communication operators
          case result := <-c: // the first case is that we might receive the result from the channel c, in that case we print it
              fmt.Print(result)
          case <- timeout: // the second case is that we might receive the current time from channel timeout, in that case we print Timed out
              fmt.Print("Timed out\n")
              return // we return from main which will exit the program
      }
   }// each time we run the select statement, it blocks until one of the cases can proceed and then it runs that case

   fmt.Printf("%.2fs total\n", time.Since(start).Seconds())
}

func count(name, url string, c chan<- string) {
    start := time.Now()
    r, err := http.Get(url)
    if err != nil {
        c <- fmt.Sprintf("%s: %s", name, err)
        return
    }
    n, _ := io.Copy(ioutil.Discard, r.Body) //we copy the body of the response to ioutil.Discard which is /dev/null of writers. But it returns the number of bytes copied, so we get it
    r.Body.Close()
    c <- fmt.Sprintf("%s %d [%.2fs]\n", name, n, time.Since(start).Seconds())
}

// results:
// Python 19190 [0.52s]
// Ruby 19190 [2.22s]
// Scala 19190 [2.49s]
// Go 19190 [0.49s]
// 2.49s total

Note how there are no conditional variables, no select loops, no epolls, no kqueues, no callback functions, no semaphores, and no mutexes. They exist in the run time but the abstractions that go provides means that you can focus on the job at hand and not these low level details

The Go project:

  • Go 1 released in March 2012
  • 2 separate compilers, coordinated with language spec
    • 1st implementation is called GCcompilers
      • derived from the Plan 9 compiler toolchain
    • 2nd implementation is basically a frontend for the GCC compiler
      • called GCC Go
  • Runs on FreeBSD, Linux, NetBSD, OpenBSD, OS X, Plan 9, Windows

A tour of Go - Official Tutorial

Basics

package main
import (
    "fmt"
    "time"
)

func main () {
    fmt.Println("Hello World")
    fmt.Println("The time is: ", time.Now())
}

Packages, variables, functions

Every go program is a part of a package Programs start running in the main package, from the main method

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(1122)
	fmt.Println("My favorite number is", rand.Intn(100))
}
  • A name is exported if it begins with a capital letter
  • eg: Pizza is exported, pizza is not
  • When importing a package, only the exported names can be accessed from outside

Functions

A function can take 0 or more arguments

func <fn_name>(<arg 1> <type of arg 1>, ..., <arg n> <type of arg n>) <return type> {
 // function body
}

// eg:

func add(x int, y int) int { // since x and y share the same type, we can write this as: func add(x, y int) int {
    return x + y
}

func main() {
  fmt.Println(add(2, 3))
}

Functions can return more than 1 value

func add(str1, str2 string) (string, string) {
    return (str2, str1)
}

func main() {
  a, b := swap("hello", "world")
  fmt.Println(a, b)
}

Return values can be named - at the function declaration step

func split(sum int) (x, y int) {
  x = sum * 4 / 9
  y = sum - x
  return // this is a naked return - it will return x and y as they have been named in the function declaration line
}

Var is used to declare a list of variables - like function argument lists - the type is last A variable can be at the package or at the function level

var a, b, c int

You can also initialize during declaration

var a, b int = 1, 2

If the initialization is present, we can skip type var a, b = 1, “str1”

You can also use := to declare variables - this is a shorthand for using var and type

:= <- this cannot be used outside a function, there we have to use var

Types

Go’s basic types are:

  • 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

int/uint is 32 bit on a 32 bit system, 64 bit on a 64 bit system

You can factor var declarations into a block (just like with import)

package main

import (
	"fmt"
	"math/cmplx"
)

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
	fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe) // T is for type, %v uses refelection to get type at runtime
	fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
	fmt.Printf("Type: %T Value: %v\n", z, z)
// Output
// Type: bool Value: false
// Type: uint64 Value: 18446744073709551615
// Type: complex128 Value: (2+3i)
}

Variables declared without an explicit initial value are given their zero value

  • 0 for numeric type
  • false for boolean type
  • ”” for strings

Type conversions

  • `T(v)` converts value v to type T
var a int = 2
var b float64 = float64(a)
var c uint = uint(b)

// or equivalently 
a := 42
b := float64(a)
c := uint(b)

Go is strongly typed - this means each variable has a predefined type and it cannot refer to something else. So, to make b refer to an int, the int needs to be type casted to float64 or else it won’t work

Type inference When declaring a variable without specifying an explicit type (either := or var = syntax), the variable’s type is inferred from the value on the rhs

var i int
j := i

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

Constants are declared like variables, with the `const` keyword They cannot use the := syntax

package main

import "fmt"

const Pi = 3.14

func main() {
	const World = "世界"
	fmt.Println("Hello", World)
	fmt.Println("Happy", Pi, "Day")

	const Truth = true
	fmt.Println("Go rules?", Truth)
}

Numeric constants

  • they are high precision values
  • an untyped constant takes the type needed by its context
package main

import "fmt"

const (
	// Create a huge number by shifting a 1 bit left 100 places.
	// In other words, the binary number that is 1 followed by 100 zeroes.
	Big = 1 << 100
	// Shift it right again 99 places, so we end up with 1<<1, or 2.
	Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
	return x * 0.1
}

func main() {
	fmt.Println(needInt(Small))
	fmt.Println(needFloat(Small))
	fmt.Println(needFloat(Big))
}
// output
// 21
// 0.2
// 1.2676506002282295e+29

Here, we see that they are printed so as to display their values correctly - this is due to their following the Stringer interface - their string method has logic with switch/case statements displaying the correct format

Flow control statements: for, if , else, switch, defer

Go has only 1 looping construct - the `for` loop It has the usual 3 components:

  • init statement - executed before the first iteration
    • the variables declared here will be only visible in the scope of the `for` statement
    • this is optional
  • condition expression - evaluated before every iteration
  • post statement - executed at the end of every iteration
    • this is optional

No () needed around for, but for the for code block, {} mandatory

package main

func main() {
  for a := 0; a < 10; a ++ {
    fmt.Printf(a)
  }

  for ; sum < 1000; { // if you make both the init and post statement optional, you can drop the semicolons to get the C's while
    sum += sum
  }

  for sum < 1000 { // this is equivalent to :top:
    sum += sum
  }
}

C’s while is spelled for in Go.

Omitting the conditional expression as well gives you an infinite loop for { // code }

If statements are like the `for` statements - no () on if statement, {} mandatory for code block

package main

import (
	"fmt"
	"math"
)

func sqrt(x float64) string {
	if x < 0 { // see ma, no braces!
		return sqrt(-x) + "i"
	}
	return fmt.Sprint(math.Sqrt(x))
}

You can also include a tiny “declaration” statement with an if - just like the init in “for” - the variable declared here will only be accessible in the if block - and also inside any of the `else` blocks

package main

import (
	"fmt"
	"math"
)

func pow(x, y, lim int) int {
  if v := math.pow(x, y); v < lim {
    return v
  }
  return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}

Switch A `switch` statement is a shorter way to write a sequence of `if-else` It runs the first case whose conditional expression evaluates to true

Go’s switch runs only the selected cases, not all cases that follow - so no break needed at end of every case’s code expression Also, the cases need not be constants, and the values involved need not be integers

package main

import (
  "fmt"
  "time"
)

func main() {
  today = time.Now().Weekday()
  switch time.Saturday { // here, we are looking for time.Saturday -> it gives us a time.Weekday
    case today + 0:
      fmt.Println("Today")
    case today + 1:
      fmt.Println("Tomorrow")
    default today + 1:
      fmt.Println("Too far away")
  }
}

We can also use switch without any condition. This is same as `switch true` -> it will execute the first block that evaluate to true This is a clean way to write long-if-then-else chains

package main

import (
  "fmt"
  "time"
)

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

Defer A `defer` statement defers the execution of a function until the surrounding function returns The deferred call’s arguments are evaluated immediately, but the function itself is not executed until the surrounding function returns

package main

import "fmt"

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

We can have multiple defer calls, they are put pushed onto the stack - so they execute LIFO

func main() {
    for a:=0;a<10;a++ {
      defer fmt.Println(a)
    }
	fmt.Println("end")
}
// output
// end
// 9
// 8
// 7
// 6
// 5
// 4
// 3
// 2
// 1

More types: structs, slices, maps

Pointers

Go has pointers. A pointer holds the memory address of a value (a value can be an integer, data structure, anything)

`*T` is a pointer to a `T` value It’s zero value is `nil` - the value it gets after declaration, before initialization

var p *int

To get the pointer to a certain value, use `&T`

a := 42 p = &a

The `*` operator denotes the pointer’s underlying value

fmt.Println(*p) // we get the value pointed to by the pointer p. *<some pointer> gives the value pointed to by the pointer // this is also called dereferencing or indirecting *p = 21 // here we set the value of the memory location pointed to by the pointer to 21

package main

import "fmt"

func main() {
	i, j := 42, 2701

	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 37   // divide j through the pointer
	fmt.Println(j) // see the new value of j
	// output
	// 42
	// 21
	// 2701/37 = 73
}

Struct

A struct is a collection of fields

package main

import "fmt"

// note the syntax: type <variable name> <variable type>
type Vertex struct {
	X int
	Y int
}

func main() {
    v := Vertex{1, 2}
	fmt.Println(v)
	fmt.Println(v.X) // You can access the struct fields using the dot notation
    v.X = 5
	fmt.Println(v)
}
// {1 2}
// 1
// {5 2}

Pointers to structs

We can have pointers to structs as well. In the example above,

package main

import "fmt"

// note the syntax: type <variable name> <variable type>
type Vertex struct {
	X int
	Y int
}

func main() {
    v := Vertex{1, 2}
    ptr_v := &v
    fmt.Println(*ptr_v) // you can print it using
    fmt.Println((*ptr_v).X) // you can access the attributes like so
    fmt.Println(ptr_v.X) // this is also allowed to make it simple to access - recall C has ptr_v->X
}

Struct literals

You can allocate a new struct value by listing the values of its fields

type Vertex struct {
  X, Y int
}

var (
  v1 = Vertex{1, 2}
  v2 = Vertex{X: 1} // Y:0 is implicit
  v3 = Vertex{} // X:0, Y:0 is implicit
  v3 = &Vertex{1, 2} // v3 is a pointer to Vertex (type *Vertex)
)

Arrays

The type `[n]T` is an array of `n` values of type `T`

var foobar[10]int

Here, foobar is an array of 10 integers

Arrays cannot be resized.

package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13} // you can declare and initialize in one go (no pun intended)
	fmt.Println(primes)
}

Slices

Arrays are fixed length but slices are dynamically sized - they offer a flexible view into the elements of an array.

A slice is formed by specifying the 2 indices - a low and high bound

a[low:high]

Low is included, high is not

package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}
	var s []int = primes[1:4] // slices have simlar syntax as arrays
	fmt.Println(s)
}

Slices are like references to arrays - they are just a dynamic resizable sliding window to peek at (and modify) the contents of the array

Slice literals is like an array literal without a length

[3]bool{true, true, true} // this is an array
[]bool{true, true, true} // this is a slice - it creates the array first and then builds a slice to reference it

// examples:

[]int{1, 2, 3, 4, 5}

[]struct {
  X int, 
  Y string
}{
  {1, "one"}, 
  {2, "two"}, 
  {3, "three"}
}

// slices have a default value for low and high index as well
// default for low: 0
// default for high: len()

The slice has length -> the number of elements it contains -> len(s) The slice has capacity -> the number of elements in the underlying array -> cap(s)

So, we can do this:

package main

func main () {
  s := []int {1, 2, 3, 4, 5}
  
  s1 := s[:0] // s1 has size 0
  s1 := s[:4] // s1's size has been increased to 4
}

The zero value of a slice is `nil` A `nil` slice has length and capacity of 0 and has no underlying array

package main

import "fmt"

func main() {
	var s []int // empty array, so the slice is nil
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("nil!")
	}
}

Slices can be made with `make` - it’s a built in function `make` allocates a 0ed array and returns a slice that refers to that array

Syntax: make([]int, <int for len>, <int for capacity>)

Matrix:

board := [][]string{ []string{””, ””, ””}, []string{””, ””, ””}, []string{””, ””, “_”}, }

Appending to a slice. We have the append function described in the docs as:

func append(s []T, vs …T) []T

It takes as arguments “s” which is a slice of T type. And it takes in a variable number of arguments of type T and it returns a slice of type T

If the backing array of “s” is too small to fit all the given values, a bigger array will be allocated and a pointer to that new array will be returned

// create a slice
var s []int // this has 0 capacity right now
s = append(s, 0) // this increases the capacity of s, returns a pointer to new array

The range form of the “for” loop iterates over a slice or map When ranging over a slice, the default behavior is that of Python’s enumerate(iterable) The 2 items returned are the index, the copy of the element at that index

a := []int {11, 22, 33, 44}
for i, v := range a {
  fmt.Println(i, v)
}

// you can skip the index by using _
for _, v := range a {
  fmt.Println(_, v) // this will throw an error
}

Maps

A map maps keys to values A zero value of a map is `nil` A `nil` has no keys, nor can keys be added

type Vertex struct {
  Lat, Lon float64
}

var m map[string]Vertex

func main() {
  m = make(map[string]Vertex)
  m["Bell Labs"] = Vertex{
    1.11, 2.22
  }
  fmt.Println(m)
}

var mymap = map[string]Vertex{
  "foo" : Vertex{1.1, 2.2},
  "bar" : Vertex{1.2, 2.3},
}

// we can skip the type name here
var mymap = map[string]Vertex{
  "foo" : {1.1, 2.2},
  "bar" : {1.2, 2.3},
}

// we can insert an element in map m
m[key] = elem

// we can get an element in map m
elem = m[key]

// we can delete an element in map m
delete(m, key)

// test if a key is there
elem, ok = m[key]

// ok is true/false. If ok is false, elem is zero value of the map's element type

///////////////////////////
// testing the word count
///////////////////////////

package main

import (
	"golang.org/x/tour/wc"
	"strings"
)

func WordCount(s string) map[string]int {
	res := make(map[string]int) // make is used to create a new dict
	words := strings.Fields(s)
	for _, v := range words { // _ because we don't want the index
		res[v] = res[v] + 1
	}
	return res
}

func main() {
	wc.Test(WordCount)
}
///////////////////////////
// testing the slices
///////////////////////////

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	res := make([][]uint8, dx) // here, we make the doubly index, (but currently empty) slice
	for i := 0; i < dx; i++ {
		res[i] = make([]uint8, dy) // here, we make the inner slice
		for j := 0; j < dy; j++ {
			res[i][j] = uint8(i^j) // here, we populate the inner slice
		}
	}
	return res
}

func main() {
	pic.Show(Pic)
}

Function values

Functions are values too. They can be passed around just like other values - they can be used as function arguments and return values

package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 { // this line is extremely readable - we take in an argument called fn, which is of type function, taking in 2 floats and returning a single float
  return fn(3, 4)   // we always call fn with 3, 4
}

func main() {
  hypot := func(x, y float64) float64 { // here, we create a function and assign it to variable called hypot
    return math.Sqrt(x*x + y*y)
  }
  fmt.Println(hypot(5, 12)) // we can call the hypot variable
  fmt.Println(compute(hypot)) // we can call the compute function
  fmt.Println(compute(math.Pow)) // we can call the compute function
}

Functions in Go can be closures as well. Closures are just functions that reference variables from outside its body.

If that function has a access to the variables, it can assign and reference them

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


func adder() func(int) int { // adder returns a function that takes an int and returns an int
  sum := 0
  return func(x int) int { // this function that we return has access to the "sum" variable and is a function closure. Each returned function has it's own "sum" variable
    sum += int
    return sum
  }
}

//////////////////////////////////////
// we can have a fibonacci closure ///
//////////////////////////////////////

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	last := 0
	now := 1
	return func() int {
		res := last + now
		last = now
		now = res
		return res
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

Methods and interfaces

Methods

Go doesn’t have classes. But we can define methods on types

So, we can define a struct type and define methods on that struct. Now, you can use the struct instantiation to call that method If we want to associate it with a type, we need to use a special receiver argument - it appears in its own argument list between the `func` keyword and the method name

type Vertex struct{ // a normal struct with 2 attrs, both float
  X, Y float4
}

func (v Vertex) Abs() float64 { // note the syntax, we have: func <type to associate it with> <function name> (<function args>) <function return values>
  return math.Sqrt(v.X*v.X + v.*v.Y)
}

func main() {
  v := Vertex{3, 4}
  fmt.Println(v.Abs()) // we can call the function on the type as if it were the type's attribute
}

The terminology: the Abs method has a receiver of type Vertex named v

So, functions are normal “unassociated with type” functions Methods are functions which are associated with type

Remember: a method is just a function with a receiver argument.

If someone gives you a method, you can ask who is the receiver of this method? Or more explicitly, what type is the receiver of this method?

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

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

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

You can declare methods on all types, not just structs.

type MyFloat float64 // we declare a new type called MyaFloat which is just a float

func (f MyFloat) Abs() float64 { // the correct way to read this is to say, this function Abs can be applied on variables of type MyFloat
  if f < 0 {
    return float64(-f)
  }
  return float64(f)
}

func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

We can also create methods on pointer receivers In that case, the receiver argument part of the function declaration will have (t *T) which is to say that this function can be called on variables of type *T, that is, this function can be called on pointers to T (where T is some type)

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

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

func (v *Vertex) Scale(f float64) { // this function can be called on pointers of Vertex struct

// here, the function Scale is associated with the type "pointer of struct Vertex". So, it modifies the parent struct

	v.X = v.X * f // recall, this should have been (*v).X, C makes this easy by v->X, Go makes it easier by v.X
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs()) // here, we can call the method on "v", and not on &v (which would have looked like: (&v).Abs()) which represents a pointer to Vertex, or *Vertex, which is what the method is associated to. This is because Go compiler executes this as *v for us.
}

Since the methods often need to modify their receiver (the type they are associated with), pointer receivers are more common than value receivers

Go has “pass by value” like C. You can “pass by reference” with pointers

func Scale(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
Scale(&v, 10) // we now need to pass a pointer to the function

Here, if try to pass just “v” and not the pointer to v, it will throw an error - the function Scale expects a pointer to Vertex, it must be give a pointer to Vertex

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

v := Vertex{3, 4}

// we should be doing
fmt.Println(v.Abs())

p := &v
// we can do
fmt.Println(p.Abs())

// here, this will be interpreted as
fmt.Println((*p).Abs())

///////////////////////////////////////
/// we can have a fibonacci closure ///
///////////////////////////////////////
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

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

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

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

	p := &Vertex{4, 3}
	fmt.Println(p.Abs())
	fmt.Println(AbsFunc(*p))
}

Bottomline: Functions only accept what they explicitly say they will accept Methods can accept either values or their pointers -> they will get the right one and work with it So, use value receiver if you don’t want to modify receiver, use pointer receiver if you want to modify the receiver Also, you might want to use a pointer receiver when you want to avoid copying the value on each method call

Example, look at Abs below ⬇️

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

Here, 🔝 we used pointer receiver even when we didn’t want to change the Vertex struct

Interfaces

There is a special type - the “interface” type It is defined as a set of method signatures - it just has the signatures, not the body

When you declare a variable of this interface type, it can hold any value (of any type that implements this interface.

So, to make things concrete: Let’s say we have:

  1. An interace “x” with 3 method signatures
  2. A type “y” which implements all the 3 methods
  3. Now, you can do var x_1 x = y
    1. Note, we were able to store y in variable x
package main

import (
 "fmt"
 "math"
)

type MyFloat float64

type Abser interface {
  Abs() float64
}

func (f MyFloat) Abs() float64 {
  if f < 0 {
    return float64(-f)
  }
  return float64(f)
}

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

func main() {
  var a Abser
  f := MyFloat(-math.Sqrt(2))
  v := Vertex{3, 4}

  a = f // this is allowed since MyFloat implements the Abser interface
  // so how this works is that we can have an interface and folks who implemented that interface can be assigned to a variable of that type

  a = &v // this is allowed since the method is implemented by pointer to Vertex, *V. a = v is not allowed
}

A type implements an interface by implementing its methods. There is no explicit declaration of intent, no “implements” keyword This means that you can declare a new interface with some method and old types that implemented that interface automatically implement that interface

“Implicit interaces decouple the definition of an interface from its implementation”

package main

import "fmt"

type I interface {
 M()
}

type T struct {
 S string
}

func (t T) M() {
 fmt.Println(t.S)
}

func main() {
  var i I = T{"hello"} // defining a variable of type I interface and assigning to it struct of type T - this is allowed since type T implements (implicitly) the interface I
  i.M() // technically, this interface should be called Mer and not I - recall this is the Go standard
}

We can also consider One db for qa and prod but using only a readonly user in qa This will allow us to test out the gateway app with the prod data but we won’t be able to test the pipeline in qa - only dev for that.

Interface values

Interfaces values (the variables of type interfaces) can be thought of as a tuple of a value and a concrete type –> (value, type) The interface value holds a value of a specific underlying concrete type

Calling a method on the interface type executes the method of the same name on its underlying type (we know that the type would have that method defined as it implements the interface)

package main

import "fmt"

// define an interface I
type I interface {
 M()
}
// define a struct type
type T struct {
 S string
}

// define a float64 type
type F float64

// make the struct type implement the interface
func (t T) M() {
  fmt.Println(t.S)
}

// make the float64 type implement the interface
func (f F) M() {
 fmt.Println(f)
}

// in main, create a var of type interface and assign it to new struct and new float64 type and call that method
func main() {
 var i I
 i = F(math.Pi)
 i.M()
 describe(i) // we can pass i to a function that accepts interface I

 i = T("hello")
 i.M()
}

func describe(i I) {
 fmt.Printf("(%v, %T)\n", i, i)
}

Interface values with nil underlying values

If the concrete value that is associated to the interface value (the interface variable) then the method will be called with a nil receiver

This would have given a null pointer exception in other languages, since we passed a null pointer as the receiver to the method - but in go, it is common to write methods that gracefully handle being called with a nil receiver

package main

import "fmt"

// define an interface I
type I interface {
 M()
}

// define a struct type
type T struct {
 S string
}

// make the struct type implement the interface
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
 describe(i) // will print (<nil>, *main.T) -> <nil> is the internal representation of the struct. Note this is: (value, type)
 i.M() // here, we are passing a nil (null) receiver to the method M

 i = &T{"hello"}
 describe(i) // will print (&{hello}, *main.T) --> here, the value is not nil
 i.M() // this will print hello
}

Nil interface values

A nil interface value holds neither a value nor concrete type So, this is a interface value (interface variable) that is not associated with an instance of a type that implements this interface

var i I
describe(i) // (<nil>, <nil>) --> nil for both value and type
i.M() // this is a run time error because there is no type inside the interface tuple to indicate which concrete method to call
// this :top: will give "panic: runtime error: invalid memory address or nil pointer dereference"

Empty interface

The interface type that specifies zero methods is known as the empty interface

interface{}

An empty interface can hold values of any type This is used by code that handles of unknown type - like fmt.Println

This is a way of sidestepping the type system in go

func main() {
 var a interface{}
 describe(a) // (<nil>, <nil>)

 a = 42 // this is okay
 describe(a) // (42, int)

 a = "hello" // this is okay as well
 describe(a) // (hello, string)
}

Type assertions

A type assertion provides access to an interface value’s underlying concrete value

t := i.(T) // here we assert that the interface value i holds the concrete type T and assigns the underlying T value to the variable t
// this will trigger a panic if i doesn't hold any type T

// to test if the interface value holds a specific type, a type assertion can return 2 values - the underlying value and a boolean value that reports wheather the assertion succeeded
t, ok := i.(T)
// if i holds a value, t will hold it and ok will be true
// if not, t will be the zero value of type T and ok will be false --> no panic occurs

var i interface{} = "hello" // the catch all interface type instance i given a string (here, the interface has no name, it is an anonymous interface)

s := i.(string) // s is hello
s, ok := i.(string) // s is hello, ok is true

s, ok := i.(float64) // s is 0, ok is false

s := i.(float64) // panic

Note we talked about reflections that allow us to get the type of the varialbe, type assertions might be using it under the hood to get the type information

Type switches

They are a construct that permits several type assertions in series

switch v := i.(type) {
 case T:
  // here, the type is T
 case S:
  // here, the type is S
 default:
 // no match, here v has the same type as i
}

func do(i interface{}) {
 switch v := i.(type) {
  case int:
    fmt.Printf("here, the type is int")
  case string:
    fmt.Printf("here, the type is string")
  default:
    fmt.Printf("None of the above, it is %T", v)
 }
}

func main() {
 do(21)
 do("hello")
 do(true) //  None of the above, it is bool. Here, i is "bool", so v also gets the bool type
}

Stringers

One of the most ubiquitous interfaces is the Stringer interface, defined by the fmt package

It has a single method called String -> as the name suggests

type Stringer interface { // we have the Stringer interface
 String () string
}

func (p Person) String() string { // the struct Person implements the Stringer interface
 return fmt.Sprintf("%v (%v years)", p.Name, p.Age) // Sprintf prints the string
}

type Person struct {
 Name string,
 Age int
}

func main() {
 p = Person{23, "Darshan"}
 fmt.Printf(p) // Darshan (23 years) // the fmt.Printf just calls the String() method on the input value
}

A simple Example:

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
	return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip) // loopback: 127.0.0.1
	}
}

Errors

Go expresses error state with “error” values The `error` type is a builtin interface similar to fmt.Stringer

type error interface {
 Error() string
}

The fmt package looks for the error interface when printing the values

Functions often return `error` value and the calling code should handle errors by testing wheather the error equals `nil`

i, err := strconv.Atoi("42")
if err != nil {
 fmt.Printf("Could not convert to integer")
 return
}

Fmt.Printf("got the integer: ", i)

Another example:

package main

import (
	"fmt"
	"time"
)

// new struct MyError
type MyError struct {
	When time.Time
	What string
}

// implement the Error method required by the error interface
func (m *MyError) Error() string {
	return fmt.Sprintf("Did not work at %v and %v", m.When, m.What)
}

// write a function that returns an error
func run() error { // this method returns type error, which is to say, anything that implements the error interface, that is to say, anything type implementes the Error() method
	return &MyError{time.Now(), "it did not work"}
}

// in main, call the function :top: returning an error and see how the Error method is invoked
func main() {
	if err := run(); err != nil {
		fmt.Println("err message: ", err) // when you try to print the error, it looks for the Error() method
	}
}

So, all the functions that can fail return “error”

We can change our sqrt function from earlier to return an error on getting negative values as input

package main

import (
	"fmt"
	"math"
)

type ErrNegativeSqrt float64 // we define a custom error type called ErrNegativeSqrt

func (e ErrNegativeSqrt) Error() string { // the custom error type ErrNegativeSqrt implements the error interface
	return fmt.Sprintf("cannot Sqrt negative number: %f", float64(e)) // here, the type casting to float64 is necessary else, we'll call the Error method of this e, which calls itself - infinite loop. By type casting as e, we call the String method of e which is the String method of float64 since we don't define it explicitly
}

func Sqrt(x float64) (float64, error) { // the Sqrt function returns a float64 and an error (which may be nil)
	z := 1.0
	prev_z := 0.0

	eps := 2.220446049250313e-16
	if x < 0 {
		return 0, ErrNegativeSqrt(x) // if the input value is <0, return an error - any value that implements the error interface
	}

	for n := 0; n < 10; n++ {
		z -= (z*z - x) / (2 * z)
		// fmt.Printf("Iteration %v: %v (%v)\n", n, z, prev_z-z)
		if math.Abs(prev_z-z) < eps {
			// fmt.Printf("eps has been reached")
			break
		}
		prev_z = z
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2)) // here, the Sqrt function will return an error, which will be printed using Error() method above
}

Readers

The `io` package specifies the `io.Reader` interface, which represents the read end of a stream of data Any type that implements the `Read` method implements this interface

The `Read` method of the `io.Reader` interface:

func (T) Read(b []byte) (n int, err error) // the method ought to return num of bytes read (len(b)) and error if any

Read populates the given byte slice with data. It also returns an error, or io.EOF when the stream ends

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("Hello World!") // strings.NewReader returns a reader, a value that implements the Reader interface, so it has the Read method. Here, we give the NewReader the string data to read

	b := make([]byte, 8) // we make a byte slice

	for { // infinite loop
		n, err := r.Read(b)                               // this fills the byte array with the data
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b) // this prints the number of bytes read, error if any and the values filled in the bytes slice
		fmt.Printf("b[:n] = %q\n", b[:n])                 // we can print the bytes slice
		if err == io.EOF {                                // if we come across eof, we break the infinite loop
			break
		}
	}
}

Exercise: Implement a custom Reader type that emits an infinite series of ‘A’

So, basically, we will define a new type (say a struct) called MyReader that will have a Read method associated with it That Read method should fill the input byte slice with ‘A’s

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (mr MyReader) Read(b []byte) (n int, err error) {
	for i := 0; i < len(b); i++ { // we iterate thru the slice and fill it with As
		b[i] = 'A'
	}
	return len(b), nil
}

func main() {
	reader.Validate(MyReader{}) // the Validate method will give it some empty byte slice and see if MyReader fills it with 'A's
}

A common pattern is to take an io.Reader and wrap it with another io.Reader that modifies the stream in some way An example: the gzip.NewReader function takes an io.Reader (a stream of compressed data) and returns a *gzip.Reader that also implements io.Reader (a stream of decompressed data)

Exercise: Implement a reader that takes in a stream of data and replaces each character with it’s 13th character

package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func (mr *rot13Reader) Read(b []byte) (n int, err error) {
	// we read the data using the rot13Reader's inner Reader and modify the stream and return that
	for {
		n, err := mr.r.Read(b) // we read the data using the original inner reader
		for i := 0; i < n; i++ {
			b[i] = 'a' // we modify the data stream
		}
		return n, err // we can just do "return" here as we have already named the return variables above
	}
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!") // this is the input to the normal NewReader
	r := rot13Reader{s} // here, we wrap it with out own reader
	io.Copy(os.Stdout, &r) // this basically calls the Read method on r and copies the bytes to stdout
}

The io.Copy handles calling the Read method muliple times till EOF is returned etc

Images

The standard library has a package called “Images”. It defines the `Image` interface

package image

type Image interface {
 ColorModel() color.Model
 Bounds() Rectangle // Rectangle is in images package, but since Image interface is also inside images package, we don't need to write images.Rectangle
 At(x, y int) color.Color // these are also interfaces, but here we will use predefined implementations color.RGBA, color.RGBAModel (implemented in images/color package)
}

// to get a new image
m := image.NewRGBA(image.Rect(0, 0, 100, 100)) // this returns an implemnetation of the Image interface

We can create our own image:

package main

import (
	"image"
	"image/color"

	"golang.org/x/tour/pic"
)

// this is our implementation of the standard Image interface
type MyImage struct {
	i image.Image
}

// here the receiver is our custom image
func (i MyImage) ColorModel() color.Model {
	return color.RGBAModel
}

// here the receiver is our custom image
func (i MyImage) Bounds() image.Rectangle {
	return image.Rect(0, 0, 300, 300)
}

// this is interesting
// At is supposed to return color.Color
// From the documentation, we see that color.Color is also an interface with only 1 method RGBA() (r, g, b, a, uint32)
// Now, the standard library implements the RGBA method. The std lib has a struct RGBA and a method RBGA() which has this struct as the receiver.
// It looks like this:
//type RGBA struct {
//        R, G, B, A uint8
//}
func (i MyImage) At(x, y int) color.Color {
	return color.RGBA{uint8(y), uint8(x), uint8(x+y), 255} // when we do this, we create a new struct. Now this struct implements the Color interface since the standard library has the RGBA() method defined with this struct as the receiver. So, we ca return this struct as color.Color return type
}

func main() {
	m := MyImage{}
	pic.ShowImage(m)
}

This gives the result:

assets/screenshot_2018-03-01_07-47-07.png

Concurrency

Goroutines

They are lightweight threads managed by the Go runtime. They give you useful abstractions to do concurrent programming. You don’t have to deal with mutexes, locks, semaphores etc - the runtime handles that for you. You get a set of very clean abstractions to work with.

go f(x, y, z)

This stars a new goroutine running f(x, y, z). The evaluation of f, x, y, z happens in the current goroutine (execution happens in a new goroutine)

Goroutines run in the same address space - so access to shared memory must be synchronized - use channels.

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}
// this prints 
// world
// hello
// hello
// world
// world
// hello
// hello
// world
// world
// hello

Why does it print “hello” so many times? 🤔

Channels

They are a “typed” conduit through which you can send and receive values with the channel operator <-

ch <- v // send v to channel ch
v := <-ch // receive from ch and assign to v

Channels must be created before use: ch:= make(chan int)

By default, sends and receives block until the other side is ready - this allows goroutines to synchronize without explicit locks/conditional variables

Look at this snippet:

package main

import "fmt"

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) // we give the slice to the function
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}

We can make buffered channels as well Just provide the second argument to make(chan int)

ch := make(chan int, 100)

Sends block only when the buffer is full. Receives block when buffer is empty

package main

import "fmt"

func main() {
	ch := make(chan int, 2) // here, the channel has capacity of 2
	ch <- 1
	ch <- 2
	// ch <- 3 // this line will cause a deadlock, because the buffer is full and no one is reading from the channel
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

Range and close

A sender can close a channel to indicate that no more values will be sent The receiver can test for this by accepting a second argument when receiving

v, ok := <- ch

Here, `ok` is `false` if there are no more values to receive - that is, `ok` is true if the channel is closed.

for a := range c // will receive values repeatedly till the channel closes

Only the send ought to close the channel, not the receiver. Sending on a closed channel will cause a panic

Closing channels isn’t mandatory, it should be done to tell the receiver that no more values are to be expected

package main

import (
	"fmt"
)

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) // this closes the channel with "c" as the final value being sent
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c) // cap(c) gives the capacity of channel c
	for i := range c { // you can use range only if the sender actually closes the chan. If he never does, you won't know when to stop listening and this will cause a deadlock if the writer is finished writing and you are still blocked on listening
		fmt.Println(i)
	}
}

We can use `select` statement to let a goroutine wait on multiple communication operations

`select` blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready

package main

import "fmt"

func fibonacci(c chan int, 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) // here, we make 2 channels.
	quit := make(chan int)
	go func() { // this anonymous function just pulls 10 values from chan c and then puts a 0 in quit chan
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()

	fibonacci(c, quit) // in this function, we just see if we put a value on c chan, or if there is a value on quit chan - we'll be able to put a value on c chan if someone is listening on it
}

We can have a default case as well, which will run if no other case is ready

Consider this:

package main

import "fmt"
import "time"

func main() {
	tick := time.Tick(100 * time.Millisecond) // this will return a chan with Time type. The channel will receive the time every tick interval - every 100ms here
	boom := time.After(500 * time.Millisecond) // here too we get a chan with Time as the type. This chan receives the current time after the mentioned time lapse.

	for {
		select {
		case <-tick:
			fmt.Println("tick")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("   .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

Exercise: Equivalent Binary trees

To store the same data, we can have many binary trees. We have to find if 2 given trees have the same data

type Tree struct {
	Left *Tree
	Value int
	Right *Tree
}
package main

import "golang.org/x/tour/tree"
import "fmt"

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
	if t.Left != nil {
		Walk(t.Left, ch)
	}
	ch <- t.Value
	if t.Right != nil {
		Walk(t.Right, ch)
	}
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
	ch1 := make(chan int)
	go Walk(t1, ch1) // the Walk method just does an inorder traversal of the BST and gives the values
	
	ch2 := make(chan int)
	go Walk(t2, ch2)
	
	var v1, v2 int
	
	for c := 0; c < 10; c ++ {
		v1, v2 = <-ch1, <-ch2 // we compare each value and if even one is not same, we conclude the trees are not the same
		if v1 != v2 {
			return false
		}
	}
	return true
}

func main() {
	fmt.Println(Same(tree.New(10), tree.New(10)))
}

sync.Mutex

Channels are great for communication among goroutines, but what if we don’t need communication, but just want only one goroutine to access a variable at a time to avoid conflicts

This is called mutual exclusion and the datastructure that provides it is called mutex

Go’s standard library has sync.Mutex with these 2 methods:

  • Lock
  • Unlock

Look at this example, starting from main

package main

import (
	"fmt"
	"sync"
	"time"
)

// 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)} // initialize the struct, it has an internal mutex
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey") // the Inc method increase the key's value after Lock()
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey")) // Value locks to read the value and then unlocks before method exit
}

Exercise: Web crawler

Misc notes

Anonymous fields in structs

In structs, you can have anonymous fields. When looking at the source for github.com/PuerkitoBio/fetchbot (a slim web crawler), I came across the `BasicAuthProvider` interface

type BasicAuthProvider interface {
    BasicAuth() (user string, pwd string) // return the username pass to perform request with basic auth
}

There is a “Cmd” struct that must implement this if it needs to set the auth headers

type Cmd struct {
	U *url.URL
	M string
}

Now, normally you would implement it as:

func (c *Cmd) BasicAuth() (string, string) {
  return "id", "password"
}

However, in the tests for the package, I saw this:

type basicAuthCmd struct {
	*Cmd
	user, pwd string
}

func (ba *basicAuthCmd) BasicAuth() (string, string) {
	return ba.user, ba.pwd
}

Note here, the receiver is not *Cmd, but *basicAuthCmd. However, even then, the Cmd is said to have implemented the interface and would pass the check

   // here, cmd is "cmd Command"
   // Command is an interface that mandates URL() and Method() methods, which Cmd struct does - simply return the U and M string attrs
	if ba, ok := cmd.(BasicAuthProvider); ok {
		req.SetBasicAuth(ba.BasicAuth())
	}

This is because in the `basicAuthCmd` struct, the `Cmd` is an anon attribute. So, you can refer to the attributes of Cmd (which are U, M) directly via the `basicAuthCmd` - so the compiler doesn’t complain if you pass basicAuthCmd struct where Cmd struct is expected - because the Cmd struct is an anon attribute whose attrs can be directly accessed

Link: http://golangtutorials.blogspot.in/2011/06/anonymous-fields-in-structs-like-object.html

Consider this:

package main

import "fmt"

type Kitchen struct {
	numOfPlates int
}

type House struct {
	Kitchen
	numOfRooms int
}

func main() {
	h := House{Kitchen{4}, 40}
	fmt.Printf("Num of rooms: %d, Num of plates: %d\n", h.numOfRooms, h.numOfPlates) // this is allowed
}

Also, we can access the Kitchen struct like so `h.Kitchen`, so this is allowed: `h.Kitchen.numOfPlates`

Naming conflicts are okay if they are not at the same level - so, we can have numOfPlates in House struct as well - when accessed, this will be presented and it will overshadow the value of Kitchen.numOfPlates

But, at the same level, we cannot have a naming conflict

type Kitchen struct {
    numOfLamps int
}

type Bedroom struct {
    numOfLamps int
}

type House struct {
    Kitchen
    Bedroom
}

func main() {
    h := House{Kitchen{2}, Bedroom{3}} //kitchen has 2 lamps, Bedroom has 3 lamps
    fmt.Println("Ambiguous number of lamps:", h.numOfLamps) //this is an error due to ambiguousness - is it Kitchen.numOfLamps or Bedroom.numOfLamps
}

init()

The entry point for any go software is main(). However, if that package has an init() (even the main package), it will be called before everything else. Actually, even before that package’s init(), the global vars in that package will be initialized first. SO:

var WhatIsThe = AnswerToLife()

func AnswerToLife() int {
    return 42
}

func init() {
    WhatIsThe = 0
}

func main() {
    if WhatIsThe == 0 {
        fmt.Println("It's all a lie.")
    }
}

Here, AnswerToLife will be called first. This is to initialize the WhatIsThe variable. Next, init() will be called. Finally, main().

How to Write Go Code

All Go code is in a single workspace

The workspace has repositories

Each repositories have 1 or more packages

Each package has 1 or more Go source files(or more directories) in a single directory

Workspace has 3 dirs

src

  • It contains Go source files

pkg

  • It contains package objects
  • They have `.a` extension. One directory for each arch (eg: darwin_amd64)

bin

  • It contains executable commands
  • this path is under $PATH, so when you install, you can use the command directly from terminal

$GOPATH is used to specify the location of the workspace

Import paths

  • globally unique import path for your package
  • When working on a new project, create the package in github.com/user dir, just in case you publish it later
  • eg: let’s build our first program `hello`

`$ mkdir $GOPATH/src/github.com/user/hello`

package main

import "fmt"

func main() {
	fmt.Printf("Hello, world.\n")
}

First program

Now we can build and install that program with the `go` tool

`go install github.com/user/hello`

You can run 🔝 from anywhere in the system, the `go` tool will use the $GOPATH If you are in the hello dir, you can do `go install`

The `install` command will produce an executable binary and places it in the workspace’s `bin` directory as `hello` (`hello.exe` under Windows) - eg: $GOPATH/bin/hello

This can be executed now with `$GOPATH/bin/hello`, or simply `hello` since `$GOPATH/bin` is in the `$PATH`

First library

We’ll write a go library and use it from the `hello` program

We’ll create a dir for it in the right place

`$ mkdir $GOPATH/src/github.com/user/stringutil`

We can write `reverse.go` now

// Package stringutil contains utility functions for working with strings.
package stringutil

// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
  r := []rune(s)
  for a, b := 0, len(r)-1, a < len(r/2), a, b = a+1, b-1 {
    r[a], r[b] = r[b], r[a]
  }
  return string(s)
}

Now we can build it: `$ go build github.com/user/stringutil` (just `go build` if in that dir) - this is just to make sure our program builds

Now, in `hello.go` we can do:

package main

import (
	"fmt"

	"github.com/user/stringutil" // we are using our new library here
)

func main() {
	fmt.Printf(stringutil.Reverse("!oG ,olleH"))
}

When we install `hello`, it will install `stringutil` as well since `stringutil` is a dependency for `hello`

Our workspace looks like this:

bin/
    hello                 # command executable
pkg/
    linux_amd64/          # this will reflect your OS and architecture
        github.com/user/
            stringutil.a  # package object
src/
    github.com/user/
        hello/
            hello.go      # command source
        stringutil/
            reverse.go    # package source

When we did `go install github.com/user/hello`, we got `stringutil.a` object in a directory inside `pkg/linux_amd64`. This is so that future invocations of `go` tool can find the package object there and not recompile the package again.

Go command executables are statically linked; the package objects need not be present to run Go programs - so, the binary that is generated is a single complete binary having all the components it needs to run (like `jq`)

The package name should be the last element of the import path; the package imported as “crypto/rot13” should be named “rot13”

The executable commands must always use “package main”.

Testing

Go has a lightweight testing framework in the std lib - the `testing` package, use it with `go test`

The tests can be in files ending with `_test.go` and have functions named `TestXXX` with the signature `t *testing.T`

If the function calls a failure function such as `t.Error` or `t.Fail` (both defined in the `testing` package), the test is considered to have failed We can add a test for our stringutil function

package stringutil

func TestReverse(t *Testing.T) {
	cases := []struct {
		in, want string
	}{
		{"Hello, world", "dlrow ,olleH"},
		{"Hello, 世界", "界世 ,olleH"},
		{"", ""},
	}

	for _, c := range cases {
		got := Reverse(c.in)
		if got != c.want {
			t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
		}
	}
}

Run the test like so:

$ go test github.com/user/stringutil
ok  	github.com/user/stringutil 0.165s

Effective Go

Introduction

A straight translation of a program from Java to Go won’t produce satisfactory results, Go programs are to be written in Go perspective - this document will teach us about that.

Examples

Tip: The Go package sources (the standard library) is intended to serve as examples and ideas of how to use the language.

Formatting

`gofmt` operates at the source file level, `go fmt` operates at the package level

Tabs are used by default, don’t change them unless you absolutely have to

Commentary

Go has C-style block comments `/* */` and C++ style line comments `//`

Block comments are extracted by `godoc` to generate documentation hosted on godoc.org

Comments that appear before top-level declarations without any intervening newlines are extracted along with the declaration to serve as explanatory text for the item

Every package should have a package comment, a block comment preceding the package clause - in any file in the package works. It appears at the top in the godoc page for the package

Eg:

/*
Package regexp implements a simple library for regular expressions.

The syntax of the regular expressions accepted is:

    regexp:
        concatenation { '|' concatenation }
    concatenation:
        { closure }
    closure:
        term [ '*' | '+' | '?' ]
    term:
        '^'
        '$'
        '.'
        character
        '[' [ '^' ] character-ranges ']'
        '(' regexp ')'
*/
package regexp

The comments are unintrepreted plain text, so HTML, markdown etc won’t work

Any comment immediately preceding a top level declaration serves as a “doc comment” for that declaration. Every exported name in a package should have a doc comment - the doc comment should start with the name being declared

Names

Capitalized names are exported in Go

Package names

Good package names are single words, no need of under_scores or mixCaps

The exported names will be prefixed by the package names, so use that to your advantage Example:

bufio.Reader and not bufio.BufReader

Also, we have io.Reader too, but since the package is different, it’s okay Similarly, we have a package ring - it has only 1 exported type: Ring So, to make new instances of Ring, we don’t have ring.NewRing, but ring.New

Long names don’t automatically make things more readable.

Getters

Go doesn’t have automatic support for getters and setters - but you can add it yourself It’s not idiomatic (godiomatic?) to put `Get` in the name

If you have an unexported field (say, owner), the getter method should be called Owner, and not GetOwner A setter function can be called SetOwner

This reads well:

owner := obj.Owner()
if owner != user {
  obj.SetOwner(user)
}

Interface names

One method interfaces should have method + “-er” - eg: Reader, Writer, Formatter etc

Also, if you have a new type and it implements the same methods as a method on a well-known type, use the same name - eg, call the string converter method String and not ToString (String also happens to mandated by the Stringer interface)

MixedCaps

Go likes MixedCaps and mixedCaps, under_scores are not welcome

Semicolons

Go’s formal grammar uses semicolons to terminate statements, but the lexer adds them automatically so they don’t need to appear in the source code

The rule is this: if the last token before a newline is an identifier (which includes words like `int` and `float64`), a basic literal such as a number or string constant, or one of these tokens - `break, continue, fallthrough, return, ++, –, ) }` the lexer inserts a semicolon after the token

Rephrasing 🔝

“If the newline comes after a token that could end a statement, insert a semicolon”

So, we won’t have semicolons inserted by the lexer

This has the consequence that you cannot put the opening brace of a control structure (if, for, switch or select) on the next line

if i < f() {
    g()
}

// correct :top:

if i < f()  // wrong!
{           // wrong!
    g()
}

// incorrect :top:

Control structures

  • Similar to C, but differ in important ways.
  • There is no `do`, no `while`. `switch` is more flexible
  • `if` and `switch` accept an optional initialization statement like that of `for`
  • `break` and `continue` take an optional label to identify what to break or continue (like, `loop` from fetchbot)
  • `select` can also used as a select for types (as a type switch)
  • `select` can also be used as a multiway communications multiplexer
  • the syntax is also a little different: no parantheses and the bodies must always be brace delimited

if

if x > 0 {
  return y
}

Recall `if` and `switch` accept an initialization statement, commonly used to setup a local variable

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

Redeclaration and reassignment

Consider:

f, err := os.Open(name) // here, we declare f and err
d, err := f.Stat() // here, we re-assign err since it is already declared above

The `:=` declaration of variable `v` may appear even if it has already been declared, provided in these cases:

  • this declaration is in the same scope as the existing declaration of `v`
    • if v is already declared in an outer scope, the declaration will create a new variable (for that scope)
  • the corresponding value in the initialization is assignable to v (that is, if it is the same type as v’s old type)
  • there is at least one other variable in the declaration that is being declared anew (and the old one gets value of same type as last time)

For

Go’s `for` unifies C’s `for`, `while` and `do-while`

// this is C's for
for init; condition; post {}

// this is C's while
for condition {}

// this is C's for(;;)
for {}

Creating an index right there in the for loop is convenient

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

To loop over an iterable (like array, slice, string, map, reading from a channel), use `range`

for key, value := range oldMap {
    newMap[key] = value
}

To use only the first item (the key of key, index), drop the second

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

To use only the 2nd, use _ for the first

sum := 0
for _, value := range array {
    sum += value
}

If you iterate thru a string, it is parsed as Unicode code points and each iteration gets a `rune` - which is one individual unicode code point (size varies of course, the ascii ones have the same encoding as ascii - 8 bits)

If the encoding is erroneous, the replacement rune is U+FFFD (size is one byte)

for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
    fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}

prints

character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7

Note the size varies for each rune

Switch

Go’s `switch` is more general than C’s The expressions need not be constants, or even integers The cases are tried from top to bottom

func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	}
	return 0
}

You can have multiple clauses for `case`

func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}

`break` can be used to terminate `switch` early. It can also be used to break out of a surrounding loop - you can put a label on the loop and `break` to that label

Loop:
	for n := 0; n < len(src); n += size {
		switch {
		case src[n] < sizeOne:
			if validateOnly {
				break
			}
			size = 1
			update(src[n])

		case src[n] < sizeTwo:
			if n+1 >= len(src) {
				err = errShortInput
				break Loop // we break out of Loop
			}
			if validateOnly {
				break // here we only break out of the of the switch
			}
			size = 2
			update(src[n] + src[n+1]<<shift)
		}
	}

Example:

// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
    for i := 0; i < len(a) && i < len(b); i++ {
        switch {
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        }
    }
    switch {
    case len(a) > len(b):
        return 1
    case len(a) < len(b):
        return -1
    }
    return 0
}

Type switch

`switch` can also be used to discover the dynamic type of an interface variable. Such a type switch uses the syntax of a type assertion with the keyword `type` in parantheses. If a varialbe is declared in the expression, the variable will have the corresponding type in each clause

var t interface{}

t = functionOfSomeType() // this function returns some type

switch t := t.(type) { // use the same name here
default:
	fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
case bool:
    fmt.Printf("boolean %t\n", t)             // t has type bool
case int:
    fmt.Printf("integer %d\n", t)             // t has type int
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}

Functions

Multiple return values

In Go, functions and methods can return multiple values

Ex, look at the `Write` method on files from `os` package `func (file *File) Write(b []byte) (n int, err error)`

It returns the num of bytes written and a non-nil error when `n!=len(b)`

Named result parameters

You can name the return variables of a function and they can be used as regular variables.

“When named, they are initialized to the zero values for their types when the function begins; if the function executes a return statement with no arguments, the current values of the result parameters are used as the returned values.”

The naming of the return variables can make the code shorter and cleaner - they serve as documentation if for example you are returning 2 ints etc

They are used nicely here:

func ReadFiull(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
}

Defer

Go’s defer statement schedules a function call to be run immediately before the function executing `defer` returns

Canonical examples are unlocking a mutex, closing a file, response body etc

2 points about defer:

  • multiple defers are allowed
  • they are executed in a LIFO manner - like stacks
  • so, defer fn_a() followed by defer fn_b() means, fn_b() runs first
  • The arguments to the deffered function are evaluated when the defer executes, not when the call executes
    • so, the argument is evaluated when you write the defer statement, not when the function being deffered is called

Example:

import "fmt"

func trace(s string) string {
	fmt.Println("entering: ", s)
	return s
}

func un(s string) {
	fmt.Println("leaving: ", s)
}

func a() {
	defer un(trace("a"))
	fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

// prints
// entering: b
// in b
// entering: a
// in a
// leaving: a
// leaving: b

Data

Allocation with new

Go has 2 allocation primitives - “new” and “make”

Both do different things:

  • “new” allocates memory - it does not initialize it, it only zeros it
  • so, new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T
  • In go terminology, it returns a pointer to a newly allocated zero value of type T

It is really nice if you can make the zero-value-useful. Since the memory returned by “new” is zeroed, this becomes particularly convenient

Eg: documentation of bytes.Buffers states that the zero value of Buffer is an empty buffer, ready to use

Similarly, sync.Mutex does not have explicit constructor or Init method, the zero value of a sync.Mutex is defined to be an unlocked mutex

This works well transitively - (if a = b, and b = c, then a = c) too Consider:

type SyncedBuffer struct {
    lock    sync.Mutex
    buffer  bytes.Buffer
}

// here, the struct is ready to be used on initialization directly
// both p and v will work without further arrangement
p := new(SyncedBuffer) // type *SyncedBuffer
var v SyncedBuffer // type SyncedBuffer

Constructors and composite literals

Sometimes, the zero value isn’t good enough and you want an initializing constructor

Consider this derived from package `os`:

func NewFile(fd int, name string) *File {
	if fb < 0 {
		return nil
	}
	f := new(File)
	f.fd = fd
	f.name = name
	f.dirinfo = nil
	f.nepipe = 0
	return f
}

We can reduce the boilerplate by using a composite literal - an expression that creates a new instance each time it is evaluated:

func NewFile(fd int, name string) *File {
	if fb < 0 {
		return nil
	}
	f := File{fd, name, nil, 0}
	return &f
}

Here, we are returning the address of a local variable and that’s OK! Recall this is where we were making the mistake when writing the C program to reverse a linked list with Prabal

It’s okay in Go because taking the address of a composite literal allocates a fresh instance each time it is evaluated

If the fields of a composite literal are not named, they must be laid out in order and all must be present. If you name them however, you can skip some and the skipped ones get the zero values of their respect types

If no values are present, you get a zero value for all the struct’s fields - File{}

These are equivalent - new(File), &File{} - recall Pike says, you can initialize the values you care for and the rest will be given the type’s zero values

Composite literals can also be used for arrays, slices and maps eg:

a := [...]string {Encone: "no error", Eio: "Eio", Einval: "invalid argument"}
b := []string {Encone: "no error", Eio: "Eio", Einval: "invalid argument"}
c := map[int]string{Encone: "no error", Eio: "Eio", Einval: "invalid argument"}

Allocation with make

The other allocating construct in Go is “make” The `make(T, args)` serves a purpose different from new(T)

It creates ONLY - slices, maps and channels It returns an initialized (not zeroed) value of type T (not *T)

“The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use” (not zeroed, but initialized)

The slice for example is a 3 item descriptor containing:

  • a pointer to the data (inside an array)
  • length
  • capacity

All 3 are required and until those items are initialized, the slice is nil

For eg:

`make([]int, 10, 100)`

Allocates an array of 100 ints and then creates a slice structure with length 10 and a capacity of 100 pointing at the first 10 elements of an array

Go Slices: usage and internals

Slices are just an abstraction over Go’s arrays. Arrays are like in every other language, they are a sequences of typed data. To be defined, they need to have a length and element type. Example, [4]int is an array of size 4, with the data type being int. The size is fixed, so [4]int and [5]int are 2 different “types” (think of the array type as being a vector, like a vector has 2 components to be defined, magnitude and direction, the array has length and data type)

var a [4]int
a[0] = 1
b := a[0] // b == 1

Array’s are initialized with the zero values of the data type. So, 🔝 a[3]==0 is true.

assets/screenshot_2018-04-28_14-55-22.png

The in-memory representation of [4]int 🔝

Go’s arrays are values, in that the array variable is not a pointer to the first element of the array, but of the entire array. Since Go has pass by value, if you pass the array variable around, you create a copy of it, to avoid that, you have to pass the reference to the array (the pointer to the array)

“One way to think about arrays is as a sort of struct but with indexed rather than named fields: a fixed-size composite value.”

a := [2]string{"foo", "bar"}
b := [...]string{"foo", "baz"} // the compiler does the counting here

Arrays are old, they are okay. Slices are awesome. The build on arrays to provide flexibility and convenience.

The type specification for a slice is `[]T`, note the absence of length declaration is exactly the same as arrays without the length

a := []string{"foo", "bar"}

You can use `make` to create the slices, signature is:

func make([]T, len, cap) []T

Note, the slice is returned, not the pointer to the slice

The capacity is optional. If omitted, cap = len

var s []byte // declaration
s = make([]byte, 5, 5) // initialization, equivalently, s = make([]byte, 5)
// we have s == []byte{0, 0, 0, 0, 0}

You can create a slice by slicing an existing slice or array. So, b[1:4] will create a new slice with elements 1, 2, 3 from b

b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
// b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b

Like in python, b[:] == b

How to create a slice given an array?

x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // a slice referencing the storage of x

Slice internals

A slice is a descriptor of an array segment. It consists of:

  • a pointer to the array
  • the length of the segment
  • its capacity (the max length of the segment)

assets/screenshot_2018-04-28_15-14-40.png

Recall we had a array byte from earlier, [5]byte

Our slice s = make([]byte, 5) looks like this:

assets/screenshot_2018-04-28_15-15-46.png

The length (2nd block in slice, 5 here) is the number of elements referred to by the slice. The capacity is the number of elements in the underlying array beginning at the element referred to by the slice pointer.

Consider we slice s above s2 := s[2:4]

Now, we have another slice that points to the same underlying array that s was pointing to, but s2 has length 2 and capacity 3

assets/screenshot_2018-04-28_15-18-26.png

“Slicing does not copy the slice’s data. It creates a new slice value that points to the original array.” - so if you modify a slice, the underlying array gets modified as well

Growing slices (the copy and append functions)

To increase the capacity of a slice, you must create a new, larger slice and copy the contents of the original slice into it. (Just like how dynamic array implementations work in other languages)

Here, we have a slice “s”, we make a new slice “t” and copy the contents of s into t and then assign the value t to s

t := make([]byte, len(s), (cap(s)+1)*2)
for i := range(s) {
  t[i] = s[i]
}
s = t

We can use the built-in copy function as well

`func copy(dst, src []T) int`

Here, int is the number of elements copied

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t

One common operation is appending data at the end of the slice

func AppendByte(slice []byte, data ...byte) []byte {
	m := len(slice)
	n := m + len(data)
	if n > cap(slice) { // we need to reallocate, provision a larger slice
		newSlice := make([]byte, (n+1)*2)
		copy(newSlice, slice)
		slice = newSlice
	}
	slice = slice[0:n]
	copy(slice[m:n], data)
	return slice
}

Usage:

p := []byte{2, 3, 5}
p = AppendByte(p, 7, 11, 13)
// p == []byte{2, 3, 5, 7, 11, 13}

This function can be used if you need fine grained control over how the slice grows, (2x each time or not etc) But if you don’t want that level of control, just fly with the in built `append` function

func append(s []T, x...T) []T

// example

a := make([]int, 1)
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}

To append one slice to another, we can use … Syntax to expand the 2nd argument to a list of arguments (like python’s *list)

a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}
a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])"
// a == []string{"John", "Paul", "George", "Ringo", "Pete"}

Recall that the zero value of a slice (nil) acts like a 0 length slice, so you can declare a slice variable and append to it in a loop

// 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
}

A possible gotcha

Since a slice is a reference to the underlying array, image a scenario where you have a large file and you read the contents of that file into a []byte and then you do a regexp search for a few bytes. But since the slice that represents the few bytes references the array, the GC cannot claim the memory and the array stays in memory

To solve it, make a new slice and return that

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

// more concisely, 
func CopyDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    return append(make([]byte, len(b)), b)
}

Arrays Slices Two-dimensional slices Maps Printing Append Initialization Constants Variables The init function Methods Pointers vs. Values Interfaces and other types Interfaces Conversions Interface conversions and type assertions Generality Interfaces and methods The blank identifier The blank identifier in multiple assignment Unused imports and variables Import for side effect Interface checks Embedding Concurrency Share by communicating Goroutines Channels Channels of channels Parallelization A leaky buffer Errors Panic Recover A web server