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
*/