Skip to content

Instantly share code, notes, and snippets.

@zcaceres
Created December 15, 2019 01:26
Show Gist options
  • Save zcaceres/db2d99f23da4ec9befcb60465f6ad29a to your computer and use it in GitHub Desktop.
Save zcaceres/db2d99f23da4ec9befcb60465f6ad29a to your computer and use it in GitHub Desktop.
Go: The Weird Parts – Relating Go to other Languages I Already Know

Go: The Weird Parts

Notes on the unique stuff about go, in the context of other languages that I already know.

Project Organization

  1. Root dir go
  2. src and bin inside the project
  3. unique project/package name

The first statement in a go source file must be package packagenamehere

Weird Stuff

Most stuff feels overall quite C-like.

Slices

Slices are a bit different. Constrained by type of their content, but not by length as a standard array.

Create an empty slice with non-zero length, use make.

Make a slice of length 3 (zero-valued).

s := make([]string, 3)

append returns a new slice with 1 or more values. Immutable, which is nice compared to Javascript's array.append.

s = append(s, "d")
s = append(s, "e")

slice operator similar to Python.

s[1:3]

Initialize values for a slice

t = []string{"go", "is", "cool"}

Maps

Similar to a dict or hash table.

String to int map.

m := map(map[string]int)

delete values with:

delete(m, "mykey")

optional second return value indicates whether the key was present in the object

value, didKeyExist = m["mykey]

Range

Sort of a mapper function for various data structure. Or a for in.

nums := []int{2 ,3 4}
sum := 0
for _, num := range nums {
  sum += nums
}

kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
  fmt.Printf("%s -> %s\n", k, v)
}

// byte index, char (rune)
for i, c := range "go" {
  fmt.Println(i, c)
}

Multiple Return Values

A bit like python tuples.

func inty(a, b int) (int, int) {
  return a, b
}

Variadic Functions

Just like the ...rest operator in JS.

func spready(nums ...int) int {
  total := 0
  for _, num := range nums {
    total += num
  }
  return total
}

Closures

Go supports anonymous functions, just like Javascript.

func factory() func() int {
  i := 0

  return func() int {
    i++
    return i
  }
}

Pointers

Pointers like C.

func passedByValue(value int) {
  # value is passed as a copy of the underlying value and here we operate on that copy only
  value = 0
}

func passedByReference(reference *int) {
  # here we mutate the underlying value by assigning a new int at the referenced address
  *reference = 0
}

Underlying memory addresses:

func main() {
  i := 0
  fmt.Println(i)

  passedByValue(i)
  fmt.Println(i)

  passedByReference(&i)
  fmt.Println(i)
  fmt.Println(&i)
}

Structs

Like C# structs.

type person {
  name string
  age int
}

func NewPerson(name string) *person {
  p := person{name: name}
  p.age = 42
  return &p
}


func main() {
This syntax creates a new struct.

    fmt.Println(person{"Bob", 20})

    fmt.Println(person{name: "Alice", age: 30})

    fmt.Println(person{name: "Fred"})

    fmt.Println(&person{name: "Ann", age: 40})

    fmt.Println(NewPerson("Jon"))

    s := person{name: "Sean", age: 50}
    fmt.Println(s.name)

    sp := &s
    fmt.Println(sp.age)

    sp.age = 51
    fmt.Println(sp.age)
}

It's idiomatic to initiate a new struct with a factory function.

You can also add methods on structs.

type rect struct {
  width, height int
}


// Pointer receiver type
func (r *rect) area() int {
  return r.width * r.height
}

// Value receiver type
func (r rect) perim() int {
  return 2*r.width + 2*r.height
}

Go seems to know that the method is on the struct since the struct is a parameter. The method is named in the function call defined at the top i.e. area().

Go also magically converts between values and pointers for method calls. You can control the behavior by specifying a pointer receiver type to avoid copying the struct on method calls or to allow the method to mutate the underlying values.

Interfaces

A "named collection of method signatures".

type geometry interface {
  area() float64
  perim() float64
}

type rect struct {
  width, height float64
}

type circle struct {
  radius float64
}

func (r rect) area() float64 {
  return r.width * r.height
}

// Value receiver type
func (r rect) perim() float64 {
  return 2*r.width + 2*r.height
}

func (c circle) area() float64 {
  return math.Pi * c.radius * c.radius
}

func (c circle) perim() float64 {
  return 2 * math.Pi * c.radius
}

func measure(g geometry) {
  fmt.Println(g.area())
  fmt.Println(g.perim())
}

Errors

Go communicates errors with explicit return values. Different than exceptions in other languages.

import (
  "errors"
)

func errorfunc(arg int) (int, error) {
  if arg == 42 {
    return -1, errors.new("An unholy number")
  }

  return arg + 3, nil
}

func main() {
  if r, e := errorFunc(42); e != nil {
    // FAIL
  } else {
    // OK
  }
}

Goroutines

Goroutine is "a lightweight thread of execution".

Sort of like a Javascript promise except actually concurrent (since JS is forever single-threaded).

func f(arg int) int {
  return arg
}

f(4) // called synchronously

go f(5) // called asynchronously

// with an anonymous function
go func(msg string) {
  fmt.Println(msg)
}("GOING ASYNC ANON")

Timers

timer

Basically setTimeout from JS.

import "time"

function timeMe() {
  // returns a channel that will be notified at that time (wait two seconds)
  timer1 := time.NewTimer(2 * time.Second)

  timer1.Stop() // cancel timer

  // sleep
  time.Sleep()
}

ticker

Basically setInterval from JS

package main
import (
    "fmt"
    "time"
)

func main() {

    ticker := time.NewTicker(500 * time.Millisecond)
    done := make(chan bool)
    go func() {
        for {
            select {
            case <-done:
                return
            case t := <-ticker.C:
                fmt.Println("Tick at", t)
            }
        }
    }()

    time.Sleep(1600 * time.Millisecond)
    ticker.Stop()
    done <- true
    fmt.Println("Ticker stopped")
}

Panic

Sort of like throw from JS but it will throw a non-zero exit code and provide a stack trace to stderr.

package main

import "os"

func main() {
	panic("a problem")

	_, err := os.Create("tmp/file")
	if err != nil {
		panic(err)
	}

}

Defer

Like a finally in JS. Except you defer a function call.

You have to check for errors even in a deferred function.

For example, you defer the cleanup of a file.

func main() {
  f := createFile("/tmp/defer.txt")
  defer closeFile(f)
  writeFile(f)
}

Exit

os.Exit(3) to exit with an explicit exit code. Return values from main don't count a la C.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment