Range

range iterates over elements in a variety of built-in data structures.

Arrays and Slice

s := []int{1, 2, 3}
for index, value := range s {
  fmt.Println(index, ": ", value)
}
/*
0: 1
1: 2
2: 3
*/

Maps

kvs := map[string]int{"apple": 1, "banana": 2}
for key, value := range kvs {
  fmt.Printf("%s -> %d\n", key, value)
}
// Iterates key-value pairs
for key := range kvs {
  fmt.Println("key: ", key)
}

Iterator

package main
import (
 "fmt"
 "iter"
 "slices"
 "strings"
)
 
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
 }
}
 
// This method will return a iterator: type of `iter.Seq[T]`
// which is a alias of func(func(T) bool)
func (lst *List[T]) All() iter.Seq[T] {
 return func(yield func(T) bool) {
  for e := lst.head; e != nil; e = e.next {
   if !yield(e.val) {
    return
   }
  }
 }
}
 
/* The iterator function takes another function as a parameter, called yield by convention.
* It will call yield for every element we want to iterate over,
* and note yield’s return value for a potential early termination.
* So, we can write it as:
func (lst *List[T]) All() func(yield func(T) bool) {
 return func(yield func(T) bool) {
  for e := lst.head; e != nil; e = e.next {
   if !yield(e.val) {
    return
   }
  }
 }
}
*/
 
// Iteration doesn’t require an underlying data structure,
// and doesn’t even have to be finite.
func genFib() iter.Seq[int] {
 return func(yield func(int) bool) {
  a, b := 0, 1
 
  for {
   if !yield(a) {
    return
   }
   a, b = b, a+b
  }
 }
}
 
func main() {
 lst := List[int]{}
 lst.Push(100)
 lst.Push(200)
 lst.Push(300)
 
 for e := range lst.All() { // We can traverse this iterator using `range`
  fmt.Println(e)
 }
 
 // Packages like slices have a number of useful functions to work with iterators.
 // For example, Collect takes any iterator and collects all its values into a slice.
 all := slices.Collect(lst.All())
 fmt.Println(all)
 
 // Standard library packages expose iterator helpers too.
 // For example, strings.SplitSeq iterates over parts of a byte slice without first building a result slice.
 for part := range strings.SplitSeq("use-dash-as-split", "-") {
  fmt.Printf("part: %s\n", part)
 }
 
 // Once the loop hits break or an early return, the yield function passed to the iterator will return false.
 for n := range genFib() {
  if n >= 10 {
   break
  }
  fmt.Println(n)
 }
}
 
/* How it works? In fact, compiler will transform our range to a anonymous function.
func main() {
 var myLst List[int] = ...
 
 // Get iterator
 seqFunc := myLst.All()
 
 // Define our loop body function
 myLoopBody := func(v int) bool {
  fmt.Println(v) // The task done in every loop
 
  if v == 20 {
   return false
  }
 
  return true
 }
 
 // Call manually by passing our loop body as a parameter.
 seqFunc(myLoopBody)
}
*/
 
// ---
 
/*
100
200
300
[100 200 300]
part: use
part: dash
part: as
part: split
0
1
1
2
3
5
8
*/

Pointers

Just like pointers in C.

func swap(a, b *int) {
  // `*type` in the pointer of that type.
  c := *a
  *a = *b
  *b = c
  // Dereference like C
}
 
func main() {
  a, b := 1, 2
  swap(&a, &b)
  // Get address like C
  fmt.Println("a: ", a, "\n", "b: ", b)
}
/*
a: 2
b: 1
*/

String and Runes

A Go string is a Slice of bytes(uint8).
The language and std library treat it specially as container of text encoded in UTF-8

In Go, the concept of characters is called rune(int32), an integer represents a Unicode code point.

package main
import (
 "fmt"
 "unicode/utf8"
)
func main() {
 const s = "ハーロー、世界!"
 fmt.Println("Length(in bytes):", len(s))
 // Since string is a []byte, the `len()` will return the length of raw bytes stored.
 // Which usually is no less than the number of ch will return the length of raw bytes stored.
 // Which usually is no less than the number of characters in the string.
 
 for i := 0; i < len(s); i++ {
  fmt.Printf("%x ", s[i]) // Print in hex.
 }
 fmt.Println()
 // Indexing a string will return the raw byte valued at each index.
 
 fmt.Println("Number of characters:", utf8.RuneCountInString(s))
 // We can use `utf8.RuneCountInString()` to count the number of characters in a string.
 // The run-time depends on the length of the string,
 // since it needs to iterate the string and decode it sequentially, the complexity is O(n).
 
 for i, r := range s {
  fmt.Printf("%d: %#U\n", i, r) // Print in char.
 }
 // A range loop handle string especailly,
 // it will decode each rune.
 // So the index maybe jump, since some characters may take more than one byte.
 
 for i, w := 0, 0; i < len(s); i += w {
  runeValue, width := utf8.DecodeRuneInString(s[i:])
  fmt.Printf("%d: %#U\n", i, runeValue)
  w = width
  // Achieve the same result with `utf8.DecodeRuneInString()`,
  // which will return the decoded rune and the width of the rune in bytes.
 
  examineRune(runeValue)
  // Pass Rune as  a parameter.
 }
}
 
func examineRune(runeValue rune) {
 if runeValue == '' {
  // Values enclosed in single quotes are rune literals.
  fmt.Println("Found ハ")
 }
}
/*
Length(in bytes): 24
e3 83 8f e3 83 bc e3 83 ad e3 83 bc e3 80 81 e4 b8 96 e7 95 8c ef bc 81 
Number of characters: 8
0: U+30CF 'ハ'
3: U+30FC 'ー'
6: U+30ED 'ロ'
9: U+30FC 'ー'
12: U+3001 '、'
15: U+4E16 '世'
18: U+754C '界'
21: U+FF01 '!'
0: U+30CF 'ハ'
Found ハ
3: U+30FC 'ー'
6: U+30ED 'ロ'
9: U+30FC 'ー'
12: U+3001 '、'
15: U+4E16 '世'
18: U+754C '界'
21: U+FF01 '!'
*/

Enums

An enum is a type that has a fixed number of possible values, each with a distinct name.

Go doesn’t have an enum type as a distinct language feature, but enums are simple to implement using existing language idioms.

package main
import "fmt"
type Enum int
 
// Define a enum type underlying int,
// so that we can ensure type safety by compiler.
 
const (
 zero  Enum = iota // iota is a predeclared identifier representing successive untyped integer constants.
 one   Enum = iota // iota starts at 0 and increments by 1 for each line in the const block.
 two   Enum = iota // In essence, it is a compile-time row counter.
 three Enum = iota
) // zero is assigned 0, one is assigned 1 and so on.
 
/*
* We can also omit the iota in the subsequent lines, as it will automatically increment by 1 for each line.
* const (
*  zero Enum = iota // zero is assigned 0
*  one              // one is assigned 1
*  two              // two is assigned 2
*  three            // three is assigned 3
* )
* */
 
/*
* We can also use _ to skip a value in the enumeration.
* const (
*  _ Enum = iota // skip 0
*  one           // one is assigned 1
*  two           // two is assigned 2
*  three         // three is assigned 3
* )
* */
 
/*
* Make bit mask using iota is also common.(1 << iota)
* const (
*  one Enum = 1 << iota // read is assigned 1 (1 << 0)
*  two                  // write is assigned 2 (1 << 1)
*  four                 // execute is assigned 4 (1 << 2)
* )
 */
 
func (e Enum) String() string {
 // We can implement the fmt.Stringer interface to print these enum values as string,
 // so that we can disginguish them from regular integers when we print them.
 switch e {
 case zero:
  return "zero"
 case one:
  return "one"
 case two:
  return "two"
 case three:
  return "three"
 default:
  return "unknown"
 }
}
 
/*
* In fact, we can use the stringer tool to automatically generate the String() method for our enum type.
*
* //go:generate stringer -type=Enum
*
* type Enum int
*
* const (
*  ...
* )
*
* Then we can run `go generate` to generate the stringer code,
* which will create a file named enum_string.go with the String() method implemented.
* */
 
func main() {
 e1 := one
 e2 := two
 fmt.Println(e1)
 fmt.Println(e2)
}
/*
* one
* two
*/