Man

Development & AI | Alper Akgun

Learn Go by example - advanced

September, 2023

In this followup blog, I will learn a bit more about go. I study through the examples at use https://gobyexample.com

Go supports pointers.

package main

import "fmt"

func val42(ival int) {
    ival = 42
}

func ptr42(iptr *int) {
    *iptr = 42
}

func main() {
    i := 1
    fmt.Println("start:", i)

    val42(i)
    fmt.Println("Zero val:", i)

    ptr42(&i)
    fmt.Println("Zero ptr:", i)

    fmt.Println("Pointer:", &i)
}
            

Structs are collection of typed fields. They enabled object oriented programming.

package main

import "fmt"

type student struct {
    name string
    age  int
    grade int
}

func newStudent(name string) *student {

    p := student{name: name}
    p.age = 42
    p.grade = 95
    return &p
}

func main() {

    fmt.Println(student{"Bob", 12})

    fmt.Println(student{name: "Alice", age: 32, grade: 100})

    fmt.Println(&student{name: "Ann", age: 42})

    fmt.Println(newStudent("John"))

    s := student{name: "Steve", age: 5, grade: 90}
    fmt.Println(s.name)

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

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

    circle := struct {
        name   string
        isFilled bool
    }{
        "MyCircle",
        true,
    }
    fmt.Println(circle)
}            

Methods can be defined on struct types.

package main

import "fmt"

type ellipse struct {
    width, height int
}

func (r *ellipse) area() int {
    return r.width * r.height * 3.14159
}


func main() {
    r := ellipse{width: 10, height: 5}

    fmt.Println("area: ", r.area())

    rp := &r
    fmt.Println("area: ", rp.area())
}
            

Interfaces allow methods to be typed and added to structs.

package main
package main

import (
    "fmt"
    "math"
)

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

type rectangle struct {
    width, height float64
}
type circle struct {
    radius float64
}

func (r rectangle) area() float64 {
    return r.width * r.height
}
func (r rectangle) 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 shape) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}

func main() {
    r := rectangle{width: 3, height: 4}
    c := circle{radius: 5}

    measure(r)
    measure(c)
}
            

Embedding structs and interfaces allow type composition.

package main

import "fmt"

type construction struct {
    num int
}

func (b construction) describe() string {
    return fmt.Sprintf("construction %v", b.num)
}

type house struct {
    construction
    str string
}

func main() {

    co := house{
        construction: construction{
            num: 1,
        },
        str: "some name",
    }

    fmt.Printf("construction={num: %v, str: %v}\n", co.num, co.str)

    fmt.Println("also num:", co.construction.num)

    fmt.Println("describe:", co.describe())

    type describer interface {
        describe() string
    }

    var d describer = co
    fmt.Println("describer:", d.describe())
}            

Generics allow making types polymorhic, similar to C++ templates.

package main

import "fmt"

func KeyMapper[K comparable, V any](m map[K]V) []K {
    r := make([]K, 0, len(m))
    for k := range m {
        r = append(r, k)
    }
    return r
}

type List[T any] struct {
    head, tail *element[T]
}

type element[T any] struct {
    next *element[T]
    val  T
}

func (lst *List[T]) Push(v T) {
    if lst.tail == nil {
        lst.head = &element[T]{val: v}
        lst.tail = lst.head
    } else {
        lst.tail.next = &element[T]{val: v}
        lst.tail = lst.tail.next
    }
}

func (lst *List[T]) GetAll() []T {
    var elems []T
    for e := lst.head; e != nil; e = e.next {
        elems = append(elems, e.val)
    }
    return elems
}

func main() {
    var m = map[int]string{1: "2", 3: "9", 4: "16"}

    fmt.Println("keys:", KeyMapper(m))

    _ = KeyMapper[int, string](m)

    lst := List[int]{}
    lst.Push(12)
    lst.Push(29)
    fmt.Println("list:", lst.GetAll())
}            

Go has explicitly returned error handling, unlike exception mechanisms in other languages.

package main

import (
    "errors"
    "fmt"
)

func fnot42(arg int) (int, error) {
    if arg == 42 {

        return -1, errors.New("can't work with 42")

    }

    return arg + 3, nil
}

type argError struct {
    arg  int
    prob string
}

func (e *argError) Error() string {
    return fmt.Sprintf("%d - %s", e.arg, e.prob)
}

func fplus3(arg int) (int, error) {
    if arg == 42 {

        return -1, &argError{arg, "can't work with it"}
    }
    return arg + 3, nil
}

func main() {

    for _, i := range []int{7, 42} {
        if r, e := fnot42(i); e != nil {
            fmt.Println("fnot42 failed:", e)
        } else {
            fmt.Println("fnot42 worked:", r)
        }
    }
    for _, i := range []int{7, 42} {
        if r, e := fplus3(i); e != nil {
            fmt.Println("fplus3 failed:", e)
        } else {
            fmt.Println("fplus3 worked:", r)
        }
    }

    _, e := fplus3(42)
    if ae, ok := e.(*argError); ok {
        fmt.Println(ae.arg)
        fmt.Println(ae.prob)
    }
}