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.

Revisions

  1. zcaceres created this gist Dec 15, 2019.
    405 changes: 405 additions & 0 deletions go-weird-parts.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,405 @@
    # 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.