Alex

Matt Holiday Go Class (YouTube)

Notes from learning the fundamentals of the Go programming language from this amazing tutorial. It is a fantastic video tutorial on YouTube that explains Go concepts from the ground up and offers some great insight into the language design

Variables

var a int
// or
a := 2 // in functions or methods
const(
    a = 1
    b = 3 * 100
    s = "hello"
)

Strings

`string with "quotes"`

Arrays and Slices

Maps

var m map[string]int // nil map (reading any key will return the default value of the map value type)
_m := make(map[string]int) // empty non-nil map
var m = map[string]int {
  "hello": 1
}
p := map[string]int{} // Empty non nil map
a, ok := p["hello"] // Returns 0, false since the key "hello" doesn't exist
p["hello"]++
b, ok := p["hello"] // Returns 1, true

if w, ok := p["the"]; ok {
  // Useful if we want to do something if an entry is / isn't in the map
}

Various Builtin Functions

Reproduced from https://www.youtube.com/watch?v=T0Xymg0_aSU

nil (From https://www.youtube.com/watch?v=ynoY2xz-F8s)

Nil Interfaces

var s fmt.Stringer   // This is a nil interface with no concrete type and no value (nil, nil)

fmt.Println(s == nil)   // Will print true since (nil, nil) == nil

//---

var p *Person // This Person satisfies the person interface

var s fmt.Stringer = p // Now we have (*Person, nil) - a concrete type (*Person) but still no value. This is now no longer equal to nil

//---

func do() error { // This will return the nil pointer wrapped in the error interface (*doError, nil)
  var err *doError
  return err // This is a nil pointer of type *doError
}

fmt.Println(do() == nil) // Will be FALSE because of the above example - (*doError, nil) != nil!!!

// It is good practice to not define or return concrete error variables

Control Statements

if x, err := doSomething(); err != nil {
  return err  
}
for i := range someArr {
  // i is an index here. Remember this - this mistake can happen often. i is the INDEX NOT THE VALUE. 
  // If you want to range over the values you can use the blank identifier like for _, v := range someArray
}

for i, v := range someArr {
  // i is an index, v is the value at that index
  // The value v is COPIED - don't modify. If the values are some large struct, it might be better to use the explicit indexing for loop
}

for k := range someMap {
  // Looping over all keys in a map
}

for k, v := range someMap {
  // Getting the keys and values in the loop
}
for {
  // Infinite loop
}
switch someVal {
  case 0,1,2:
    fmt.Println("Low")
  case 3,4,5:
    // Noop
  default:
    fmt.Println("Other")
}
a := 3

switch {
  case a <= 2:
  case a == 8:
  default:
    // Do something
}

Packages

Imports

Variable Declarations

var a int
var a int = 1
var c = 1       // Type inference
var d = 1.0

// Declaration block for simplicity
var (
  x, y int
  z    float64
  s    string
)

Short Declaration Operator :=

err := doSomething()
err := doSomethingElse() // This is wrong, you can't re-declare err
x, err := doSomethingOther() // This is fine since you are declaring the new var x, and just reassigning err from the original assignment on the skip line above
func do() error {
  var err error

  for {
    n, err := f.Read(buf)

    if err != nil {
      break
    }

    doSomething(buf)
  }

  return err
}

Typing

Structural and Named Typing

Functions

Parameter Passing

Multiple Return Values

Naked Return Values

Defer

func main() {
  f := os.Stdin

  if len(os.Args) > 1 {
    if f, err := os.Open(os.Args[1]); err != nil {
      ...
    }
    defer f.close()
  }

  // At this point we can do something with the file and only if it is a file passed in the params will it be closed at function exit
}
func thing() {
  a := 10
  defer fmt.Println(a)
  a = 11
  fmt.Println(a)
  // Will print 11,10
}

Closures

func fib() func() int {
  a, b := 0, 1

  return func() int {
    a, b = b, a+b
    return b 
  }
}

func main() {
  f := fib()

  for x := f(); x < 100; x = f() {
    fmt.Println(x) // Prints fibonacci numbers less than 100
  }
}

More on Slices

// The following shows some different slices, with information on them given below

var s []int
t := []int{}
u := make([]int, 5)
v := make([]int, 0, 5)
w := []int{1,2,3,4,5}

The Slice Operator

The Slice Capacity Issue

Array and Slice APIs From Here

s := make([]int, 5)

// This is basically the internal implementation of slice growing that Go uses when appending to a slice that has reached it's max capacity
t := make([]int, len(s), (cap(s)+1)*2)
copy(t, s)
s = t

func filter(s []int, fn func(int) bool) {
  var res []int // == nil
  for _, v := range s {
    if fn(v) {
      res = append(res, v)
    } 
  }

  return res
}

Structs and JSON

type Employee struct {
  Name string
  Number int
  Boss *Employyee
  Hired time.Time
}

Maps of Structs

Structure & Name Compatibility of Structs

type thing1 struct {
	field int
}
type thing2 struct {
	field int
}
func main() {
	a := thing1{field: 1}
	b := thing2{field: 1}
	a = thing1(b) // Valid
}

JSON with Structs

type Response struct {
  Data string `json:"data"` // Only exported fields are included in a marshalled JSON string
  Status int `json:"status"`
}

func main() {
  // Serializing
  r := Response{"Some data", 200}
  j, _ := json.Marshal(r)

  // j will be []byte containing "{"data":"Some data","status":200}"

  // Deserializing
  var r2 Response
  _ = json.Unmarshal(j, &r2)
}

Reference and Value Semantics

More on Copying

for i, thing := range things {
  // thing is always a copy - mutating it doesn't mutate the thing in things
}

// You have to use an index if you want to mutate the element
for i := range things {
  things[i].field = value
}
func update(things []thing) []thing {
  things = append(things, x) // Copy
  return things
}

Stack Usage and Escaping

HTTP and Networking in Go

type Handler interface {
  ServeHTTP(http.ResponseWriter, *http.Request)
}
type HandlerFunc func(ResponseWriter, *Request)

// This is a method declaration on a function type
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
  f(w, r)
}

// Then we can define a function that conforms to that interface without 
// requiring explicit implementation of ServeHTTPz§
func handler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello, world")
}
var form = `
<h1>Todo #</h1>
<div></div>

OOP Concepts in Go

An Overview

Methods and Interfaces

type Stringer interface {
  String() string
}
type ReadWriter interface {
  Reader
  Writer
}

Interface Declarations

type Bigger struct {
  otherpackage.Big // Struct composition to be explored later
}

func (b Bigger) SomeMethod() {

}

Composition in Go

type Host struct {
  Hostname string
  Port int
}

type SimpleURI struct {
  Host
  Scheme string
  Path string
}

func main() {
  s := SimpleURI{
		Host:   other.Host{Hostname: "google.com", Port: 8080},
		Scheme: "https",
		Path:   "/search",
	}

  fmt.Println(s.Hostname, s.Scheme) // See how the Host has been promoted
}
type Thing struct {
	Field string
}

func (t *Thing) bruh() {
	fmt.Println(t.Field)
}

// Would also be valid with a value receiver method
// func (t Thing) bruh() {
// 	fmt.Println(t.Field)
// }

type Thing2 struct {
	*Thing
	Field2 string
}

func main() {
	t := Thing2{&Thing{"Hello"}, "world"}
	t.bruh() // Method call here is valid
}

Composition with Sorting Example

type Interface interface {
  // The length of the collection
  Len() int
  // Says whether the element at index i is less than the element at index j
  Less(i, j int) bool
  // Swaps the element at index i with the element at index j in the collection
  Swap(i, j int)
}
type Component struct {
  Name string
  Weight int
}

type Components []Component

func (c Components) Len() int { return len(c) }
func (c Components) Swap() { c[i], c[j] = c[j], c[i] }
func (c Components) Less(i, j int) {
  // LT rather than the less than symbol because Jekyll
  return c[i].Weight LT c[j].Weight
}
type ByName struct{ Components }
func (bn ByName) Less(i, j int) bool {
  return bn.Components[i].Name LT bn.Components[j].Name
}

type ByWeight struct{ Components }
func (bw ByWeight) Less(i,j int) bool {
  return bn.Components[i].Weight LT bn.Components[j].Weight
}
type reverse struct {
  Interface // It just embeds sort.Interface
}

func (r reverse) Less(i, j int) bool {
  return r.Interface.Less(j, i) // Note swapped arguments for reverse sorting
}

func Reverse(data Interface) Interface {
  return &reverse{data}
}

Making Nil Useful

// The nil / zero value of this struct is ready to use since a nil slice can be appended to
type StringStack struct {
  data []string
}

func (s *StringStack) Push(x string) {
  s.data = append(s.data, x)
}

func (s *StringStack) Pop() string {
  l := len(s.data)

  if l == 0 {
    panic("pop from empty stack")
  }

  t := s.data[l-1]
  s.data = s.data[:l-1]
  return t
}
type IntList struct {
  Value int
  Tail *IntList
}

func (list *IntList) Sum() int {
  if list == nil {
    return 0
  }
  
  return list.Value + list.Tail.Sum()
}

Exploring Value / Pointer Method Semantics

type Thing struct{}

func (t Thing) ValMethod() {}
func (t *Thing) PointerMethod() {}

type IVal interface { ValMethod() }
type IPtr interface { PointerMethod() }

func main() {
  var t Thing

  var iVal IVal
  var iPtr IPtr

  iVal = t  // Valid
  iVal = &t // Valid

  iPtr = t  // Not valid, since the value t doesn't have the pointer method PointerMethod in it's method set
  iPtr = &t // Valid
}

More on Interfaces

var r io.Reader // nil interface here
var b *bytes.Buffer // nil value here

r = b // at this point r is no longer nil itself, but it has a nil pointer to a buffer

The Error Interface

type error interface {
  func Error() string
}
type someErr struct {
  err error
  someField string
}

func (e someErr) Error() string {
  return "this is some error"
}

func someFunc(a int) *someErr { // We should NEVER return a concrete error type
  return nil
}

func main() {
  var err error = someFunc(123456)

  if err != nil {
    // Even though we logically didn't want to throw an error, returning a concrete error type 
    // meant that the err variable was initialised and looks like (*someErr, nil) which in the
    // semantics of interfaces ISN'T NIL
    fmt.Println("Oops")
  } else {
    // If we'd done err := someFunc(123456), the above check would have worked although again we 
    // should never return a concrete error implementation from a function
  }
}

More on Pointer vs Value Receivers from Matt Holiday Vid

Revisiting Understanding nil

Zero Values

Nil

Understanding the Different Types of nil

Pointers

Slices

Maps, Channels and Functions

Nil Interfaces

func bad1() error {
  var err *someConcreteError
  return err // We are returning (*someConcreteError, nil) which !=nil
}

func bad2() *someConcreteError {
  // We are returning a concrete pointer to an error which will pass ==nil, however
  // it is very bad practice because the second you wrap this pointer in the error
  // interface you will have the same problem as above
  return nil
}

How is Nil Useful

Nil Pointers

Nil Slices

Nil Slices

Nil channels example

Function Currying

func Add(a, b int) {
  return a+b
}

func main() {
  var addTo5 func(int) int = func (a int) int {
    return Add(5, a)
  }
}

Method Values

func (p Point) Distance(q Point) float64 {
  return math.Hypot(q.X-p.X, q.Y-p.Y)
}

func main() {
  p := Point{1,2}
  q := Point{4,6}

  distanceFromP := p.Distance // Here we close over the receiver value p, returning a curried function
}