// this is a remix of multiple documentations / tutorials / blogs. // multlicensed under: // SPDX-License-Identifier: EUPL-1.2 // SPDX-License-Identifier: CC-BY-3.0 // SPDX-License-Identifier: BSD-3-Clause // code from https://gobyexample.com // Copyright Mark McGranaghan // Copyright Renée French // code from https://go.dev/tour/list // Copyright 2011 The Go Authors. // code / notes from https://go.dev/blog/slices-intro // Copyright Andrew Gerrand package main import ( "errors" "fmt" "iter" "maps" "math" "reflect" "slices" "sync" "time" "unicode/utf8" ) // constant const const_s string = "constant hello world" func main() { fmt.Println("hello world") // values, variables and constants /////////////////////////////////////// fmt.Println("go" + "lang") // golang fmt.Println("1+1=", 1+1) // 2 fmt.Println("7.0/3.0=", 7.0/3.0) fmt.Println(true && false) // false fmt.Println(true || false) // true fmt.Println(!true) // false var a = "initial" var b, c int = 1, 2 // unlike c, type (class) name comes after var name var d = true var e int // is this uninitialized? f := "apple" // shorthand for declaring + initialization fmt.Println(a) fmt.Println(b, c) fmt.Println(d) fmt.Println(e) fmt.Println(f) fmt.Println(const_s) const n = 500000000 const n2 = 3e20 / n fmt.Println(n2) // 6e+11 fmt.Println(int64(n2)) fmt.Println(math.Sin(n)) // control flows ///////////////////////////////////////////////////////// fmt.Println("------- - control flows---") i := 1 // while style for i <= 3 { fmt.Println(i) i = i + 1 } // c style for j := 0; j < 3; j++ { fmt.Println(j) } // iterator style for i := range 3 { fmt.Println("range", i) } // continue for n := range 6 { if n%2 == 0 { continue } fmt.Println(n) } // forever and break for { fmt.Println("loop") break } // statements can precede if conditionals. // variables declared here are visible in all branches if num := 9; num < 0 { fmt.Println(num, "is negative") } else if num < 10 { fmt.Println(num, "has 1 digit") } else { fmt.Println(num, "has multiple digits") } // switch-case; unlike C, only one arm is taken therefore no need for break. something := 2 switch something { case 1: fmt.Println("one") case 2: fmt.Println("two") case 3: fmt.Println("three") } // can use switch-case as a tidier if-if-else: case can take conditions t := time.Now() switch { case t.Hour() < 12: fmt.Println("it's before noon") default: fmt.Println("it's after noon") } // likewise for `if` we can bind a value here switch day := time.Now().Weekday(); day { // can merge multiple arms case time.Saturday, time.Sunday: fmt.Println(day, "is weekend") default: fmt.Println(day, "is weekday") } // type switch : switch on type instead of values whatAmI is a function // object, see later note: this can also be done with reflect.TypeOf() an // empty interface means pretty much "anything" whatAmI := func(i interface{}) { switch t := i.(type) { case bool: fmt.Println("I'm bool") case int: fmt.Println("I'm int") default: fmt.Printf("Don't know type %T\n", t) } } whatAmI(true) whatAmI(1) whatAmI("hello") whatAmI(whatAmI) // arrays //////////////////////////////////////////////////////////////// fmt.Println("--------- arrays ---------") // array is zero valued by default var arr [5]int fmt.Println("init array a[5]int:", arr) // accessing by index: arr[4] = 100 fmt.Println("set", arr) fmt.Println("get @4", arr[4]) whatAmI(arr) // length fmt.Println("length of arr is", len(arr)) // const initializer: arr_2 := [5]int{1, 2, 3, 4, 5} fmt.Println("arr_2", arr_2) // auto length arr_3 := [...]int{1, 2, 3, 4, 5} fmt.Println("arr_3", arr_3) // syntax suggar, specify index: {100, 0, 0, 400, 500} // unspecified elements are default to zero arr_4 := [...]int{100, 3: 400, 500} fmt.Println("arr_4", arr_4) // multi-dimensional var arr_2d [2][3]int for i := 0; i < 2; i++ { for j := 0; j < 3; j++ { arr_2d[i][j] = i + j } } fmt.Println("2d arr: ", arr_2d) // 2d array const init arr_2d = [2][3]int{ {1, 2, 3}, {1, 2, 3}, } fmt.Println("2d arr: ", arr_2d) // slices //////////////////////////////////////////////////////////////// // https://go.dev/blog/slices-intro // slices ar edynamically-sized, flexible view into elements of an array // (pretty much the same as rust slice?) fmt.Println("--------- slices ---------") // slice has type []T (unlike array, no length specified) arr_base := [6]int{1, 2, 3, 4, 5, 6} // create slice from array with arr_base[low : high] // left inclusive, right exclusive var arr_slice []int = arr_base[1:4] // or omit high/low bounds to use the defaults (0 for low, len for high) var arr_slice2 []int = arr_base[:3] var arr_slice3 []int = arr_base[:] fmt.Println("array_base: ", arr_base, " has type ", reflect.TypeOf(arr_base)) fmt.Println("array_slice: ", arr_slice, " has type ", reflect.TypeOf(arr_slice)) // slice is mutable reference to the underlying array arr_slice[1] = 42 fmt.Println("arr_base after mod: ", arr_base) // all slices to the array base sees the modification fmt.Println("arr_slice2 after mod: ", arr_slice2) fmt.Println("arr_slice3 after mod: ", arr_slice3) // slice has length and capacity // read with len(s) and cap(s) // cap is length of underlying array, _counting from slice low bound_ fmt.Println("arr_slice has length ", len(arr_slice), "and cap ", cap(arr_slice)) // slices can be trimmed or extended (given slice has enough capacity) arr_slice = arr_slice[:0] sliceInfo(arr_slice) // len = 0, cap=5 [] // extend the slice arr_slice = arr_slice[:4] // looks like out-of-bound but no sliceInfo(arr_slice) // len = 4, cap=5 [2 42 4 5] // drop first to values arr_slice = arr_slice[2:] // slice cap changes when its low bound shifts sliceInfo(arr_slice) // len = 2 cap=3 [4 5] // there is no way (?) to extend the slice on the lower bound // nil slice var s_nil []int sliceInfo(s_nil) // s_nil == nil // append to slice // The resulting value of append is a slice containing all the elements of // the original slice plus the provided values. If the backing array of s is // too small to fit all the given values a bigger array will be allocated. // The returned slice will point to the newly allocated array. s_nil = append(s_nil, 1) sliceInfo(s_nil) // len 1, cap 1, [1] s_nil = append(s_nil, 2, 3, 4) // can take VA sliceInfo(s_nil) // len 4, cap 4, [1 2 3 4] // make builtin fuction: make([]T, len, cap) // create new, initialized slice value for a given element type T // a slice created with make always allocates a new, hidden array to whicvh // the the returned slice value refers. // sm1 and sm2 are equivalent var sm1 = make([]int, 50, 100) var sm2 = new([100]int)[0:50] fmt.Println("sm1 has type ", reflect.TypeOf(sm1)) fmt.Println("sm2 has type ", reflect.TypeOf(sm2)) // slice literals : q is a reference to a (anonymous) constant array p := [...]int{1, 2, 3, 4, 5, 6, 7} q := []int{1, 2, 3, 4, 5, 6, 7} fmt.Println("p has type ", reflect.TypeOf(p)) fmt.Println("q has type ", reflect.TypeOf(q)) var s []string fmt.Println("uninit:", s) s = make([]string, 3, 3) // shorthand : can use make([]T, len) s[0] = "apple" s[1] = "banana" s[2] = "carrot" s = append(s, "e", "f", "g") fmt.Println(s, "len:", len(s), "cap:", cap(s)) // 2d slices (like 2d array) slice_2d := make([][]int, 3) for i := 0; i < 3; i++ { innerLen := i + 1 slice_2d[i] = make([]int, innerLen) for j := 0; j < innerLen; j++ { slice_2d[i][j] = i + j } } fmt.Println("2d slice ", slice_2d) // slices package functions // slices. {Max, Min, Equal, Grow ....} // maps ////////////////////////////////////////////////////////////////// // https://go.dev/blog/slices-intro fmt.Println("--------- maps -----------") // create map // make(map[key_type]val_type) // TODO: what's under the hood? m := make(map[string]int) m["k1"] = 7 // if key doesn't exist a new pair is created m["k2"] = 13 m["k3"] = 0 fmt.Println("map: ", m) // [k1:7 k2:13 k3:0] fmt.Println("len of map:", len(m)) fmt.Println("v1: ", m["k1"]) // 7 fmt.Println("v2: ", m["k2"]) // 13 fmt.Println("v3: ", m["k3"]) // 0 // if key doesn't exist, returns default value (0) fmt.Println("v_na: ", m["kna"]) // disambiguate between missing keys and zero-valued keys // optionally return a presence bool from map v, present := m["kna"] fmt.Println("presence of kna ", present, " value is ", v) // false, 0 delete(m, "k2") // remove a kv pair fmt.Println("map: ", m) // [k1:7 k3:0] delete(m, "kna") // no error reported when deleting non-exist fmt.Println("map: ", m) // [k1:7 k3:0] clear(m) // remove everything fmt.Println("map: ", m) // [] // inline declaration map1 := map[string]int{"foo": 1, "bar": 2} map2 := map[string]int{"foo": 1, "bar": 2} // map equal? if maps.Equal(map1, map2) { fmt.Println("n1 == n2: ", map1) } // range ///////////////////////////////////////////////////////////////// // iterate over array (slice) with auto index (from 0) for i, n := range arr_base { fmt.Printf("%d:%d ", i, n) } fmt.Println() // range index always starts from 0 for i, n := range arr_base[2:] { fmt.Printf("%d:%d ", i, n) } fmt.Println() // range over map: kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Printf("%s -> %s \n", k, v) } // range over string: (by unicode rune) for i, c := range "go" { fmt.Println(i, c) } // with widechars the indexing will be quite different (TODO) // https://gobyexample.com/strings-and-runes // for i, c := range "测试" { // fmt.Println(i,c) // } // pointers ////////////////////////////////////////////////////////////// // just like c. f_val := func(ival int) { ival = 0 } f_ptr := func(iptr *int) { *iptr = 0 } test_vi := 1 fmt.Println(test_vi) // 1 f_val(test_vi) fmt.Println(test_vi) // 1 f_ptr(&test_vi) fmt.Println(test_vi) // 0 fmt.Println("ptr addr:", &test_vi) // strings and runes (TODO) ////////////////////////////////////////////// const test_s = "测试" fmt.Println("string: ", test_s, " len: ", len(test_s), " runes: ", utf8.RuneCountInString(test_s)) // strings are equivalent to []byte, iterating results in raw bytes for i := 0; i < len(s); i++ { fmt.Printf("%x ", s[i]) } fmt.Println() // range over string gets runes. The idx is byte idx where the rune starts for idx, runeValue := range test_s { // use %U to print a rune fmt.Printf("%#U starts a at %d\n", runeValue, idx) } // type rune and literals examineRune := func(r rune) { if r == '试' { fmt.Println("found \"试\"") } } // manually decode runes from raw byte array for i, w := 0, 0; i < len(test_s); i += w { runeValue, width := utf8.DecodeRuneInString(test_s[i:]) fmt.Printf("%#U starts at %d\n", runeValue, i) w = width examineRune(runeValue) } // structs / embedded structs //////////////////////////////////////////// // TODO: https://gobyexample.com/struct-embedding // create a type "person" from a struct type person struct { name string age int } test_p := person{name: "john"} // age will take default value test_p.age = 42 test_p2 := person{name: "alice", age: 24} fmt.Println(test_p, test_p2) // automatic dereference fmt.Println(test_p.name) test_p_ptr := &test_p fmt.Println(test_p_ptr.name) // anonymous struct: dog := struct { name string isGood bool }{ "Rex", true, } fmt.Println(dog) // goroutines //////////////////////////////////////////////////////////// // add keyword `go` before function calls to run the function concurrently test_func := func(str string) { for i := 0; i < 3; i++ { fmt.Println(str, ":", i) } } go test_func("hello") // anonymous go func(msg string) { fmt.Println(msg) }("world") // wait group var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) go func() { // a wrapper func with to notify finish defer wg.Done() func(id int) { // the actual worker function fmt.Printf("Worker %d starting\n", id) time.Sleep(50 * time.Millisecond) fmt.Printf("Worker %d done\n", id) }(i) }() } wg.Wait() // TODO for error propagation from workders, check errgroup package // https://pkg.go.dev/golang.org/x/sync/errgroup // channels ////////////////////////////////////////////////////////////// // create a new channel with make(chan valType) messages := make(chan string) // send a value to channel with `channel <- thing` go func() { messages <- "ping" }() // receive message from channel with `<- channel` msg := <-messages fmt.Println(msg) // channel with buffering messages_2 := make(chan string, 2) messages_2 <- "hello" messages_2 <- "world" // messages_2 <- "foo" // exceeding buffer limit // fatal error: all goroutines are asleep - deadlock! fmt.Println(<-messages_2) fmt.Println(<-messages_2) // channel and goroutine sync: test_chan_func := func(done chan bool) { fmt.Println("working") time.Sleep(50 * time.Millisecond) done <- true } done := make(chan bool) go test_chan_func(done) <-done // this will block until the goroutine sends to channel // specify channel direction in function parameter ping := func(pings chan<- string, msg string) { pings <- msg } pong := func(pings <-chan string, pongs chan<- string) { msg := <-pings pongs <- msg } pings := make(chan string, 1) pongs := make(chan string, 1) go ping(pings, "hello") go pong(pings, pongs) fmt.Println("received from pongs: ", <-pongs) // select : wait on multiple channels (like unix epoll) c1 := make(chan string, 1) // buffered, non-blocking send c2 := make(chan string, 1) go func() { time.Sleep(1 * time.Second) c1 <- "one" }() go func() { time.Sleep(2 * time.Second) c1 <- "two" }() for i := 0; i < 2; i++ { select { case msg1 := <-c1: fmt.Println("received", msg1) case msg2 := <-c2: fmt.Println("received", msg2) case <-time.After(3 * time.Second): // implements a timenot, note that channels are buffered and sends // are non-blocking fmt.Println("timeout 3") } } // non-blocking channels messages_3 := make(chan string) messages_4 := make(chan string) // messages_3 is unbuffered; send/receive typically blocks // however this select will not block with a default select case // multi-way nonblocking select: select { case msg := <-messages_3: fmt.Println("received message 3", msg) case msg := <-messages_4: fmt.Println("received message 4", msg) default: fmt.Println("no message received") } // TODO closing a channel close(messages_3) close(messages_4) // TODO worker pools // https://gobyexample.com/worker-pools // TODO rate limiting // https://gobyexample.com/rate-limiting // iterate / range over channels messages_5 := make(chan string, 2) messages_5 <- "one" messages_5 <- "two" close(messages_5) // need to close before iterating for elem := range messages_5 { fmt.Println(elem) // one two } // nonblocking send select { case messages <- "hello": fmt.Println("sent message hello") default: fmt.Println("no message sent") } // timers and tickers //////////////////////////////////////////////////// // TODO https://gobyexample.com/timers // TODO https://gobyexample.com/tickers // atomic types ; mutex ////////////////////////////////////////////////// // TODO https://gobyexample.com/atomic-counters // TODO stateful goroutines (BOOKMARK!!!!) // https://gobyexample.com/stateful-goroutines // sugars ..////////////////////////////////////////////////////////////// // 不是,你们go玩家吃的都那么好的吗 // inlined declaration (anonymous type) arr_struct := []struct { i int b bool }{ {1, true}, {2, false}, {3, true}, } fmt.Println(arr_struct) // turn slice into multiple arguments: nums := []int{1, 2, 3, 4} fmt.Println(sums(nums...)) // x - keywords and builtin functions /// fmt.Println("--- [x] - keywords -------") // make, new // other tests /////////////////////////////////////////////////////////// fmt.Println(sums(1, 2, 3, 4)) // use ... to expand slice into variadic attrs fmt.Println(sums(nums...)) // closure: nextInt := intSeq() fmt.Println(nextInt()) // 1 fmt.Println(nextInt()) // 2 fmt.Println(nextInt()) // 3 fmt.Println(nextInt()) // 4 newInt := intSeq() fmt.Println(newInt()) // 1 // return function local reference safely sth := new_something("hello", 42) fmt.Println(*sth) // test methods + auto value/ptr conversion r := rect{width: 10, height: 5} rp := &r fmt.Println("area: ", r.area()) // 50 fmt.Println("perim:", r.perim2()) // 30 fmt.Println("area: ", rp.area()) // 50 fmt.Println("perim:", rp.perim2()) // 30 // test interfaces cir := circle{radius: 5} measure(cir) measure(r) // test enums ns := transition(StateIdle) fmt.Println(ns) ns2 := transition(ns) fmt.Println(ns2) // test generics var test_strarr = []string{"foo", "bar", "zoo"} // can explicitly specify the types but the compiler can infer the types // automatically : below is warning "unnecessary type arguments" // _ = SliceIndex[[]string, string](test_strarr, "zoo") fmt.Println("index of zoo:", SliceIndex(test_strarr, "zoo")) // 2 // test generics (linked list) lst := List[int]{} lst.Push(1) lst.Push(2) lst.Push(3) fmt.Println("list is : ", lst.AllElements()) // test iterators for e := range lst.All() { fmt.Println(e) } // slices.Collect: fmt.Println(slices.Collect(lst.All())) // "infinite" iterator for n := range genFib() { if n >= 10 { break } fmt.Println(n) } } // functions ///////////////////////////////////////////////////////////////// // func sliceInfo(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } // with return type func plusplus(a, b, c int) int { return a + b + c } // with multiple returns, can use blank identifier to ignore a ret value // a, b := vals() // _, b := vals() func vals() (int, int) { return 3, 7 } // variadic functions func sums(nums ...int) int { // nums is slice type: []int total := 0 for _, num := range nums { total += num } return total } // closures (anonymous/lambda functions) // this function returns a function func intSeq() func() int { i := 0 // i is basically a "static" variable to the returned function. // each returned closure instance has its own copy of the variable return func() int { i++ return i } } // recursion func fact(n int) int { if n == 0 { return 1 } return n * fact(n-1) } // go is GC language, can safely return reference to a function local variable type something struct { name string val int } func new_something(name string, val int) *something { p := something{name: name} p.val = val return &p // ok .... } // methods /////////////////////////////////////////////////////////////// type rect struct { width, height float64 } // methods have receiver func (r rect) area() float64 { return r.width * r.height } func (r rect) perim() float64 { return r.width * r.height } // auto conversion between value and pointers func (r *rect) perim2() float64 { return 2 * (2*r.width + 2*r.height) } // interface ///////////////////////////////////////////////////////////// // interfaces are named collections of method signatures type geometry interface { area() float64 perim() float64 } type circle struct { radius float64 } func (c circle) area() float64 { return math.Pi * c.radius * c.radius } func (c circle) perim() float64 { return 2 * math.Pi * c.radius } // take interface as parameter type: i.e. g implements interface geometry func measure(g geometry) { fmt.Println(g) fmt.Println(g.area()) fmt.Println(g.perim()) } // enums ///////////////////////////////////////////////////////////////// type State int const ( // keyword iota generates successive constant values automatically StateIdle State = iota // 0 StateConnected // 1 StateError // 2 StateRetrying // 3 ) var stateName = map[State]string{ StateIdle: "idle", StateConnected: "connected", StateError: "error", StateRetrying: "retrying", } // .String() method is automatically called when e.g. printing as %s func (ss State) String() string { return stateName[ss] } // switch case w/ enums func transition(s State) State { switch s { case StateIdle: return StateConnected case StateConnected, StateRetrying: return StateIdle case StateError: return StateError default: panic(fmt.Errorf("unknown state: %s", s)) } } // generics ////////////////////////////////////////////////////////////// // function takes a slice of a comparable type E // and finds the index of first occurrence of value v // ~ means the type argument for S can be any type whose underlying type is a // slice type // https://stackoverflow.com/questions/70888240/whats-the-meaning-of-the-new-tilde-token-in-go // the [] brackets here is just like <> for c++ and rust func SliceIndex[S ~[]E, E comparable](s S, v E) int { for i := range s { if v == s[i] { return i } } return -1 } type List[T any] struct { head, tail *element[T] // tail is a pointer to an element with type parameter 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} // empty list, create anew lst.tail = lst.head } else { lst.tail.next = &element[T]{val: v} // append to tail lst.tail = lst.tail.next } } func (lst *List[T]) AllElements() []T { var elems []T for e := lst.head; e != nil; e = e.next { elems = append(elems, e.val) } return elems } // (custom) iterators //////////////////////////////////////////////////// // returns an iterator from a linked list func (lst *List[T]) All() iter.Seq[T] { // returns a function, that takes a `yield` function as parameter // yield is called on every element we want to iterate over // * need to check yield return value for early termination return func(yield func(T) bool) { for e := lst.head; e != nil; e = e.next { if !yield(e.val) { return } } } } func genFib() iter.Seq[int] { return func(yield func(int) bool) { a, b := 1, 1 for { if !yield(a) { return } a = b b = a + b } } } // errors //////////////////////////////////////////////////////////////// // TODO https://gobyexample.com/errors // TODO https://gobyexample.com/custom-errors // TODO matching errors (along the chains) // by convention, erros are the last return value and have type error func f(arg int) (int, error) { if arg == 42 { return -1, errors.New("can't work with 42") } return arg + 3, nil } // custom error type type argError struct { arg int message string } // implement method Error() string // to comply with the error interface func (e *argError) Error() string { return fmt.Sprintf("%d - %s", e.arg, e.message) }