Go Details & Tips 101 (2024 - 03 - 16) - Tapir Liu
Go Details & Tips 101 (2024 - 03 - 16) - Tapir Liu
Tapir Liu
Contents
1 Acknowledgments 5
1
3.25 If the left operand of a non-constant bit-shift expression is untyped, then its
type is determined as the assumed type of the expression . . . . . . . . . . 25
3.26 aConstString[i] and aConstString[i:j] are non-constants even if
aConstString, i and j are all constants . . . . . . . . . . . . . . . . . . . . 26
3.27 The result of a conversion to a parameter type is always viewed as a non-
constant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.28 The type deduction rule for a binary operation which operands are both
untyped . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.29 An untyped constant integer may overflow its default type . . . . . . . . . . 29
3.30 The placement of the default branch (if it exists) in a switch code block
could be arbitrary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.31 The constant case expressions in a switch code block may be duplicate or
not, depending on compilers . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.32 The switch expression is optional and its default value is a typed value true
of the built-in type bool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.33 Go compilers will automatically insert some semicolons in code . . . . . . . 32
3.34 What are exactly byte slices (and rune slices)? . . . . . . . . . . . . . . . . 33
3.35 Prior to Go 1.22, freshly-declared iteration variables are shared between loop
iterations; since Go 1.22 freshly-declared iteration variable are instantiatied
per loop iteration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.36 Some unexpected behaviors of the new semantics of 3-clause for-loops . . . 35
3.37 Since Go 1.22, please don’t declare no-copy values as loop variables of 3-
clause for-loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.38 int, false, nil, etc. are not keywords . . . . . . . . . . . . . . . . . . . . . 39
3.39 Selector colliding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.40 Each method corresponds a function which first parameter is the receiver
parameter of that method . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.41 Normalization of method selectors . . . . . . . . . . . . . . . . . . . . . . . 40
3.42 The famous := trap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.43 The official Go compiler checks some potential bugs caused by the := trap
but not all of them . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.44 The meaning of a nil identifier depends on specific context . . . . . . . . . 45
3.45 Some expression evaluation orders are unspecified in Go . . . . . . . . . . . 45
3.46 We can use a generic eval function to convert some non-function-call expres-
sions to function calls, to make some expression evaluation orders determined 48
3.47 We can use the min and max built-in functions (since Go 1.21) as the eval
function for ordered values . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.48 Go supports loop types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.49 Almost any code element could be declared as the blank identifier _ . . . . 51
3.50 Copy slice elements without using the built-in copy function . . . . . . . . . 51
3.51 A detail in const specification auto-complete . . . . . . . . . . . . . . . . . . 51
4 Conversions Related 53
4.1 If the underlying type of a named type is an unnamed type, then values of
one of the named types may be implicitly converted to the underlying type,
and vice versa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.2 Values of two different named pointer types may be indirectly converted
to each other’s type if the base types of the two types shares the same
underlying type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.3 Values of a named bidirectional channel type may not be converted to a
named unidirectional channel type with the same element type directly, but
may indirectly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2
4.4 The capacity of the result of a conversion from string to byte slice is unspecified 56
5 Comparisons Related 58
5.1 Compare two slices which lengths are equal and known at coding time . . . 58
5.2 More ways to compare byte slices . . . . . . . . . . . . . . . . . . . . . . . . 58
5.3 Comparing two interface values produces a panic if the dynamic type of the
two operands are identical and the identical type is an incomparable type . 59
5.4 How to make a struct type incomparable . . . . . . . . . . . . . . . . . . . . 59
5.5 Array values are compared element by element . . . . . . . . . . . . . . . . 60
5.6 Struct values are compared field by field . . . . . . . . . . . . . . . . . . . . 61
5.7 The _ fields are ignored in struct comparisons . . . . . . . . . . . . . . . . . 61
5.8 NaN != NaN, Inf == Inf . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
5.9 How to avoid putting entries with keys containing NaN into a map . . . . . 63
5.10 Some details in using the reflect.DeepEqual function . . . . . . . . . . . . 64
5.11 The return results of the bytes.Equal and reflect.DeepEqual functions
might be different . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
5.12 A type alias embedding bug . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
3
4
Chapter 1
Acknowledgments
Some of the details and tips in this book are collected from the Internet, some ones are
found by myself. I will try to list the source of a detail if it is possible. But I’m sorry that
it is impossible task to do this for every detail.
Thanks to Olexandr Shalakhin for the permission to use one of the wonderful gopher icon
designs in the cover image. And thanks to Renee French for designing the lovely gopher
cartoon character.
Thanks to the authors of the following open source software and libraries, which are used
in building this book:
• golang, https://go.dev/
• gomarkdown, https://github.com/gomarkdown/markdown
• goini, https://github.com/zieckey/goini
• go-epub, https://github.com/bmaupin/go-epub
• pandoc, https://pandoc.org
• calibre, https://calibre-ebook.com/
• GIMP, https://www.gimp.org
Thanks to all contributors for improving this book, including cortes-, Yang Yang, I Putu
Gede Wirasuta, etc.
5
Chapter 2
This book collects many details and provides several tips in Go programming. The details
and tips are categorized into
• syntax and semantics related
• conversions related
• comparisons related
• compiler and runtime related
• standard and user packages related
Most of the details are Go specific, but several of them are language independent.
2.3 Feedback
Welcome to improve this book by submitting corrections to Go 101 issue list (https://gi
thub.com/go101/go101) for all kinds of mistakes, such as typos, grammar errors, wording
inaccuracies, wrong explanations, description flaws, code bugs, etc.
It is also welcome to send your feedback to the Go 101 twitter account: @go100and1
(https://twitter.com/go100and1).
6
Chapter 3
import "unsafe"
type A [0][256]int
type S struct {
x A
y [1<<30]A
z [1<<30]struct{}
}
type T [1<<30]S
func main() {
var a A
var s S
var t T
println(unsafe.Sizeof(a)) // 0
println(unsafe.Sizeof(s)) // 0
println(unsafe.Sizeof(t)) // 0
}
In Go, sizes are often denoted as int values. That means the largest possible length of an
array is MaxInt, which value is 2^63-1 on 64-bit OSes. However, the lengths of arrays with
non-zero element sizes are hard limited by the official standard Go compiler and runtime.
An example:
var x [1<<63-1]struct{} // okay
var y [2000000000+1]byte // compilation error
7
var z = make([]byte, 1<<49) // panic: runtime error: makeslice: len out of range
var g *[0]int
var a, b [0]int
//go:noinline
func f() *[0]int {
return new([0]int)
}
func main() {
// x and y are allocated on stack.
var x, y, z, w [0]int
// Make z and w escape to heap.
g = &z; g = &w
println(&b == &a) // false
println(&x == &y) // false
println(&z == &w) // true
println(&z == f()) // true
}
Please note that, the outputs of the above program depend on specific compilers. The
outputs might be different for future official standard Go compiler versions.
Also note that, in the current implementation (v1.22) of the official standard Go compiler,
although the two comparisons in the above example are both evaluated to false, the
addresses of a and b are actually identical (the same for x and y). The compiler just
deliberately evaluates the two comparisons to false. The fact can be verified by the
following code:
package main
var a, b [0]int
var r, t = &a, &b
func main() {
var p, q = &a, &b
println(&a == &b) // false
println(&a, &b) // (two same addresses)
println(r == t) // true
println(p == q) // false
8
var c, d any = &x, &y
println(&x == &y) // false
println(&x, &y) // (two same addresses)
println(c == d) // true
}
import "unsafe"
type Ty struct {
_ [0]func()
y int64
}
type Tz struct {
z int64
_ [0]func()
}
func main() {
var y Ty
var z Tz
println(unsafe.Sizeof(y)) // 8
println(unsafe.Sizeof(z)) // 16
}
Why the size of the type Tz is larger?
In the current standard Go runtime implementation, as long as a memory block is refer-
enced by at least one alive pointer, that memory block will not be viewed as garbage and
will not be collected.
All the fields of an addressable struct value can be taken addresses. If the size of the final
field in a non-zero-size struct value is zero, then taking the address of the final field in the
struct value will return an address which is beyond the allocated memory block for the
struct value. The returned address may point to another allocated memory block which
closely follows the one allocated for the non-zero-size struct value. As long as the returned
address is stored in an alive pointer value, the other allocated memory block will not get
garbage collected, which may cause memory leaking.
To avoid the kind of memory leak problems, the standard Go compiler will ensure that
taking the address of the final field in a non-zero-size struct will never return an address
which is beyond the allocated memory block for the struct. The standard Go compiler
implements this by padding some bytes after the final zero-size field when needed.
So at least one byte is padded after the final (zero) field of the type Tz. This is why the
size of the type Tz is larger than Ty.
9
In fact, on 64-bit OSes, 8 bytes are padded after the final (zero) field of Tz. To explain
this, we should know two facts in the official standard compiler implementation:
1. The alignment guarantee of a struct type is the largest alignment guarantee of its
fields.
2. A size of a type is always a multiple of the alignment guarantee of the type.
The first fact explains why the alignment guarantee of the type Tz is 8 (which is the
alignment guarantee of the built-in int64 type). The second fact explains why the size of
the type Tz is 16.
Source: https://github.com/golang/go/issues/9401
const N = 8
var n = 8
func main() {
for i := range [N]struct{}{} {
println(i)
}
for i := range [N][0]int{} {
println(i)
}
for i := range make([][0]int, n) {
println(i)
}
}
The steps of the first two loops must be known at compile time, whereas the last one has
not this requirement. But the last one allocates a little more memory (on stack, for the
slice header).
Note: there is a simpler way to do the same job since Go 1.22:
for i := range N {
...
}
The simpler way might be supported as early as Go 1.22.
func main() {
var s0 = make([]int, 100)
10
var s1 = []int{99: 0}
var s2 = (&[100]int{})[:]
var s3 = new([100]int)[:]
// 100 100 100 100
println(len(s0), len(s1), len(s2), len(s3))
}
func main() {
var a = [...]int{1, 2, 3}
for i, n := range a {
if i == 0 {
a[1], a[2] = 8, 9
}
print(n)
}
}
If the ranged container is a large array, then the cost of making the copy will be large.
There is an exception: if the second iteration variable in a for-range is omitted or ignored,
then the ranged container will not get copied, because it is unnecessary to make the copy.
For example, in the following two loops, the array a is not copied.
func main() {
var a = [...]int{1, 2, 3}
for i := range a {
print(i)
}
for i, _ := range a {
print(i)
}
}
In Go, an array owns its elements, but a slice just references its elements. Values are copied
shallowly in Go, copying a value will not copy the values referenced by it. So copying a
slice will not copy its elements. This could be reflected in the following program. The
program prints 189.
package main
func main() {
var s = []int{1, 2, 3}
for i, n := range s {
if i == 0 {
s[1], s[2] = 8, 9
}
print(n)
11
}
}
func main() {
var a = [128]int{3: 789}
var pa = &a
// Iterate array elements without copying array.
for i, v := range pa {
_, _ = i, v
}
// Get array length and capacity.
_, _ = len(pa), cap(pa)
// Access array elements.
_ = pa[3]
pa[3] = 555
// Derive slices from array pointers.
var _ []int = pa[:]
}
Range over a nil array pointer will not panic if the second iteration variable is omitted or
ignored. For example, the first two loops in the following code both print 01234, but the
last one causes a panic.
package main
func main() {
var pa *[5]string
// Prints 01234
for i := range pa {
print(i)
}
// Prints 01234
for i, _ := range pa {
print(i)
}
// Panics
for _, v := range pa {
_ = v
}
}
12
3.8 Some function calls are evaluated at compile time
The function calls evaluated at compile time are also called as constant calls, because their
evaluation results are constant values.
All calls to the unsafe.Sizeof, unsafe.Offsetof and unsafe.Alignof functions are
evaluated at compile time (except that the argument types are parameter types).
If the argument of a call to the built-in len or cap function is a constant string, an array
or a pointer to array, and the argument expression does not contain channel receives or
non-constant function calls, then the call will be evaluated at compile time (except that
the argument types are parameter types).
In evaluating constant calls to the just mentioned functions, only the types of involved
arguments matter (except the arguments are constant strings), even if evaluating such an
argument might cause a panic at run time.
For example, calls of the f and g functions in the following code will not panic at run time.
package main
import "unsafe"
func f() {
var v *int64 = nil
println(unsafe.Sizeof(*v)) // 8
}
func g() {
var t *struct {s [][16]int} = nil
println(len(t.s[99])) // 16
}
func main() {
f()
g()
}
On the other hand, calls of the f2 and g2 functions will cause panics at run time.
func f2() {
var v *int64 = nil
_ = *v
}
func g2() {
var t *struct {s [][16]int} = nil
_ = t.s[99]
}
Please note that the built-in len function is implicitly called in a for-range loop. Knowing
this is the key to understand why the first two loops in the following code don’t cause panics,
but the last one does.
package main
13
type T struct {
s []*[5]int
}
func main() {
var t *T
for i, _ := range t.s[99] { // not panic
print(i)
}
for i := range *t.s[99] { // not panic
print(i)
}
for i := range t.s { // panics
print(i)
}
}
Yes, the implicit len(t.s[99]) and len(*t.s[99]) calls are evaluated at compile time.
Only the length (5 here) of the array value t.s[99] matters in the evaluations. However,
the implicit call len(t.s) is evaluated at run time, so it causes a panic for t is a nil pointer.
As above mentioned, a call to the built-in len or cap function with an argument containing
channel receives or non-constant function calls will not be evaluated at compile time. For
example, the following code doesn’t compile.
var c chan int
var s []byte
const X = len([1]int{<-c}) // error: len(...) is not a constant
const Y = cap([1]int{len(s)}) // error: cap(...) is not a constant
In the following code, the expression imag(X) is a constant function call, but the expression
imag(y) is not (because X is a constant but y is not), so the expression len(A{imag(y)})
will not be evaluated at compile time, which is why the last line doesn’t compile. However,
the expression len(z) doesn’t contain non-constant function calls, so it is viewed as a
constant expression and evaluated at compile time.
const X = 1 + 2i
var y = 1 + 2i
type A [8]float64
14
package main
var x [2000000000+1]byte
func main() {}
The size of a heap-allocated array may be larger than 2GB. For example, the following
program compiles okay, because the two arrays, x and y will be both allocated on heap at
run time.
package main
var y *[2000000000+1]byte
func main() {
var x [2000000000+1]byte
y = &x
}
Source: https://github.com/golang/go/issues/17378
func foo() {
// Literals are unaddressable.
_ = &([10]bool{}[1]) // error
// Map elements are unaddressable.
var mi = map[int]int{1: 0}
_ = &(mi[1]) // error
var ma = map[int][10]bool{2: [10]bool{}}
_ = &(ma[2][1]) // error
_ = &(T{}.x) // error
}
func bar() {
15
var _ = &([]int{1: 0}[1]) // okay
// All variables are addressable.
var a [10]bool
_ = &(a[1]) // okay
var t T
_ = &(t.x) // okay
}
It is also illegal to derive slices from unaddressable arrays. So the following code also fails
to compile.
var aSlice = [10]bool{}[:]
type T struct {
x int
}
func main() {
// All the address taking operations are legal.
_ = &T{}
_ = &[8]byte{}
_ = &[]byte{7: 0}
_ = &map[int]bool{}
}
Please note that the precedences of the index operator [] and property selection operator
. are both higher than the address-taking operator &. For example, both of the two lines
in the following code don’t compile.
_ = &T{}.x // error
_ = &[8]byte{}[1] // error
The reason why they fails to compile is the above code lines are equivalent to the following
lines.
_ = &(T{}.x) // error
_ = &([8]byte{}[1]) // error
On the other hand, the following lines compile okay.
_ = (&T{}).x // okay
_ = (&[8]byte{})[1] // okay
16
3.12 One-line trick to create pointers to a non-zero
bool/numeric/string values
We may take addresses of composite literals, but we may not take addresses of other literals.
For example, all the code lines shown below are illegal.
var pb = &true
var pi = &123
var pb = &"abc"
In fact, we could achieve the similar effects, in one-line but more verbose forms:
var pb = &(&[1]bool{true})[0]
var pi = &(&[1]int{9})[0]
var ps = &(&[1]string{"Go"})[0]
...
}
If there are many other options, the distance from the declaration of x to its use would be
very far. This is not a big problem, but hurts code readability to some extend.
Instead, we could use the following code to avoid the far distance problem:
var cfg = mypkg.Config {
... // many other options
17
...
}
Learned from this issue thread.
type T struct {
x int
}
func main() {
var mt = map[int]T{1: T{x: 2}}
var ma = map[int][3]bool{}
mt[1] = T{x: 3} // okay
ma[1] = [3]bool{0: true} // okay
18
3.15 Use maps to emulate sets
Go supports built-in map types, but doesn’t support set types. We could use map types
to emulate set types. If the element type T of a set type is comparable, then we could use
the type map[T]struct{} to emulate the set type.
package main
func main() {
var s = make(Set)
s.Put(2)
s.Put(3)
println(len(s)) // 2
println(s.Has(3)) // true
println(s.Has(5)) // false
s.Remove(3)
println(len(s)) // 1
println(s.Has(3)) // false
}
If the element type T of a set type is incomparable, we could use a map type map[*byte]T
to emulate the set type, though the functionalities of the set type is reduced much.
An example:
package main
func main() {
var s = make(Set)
19
remove1 := s.Put(func(){ println(111) })
remove2 := s.Put(func(){ println(222) })
for _, f := range s {
f()
}
println(len(s)) // 2
remove1()
println(len(s)) // 1
remove2()
println(len(s)) // 0
}
The base type of the key (pointer) type must not be a zero-size type, otherwise the pointers
created by the new function might be not unique (this is described in a previous section).
The set implementation is simple, but it is only useful for a few scenarios. The trick is
learned from the Tailscale project.
func main() {
var m = map[int]int{3:3, 1:1, 2:2}
for k, v := range m {
print(k, v)
}
}
But please note that, the print functions in the fmt standard package will sort the entries
(by their keys) of a map when printing the map. The same happens for the outputs of calls
to thejson.Marshal function.
func main() {
for k, v := range m {
m[len(m)] = true
println(k, v)
20
}
}
Some possible outputs:
$ go run main.go
0 true
1 true
2 true
3 true
$ go run main.go
0 true
1 true
$ go run main.go
0 true
1 true
2 true
$ go run main.go
1 true
2 true
3 true
4 true
5 true
6 true
7 true
0 true
Please note that, as mentioned above, the entry iteration order is randomized (kind of).
21
Please note that non-constant duplicate keys in map literals lead to unspecified behaviors.
For example, it is okay for the following code to print 1, 2 or 3. Any of these print results
doesn’t violate the Go specification.
package main
var a = 1
func main() {
m := map[int]int{1: 1, a: 2, a: 3}
println(m[1])
}
22
3.22 The return results of a function may be modified
after a return statement is executed
Yes, a deferred function call could modify the named return results of its containing func-
tion. For example, the following program prints 9 instead of 6.
package main
return n + n
}
func main() {
println(triple(3)) // 9
}
func main() {
var f = func (x int) {
println(x)
}
var n = 1
defer f(n)
f = func (x int) {
println(3)
}
n = 2
}
The following program doesn’t panic. It prints 123.
package main
func main() {
var f = func () {
println(123)
}
defer f()
23
f = nil
}
The following program prints 123, then panics.
package main
func main() {
var f func () // nil
defer f()
println(123)
f = func () {
}
}
type T struct{}
func main() {
var t T
defer t.M(1).M(2)
t.M(3)
}
The following example is more natural.
import "sync"
24
func (c *Counter) Unlock() *Counter {
c.mu.Unlock()
return c
}
func main() {
var n = 8
var x byte = 1 << n / 128
print(x) // 0
var y = byte(1 << n / 128)
print(y) // 0
const N = 8
var z byte = 1 << N / 128
println(z) // 2
}
Why an untyped integer in such situations is not deduced as a value of its default type
int? This could be explained by using the following example. If the untyped 1 in the
following code is deduced as an int value instead of an int64 value, then the bit-shift op-
eration will return different results between 32-bit architectures (0) and 64-bit architectures
(0x100000000), which may produce some silent bugs hard to detect in time.
var n = 32
var y = int64(1 << n)
25
The following bit-shift expressions all fail to compile, because the first three untyped integer
1s are both deduced as values of the assume type float64 and the last one is deduced as
a value of the assume type string, whereas floating-point and string values may not be
shifted.
var n = 6
var x float64 = 1 << n // error
var y = float64(1 << n) // error
var z = 1 << n + 1.0 // error
var w = string(1 << n) // error
The following program prints 0 1:
package main
var n = 8
// The assumed type is byte.
var x = 1 << n >> n + byte(0)
// The assumed type is int16.
var y = 1 << n >> n + int16(0)
func main() {
println(x, y) // 0 1
}
Without an assumed type, the untyped left operand will be deduced as its default type. So
the untyped 1 in the following code is deduced as an int value. The variable x is initialized
as 0 on 32-bit architectures (overflows), but as 0x100000000 on 64-bit architectures, which
should not be a surprise to a qualified Go programmer.
var n = 32
var x = 1 << n // an int value
The following code fails to compile, because the untyped 1.0 in the following code is
deduced as float64 value.
var n = 6
var y = 1.0 << n // error
26
package main
func main() {
println(a, b) // 4 0
}
Source: https://github.com/golang/go/issues/28591
func main() {
println(foo()) // 1
println(bar()) // 0
}
Another type parameters related detail: if the type of the argument of a len or cap function
call is a parameter type, then the call is always viewed as a non-constant. For example,
the following program prints 1 0.
package main
const S = "Go"
27
func main() {
var x [8]int
println(ord(x), gen(x)) // 1 0
}
The same situation happens for unsafe.Alignof, unsafe.Offsetof and unsafe.Sizeof
functions. For example, the following program prints 1 0:
package main
import "unsafe"
func main() {
var n int64 = 0
println(f(n), g(n)) // 1 0
}
import "fmt"
const A = 'A' // 65
const B = 66
const C = 67 + 0i
const One = B - A // 1
const Two = C - A // 2
const Three = B / 22.0
func main() {
28
fmt.Printf("%T\n", One) // int32
fmt.Printf("%T\n", Two) // complex128
fmt.Printf("%T\n", Three) // float64
}
The following program prints 01 (on 64-bit architectures), because the kind of the untyped
constant R is viewed as rune (int32).
package main
import "fmt"
var n = 32
func main() {
if R == 1 {
fmt.Print(R << n >> n) // 0
fmt.Print(1 << n >> n) // 1
}
}
The following program prints 2 3.
package main
import "fmt"
const X = 3 / 2 * 2.
const Y = 3 / 2. * 2
var x, y int = X, Y
func main() {
fmt.Println(x, y) // 2 3
}
29
Whereas these following lines are all legal:
const N int = 1 << 200 >> 199
const R rune = 'a' + 1 << 31 - 'b'
var x = 1 << 200 >> 199
var y = 'a' + 1 << 31 - 'b'
switch n {
default: println("n >= 2")
case 0: println("n == 0")
case 1: println("n == 1")
}
switch n {
case 0: println("n == 0")
default: println("n >= 2")
case 1: println("n == 1")
}
}
The same is for the default branch in a select code block.
30
case false: // okay
}
The official standard Go compiler disallows duplicate constant string case expressions, but
gccgo allows.
func main() {
switch {
case x: println("False")
case y: println("True")
}
}
But the following code fails to compile, because MyBool values, x and y, may not compare
with bool values.
package main
func main() {
switch {
case x: // error
case y: // error
}
}
To make it compile, the switch code block should be modified to
switch MyBool(true) {
case x: // okay
case y: // okay
}
or
switch {
case x == true: // okay
case y == true: // okay
}
31
3.33 Go compilers will automatically insert some semi-
colons in code
Let’s view a small program:
package main
func main() {
switch foo()
{
case false: println("False")
case true: println("True")
}
}
What is the output of the above program? Let’s think for a while.
~
~
~
False? No, it prints True. Surprised? Doesn’t the function foo always return false?
Yes, the function foo always returns false, but this is unrelated here.
Compilers will automatically insert some semicolons for the above code as:
package main
func main() {
switch foo();
{
case false: println("False");
case true: println("True");
};
};
Now, it clearly shows that the switch expression (true) is omitted. The switch block is
actually equivalent to:
switch foo(); true
{
case false: println("False");
case true: println("True");
};
That is why the program prints True.
32
About detailed semicolon insertion rules, please read this article.
var x Tx
var y Ty
var s = "Go"
func foo() {
x = Tx(s)
y = Ty(s)
s = string(x)
}
func bar() {
s = string(y) // error (by gc v1.17-)
}
Since version 1.18, gc also has fully adpoted the second interpretation, so the bar function
compiles okay by using gc version 1.18+.
The Go specification formally adopted the second interpretation since Go 1.19
Please note that, when validating the arguments passed to calls of built-in copy and append
functions, the first interpretation should be adopted. The g function in the following code
compiles okay with gccgo (a bug), but fails to compile with gc (the current implementation).
type Tx []byte
type MyByte byte
type Ty []MyByte
var x = make(Tx, 2)
var y = make(Ty, 2)
33
var s = "Go"
func f() {
copy(x, s)
_ = append(x, s...)
}
func g() {
copy(y, s) // error (for gc)
_ = append(y, s...) // error (for gc)
}
The situations are the similar for rune slices.
34
func main() {
var s1 = []int{1, 2, 3}
// Prior to Go 1.22, it prints 333.
// Since Go 1.22, it prints 123.
printAll( loop1(s1) )
var s2 = []int{1, 2, 3}
// It prints 123.
printAll( loop2(s2) )
}
For the same reason, * prior to Go 1.22, the first loop in the following code prints 333,
whereas the second one prints 321. * since Go 1.22, they both print 321.
package main
func main() {
var s = []int{1, 2, 3}
// Prints 333
for _, v := range s {
defer func() {
print(v)
}()
}
// Prints 321
for _, v := range s {
v := v
defer func() {
print(v)
}()
}
}
import "fmt"
func main() {
defer println()
35
for counter, n := 0, 0; n < 3; n++ {
defer func(v int) {
fmt.Print(counter)
counter++
}(n)
}
}
Run it with diffrent Go toolchain versions:
$ gotv 1.21. run demo-defer.go
[Run]: $HOME/.cache/gotv/tag_go1.21.8/bin/go run demo-defer.go
012
$ gotv 1.22. run demo-defer.go
[Run]: $HOME/.cache/gotv/tag_go1.22.1/bin/go run demo-defer.go
000
The second example:
// demo-closure.go
package main
func main() {
var printN func()
for n := 0; n < 9; {
if printN == nil {
printN = func() {
println(n)
}
}
n++
if n == 9 {
break
}
}
printN()
}
Run it with diffrent Go toolchain versions:
$ gotv 1.21. run demo-closure.go
[Run]: $HOME/.cache/gotv/tag_go1.21.8/bin/go run demo-closure.go
9
$ gotv 1.22. run demo-closure.go
[Run]: $HOME/.cache/gotv/tag_go1.22.1/bin/go run demo-closure.go
1
The third example:
// demo-largesize.go
package main
import (
"fmt"
"time"
)
36
type Large [1<<12]byte
func foo() {
for a, i := (Large{}), 0; i < len(a); i++ {
readOnly(&a, i)
}
}
func main() {
bench := func() time.Duration {
start := time.Now()
foo()
return time.Since(start)
}
fmt.Println("elapsed time:", bench())
}
Run it with diffrent Go toolchain versions:
$ gotv 1.21. run demo-largesize.go
[Run]: $HOME/.cache/gotv/tag_go1.21.8/bin/go run demo-largesize.go
elapsed time: 1.836µs
$ gotv 1.22. run demo-largesize.go
[Run]: $HOME/.cache/gotv/tag_go1.22.1/bin/go run demo-largesize.go
elapsed time: 990.421µs
Whop! The performance degradation caused by the Go 1.22 new semantics is so huge!
The 4th example: prior Go 1.22, the code of the following program did concurrency cor-
rectly. But since Go 1.22, it becomes into bad concurrency code.
// demo-concurency.go
package main
import (
"fmt"
"sync"
)
const NumWorkers = 3
func main() {
var c = make(chan uint64)
var m sync.Mutex
for n, i := 0, uint64(0); n < NumWorkers; n++ {
go func() {
for {
37
m.Lock()
i++
v := i
m.Unlock()
if isGold(v) {
c <- v
}
}
}()
}
for n := range c {
fmt.Println("Found gold", n)
}
}
Run it with diffrent Go toolchain versions:
$ CGO_ENABLED=true gotv 1.21. run -race demo-concurency.go
[Run]: $HOME/.cache/gotv/tag_go1.21.8/bin/go run -race demo-concurency.go
Found gold 1048576
Found gold 2097152
Found gold 3145728
^C
$ CGO_ENABLED=true gotv 1.22. run -race demo-concurency.go
[Run]: $HOME/.cache/gotv/tag_go1.22.1/bin/go run -race demo-concurency.go
==================
WARNING: DATA RACE
...
==================
Found gold 1048576
Found gold 1048576
Found gold 1048576
Found gold 2097152
Found gold 2097152
Found gold 2097152
^C
Do you think the semantic changes made for 3-clause for-loops in Go 1.22 is good? Per-
sonally, I think, overall, the impact of the new semantics of for;; loops is negative.
For more unexpected behaviors caused by the semantic changes made in Go 1.22, please
read for-Loop Semantic Changes in Go 1.22: Be Aware of the Impact.
38
for mu := (sync.Mutex{}); aCondition; aPostStatement {
... // use mu
}
func main() {
var s = []bool{true, true, true}
println(s[0]) // false
println(len(s)) // 123
}
type A struct { T1 }
type B struct { T1; T2 }
func main() {
39
var a A
_ = a.m
_ = a.n
var b B
_ = b.m // error: ambiguous selector
_ = b.n // error: ambiguous selector
}
Please note that, the import path of the containing package of a non-exported selector
(either field or method) is an intrinsic property of the selector. Two unexported selectors
with the same name from two different packages will not collide with each other.
For example, in the above example, if the two types T1 and T2 are declared in two different
packages, then the type B will obtain 3 fields and one method.
type T struct {
X int
}
func main() {
var t = T{X: 3}
_ = T.M1(t)
_ = (*T).M1(&t)
_ = (*T).M2(&t)
}
40
forms to their original respective full forms.
The following program prints 0 and 9, because the modification to t1.X has no effects on
the evaluation result of *t1 during evaluating (*t1).M1.
package main
type T struct {
X int
}
func main() {
var t1 = new(T)
var f1 = t1.M1 // <=> (*t1).M1
t1.X = 9
println(f1()) // 0
var t2 T
var f2 = t2.M2 // <=> (&t2).M2
t2.X = 9
println(f2()) // 9
}
In the following code, the function foo runs okay, but the function bar will produce a
panic. The reason is s.M is a simplified form of (*s.T).M. At compile time, the compiler
will normalize the simplified form to it original full form. At runtime, if s.T is nil, then
the evaluation of *s.T will cause a panic. The two modifications to s.T have no effects on
the evaluation result of *s.T.
package main
type T struct {
X int
}
type S struct {
*T
}
func foo() {
var s = S{T: new(T)}
var f = s.M // <=> (*s.T).M
41
s.T = nil
f()
}
func bar() {
var s S
var f = s.M // panic
s.T = new(T)
f()
}
func main() {
foo()
bar()
}
Please note that, interface method values and method values got through reflection will
be expanded to the promoted method values with a delay. For example, in the following
program, the modification to s.T.X has effects on the return results of the method values
got through reflection and interface ways.
package main
import "reflect"
type T struct {
X int
}
type S struct {
*T
}
func main() {
var s = S{T: new(T)}
var f = s.M // <=> (*s.T).M
var g = reflect.ValueOf(&s).Elem().
MethodByName("M").
Interface().(func() int)
var h = interface{M() int}(s).M
s.T.X = 3
println( f() ) // 0
println( g() ) // 3
println( h() ) // 3
}
Source: https://github.com/golang/go/issues/47863
Note, there was a bug in the official standard Go compiler before version 1.20. The older
compilers de-virtualize some interface methods at compile time but the de-virtualizations
42
are made too far to be correct. For example, the following program should print 2 2, but
it prints 1 2 if it is complied with the official standard Go compiler v1.19.
package main
type T struct{
x int
}
func (t T) M() {
println(t.x)
}
func main() {
var t = &T{x: 1}
var i I = t
var f = i.M
defer f() // 2 (correct)
t.x = 2
}
The bug was fixed in Go toolchain 1.20.
Source: https://github.com/golang/go/issues/52072
import "fmt"
import "strconv"
if b {
n = 123
43
}
}
return n, err
}
func main() {
fmt.Println(parseInt("true"))
}
We know that the call strconv.Atoi(s) will return a non-nil error, but the call
strconv.ParseBool(s) will return a nil error. Then, will the call parseInt("true")
return a nil error, too? The answer is it will return a non-nil error. The outputs of the
program is shown below:
err: strconv.Atoi: parsing "true": invalid syntax
err: <nil>
123 strconv.Atoi: parsing "true": invalid syntax
Wait, isn’t the err variable is re-declared in the inner code block and its value has been
modified to nil before the parseInt("true") returns? This is a confusion many new Go
programmers, including me, ever encountered when they just started using Go.
The reason why the call parseInt("true") returns a non-nil error is a variable declared in
an inner code block is never a re-declaration of a variable declared in an outer code block.
Here, the inner declared err variable is set (initialized) as nil. It is not a re-declaration
(a.k.a. modification) of the outer declared err variable. The outer one is set (initialized)
as a non-nil value, then it is never changed later.
There is the voice to remove the ... := ... re-declaration syntax form from Go. But it
looks this is a too big change for Go. Personally, I think explicitly marking the re-declared
variables out is a more feasible solution.
import "fmt"
44
func main() {
fmt.Println(f())
}
The official Go compiler outputs the following error messages:
./main.go:11:3: result parameter err not in scope at return
./main.go:10:8: inner declaration of var err error
In my honest opinion, Go compilers should not do vet jobs.
func main() {
// The left nil is interpreted as a nil pointer value.
// The right nil is interpreted as a nil interface value.
println(box(nil) == nil) // false
var x interface{} = nil
var y chan int = nil
// y is converted to interface{} before comparing.
println(x == y) // false
45
are unspecified. The relative order between a non-function operand and a function call is
also unspecified.
For example, the following program prints two different lines (with Go toolchains before
v1.20). In the first multi-value assignment (re-declaration) statement, the expression a is
evaluated after those function calls. But in the second multi-value assignment statement
(normal variable declaration), the expression a is evaluated before those function calls.
Neither is wrong. In fact, there is a third valid possibility: 3 3 6 (if the expression a is
evaluated between those function calls).
(Note: since Go toolchain v1.20, the program prints two same line: 6 3 6.)
We should not write such unprofessional code in practice.
package main
var a int
func main() {
{
a = 2
x, y, z := a, f(), g()
println(x, y, z) // 6 3 6
}
{
a = 2
var x, y, z = a, f(), g()
println(x, y, z) // 2 3 6
}
}
The following is another unprofessional example, in which the CreateT call might return
a T value which x field might be 53 (gccgo version 12.2.0) or 50 (gc version 1.22).
package main
import (
"errors"
"fmt"
)
type T struct {
x int
}
46
func validate(t *T) error {
if t.x < 0 || t.x > 100 {
return errors.New("T.x out if range")
}
t.x = t.x / 10 * 10
return nil
}
func main() {
var t, _ = CreateT(53)
fmt.Println(t)
}
For the same reason, in the following program, both the bar function and the foo function
may either produce a panic or exit normally, depending on how compilers determine the
relative evaluation order of the f and g() sub-expressions when evaluating f(g()). The
official standard Go compiler v1.21 adopts different relative orders in the foo and bar
functions, so that the foo function will exit normally and the bar function will produce a
panic at run time (in the implementations of v1.22+ versions, both functions will produce
a panic).
package main
func foo() {
f := func(int) {}
g := func() int {
f = nil
return 1
}
defer f(g())
}
func bar() {
f := func(int) {}
g := func() int {
f = nil
return 1
}
f(g())
}
func main() {
foo()
bar()
}
47
3.46 We can use a generic eval function to convert
some non-function-call expressions to function
calls, to make some expression evaluation orders
determined
For example, by using a generic eval function shown below, the following program is
guaranteed to print 2 3 6 and not panic.
package main
// A generic function
func eval[T any](v T) T {
return v
}
var a int
func bar() {
f := func(int) {}
g := func() int {
f = nil
return 1
}
eval(f)(g())
}
func main() {
{
a = 2
x, y, z := eval(a), f(), g()
println(x, y, z) // 2 3 6
}
bar()
}
48
3.47 We can use the min and max built-in functions
(since Go 1.21) as the eval function for ordered
values
Both of the built-in min and max functions can take one argument and return that argument
as the result, which is the same as the behavior of an eval function.
For example, the following program is guaranteed to print 2 3 6:
package main
var a int
func main() {
{
a = 2
x, y, z := min(a), f(), g()
println(x, y, z) // 2 3 6
}
}
Similarly, we can use the append built-in function, which can take only one slice argument,
as the eval function for slice values. For example, the following program is guaranteed to
print 2. However, it may print 2 or 9 without calling the append function.
package main
func main() {
s := []int{1, 2}
f := func() int {
s = []int{8, 9}
return 1
}
println(append(s)[f()]) // 2
49
}
func main() {
type P *P
var pp = new(P)
*pp = pp
_ = ************pp
}
The following program also compiles and runs both okay.
package main
type F func() F
func f() F {
return f
}
func main() {
f()()()()()()()()()
}
Note, the print functions in the standard fmt package don’t work well for loop container
types. For example, the following program crashes (stack overflow):
package main
import "fmt"
func main() {
type S []S
var s = make(S, 1)
s[0] = s
_ = s[0][0][0][0][0][0][0]
fmt.Println(s) // panic
}
50
3.49 Almost any code element could be declared as the
blank identifier _
For example, the following code is legal.
const _ = 123
var _ = false
type _ string
func _() {
_: // a label
return
}
type T struct{
_ []int
}
func (T) _() {}
Package name and interface method names may not be blank identifiers.
const N = 128
var x = []int{N-1: 789}
func main() {
var y = make([]int, N)
*(*[N]int)(y) = *(*[N]int)(x) // <=> copy(y, x)
println(y[N-1]) // 789
}
But please note that there was a bug when copying array/slices with overlapping elements
in this way in some Go toolchain releases (1.17 - 1.17.13, 1.18 - 1.18.5, and 1.19). The bug
has already been fixed since Go toolchain v1.19.1.
const X = 1
func main() {
const (
X = X + 1
Y
51
)
println(X, Y)
}
No nonsense words, this program prints 2 3 when using the official standard Go com-
piler 1.18+ versions, but it prints 2 2 when using the official standard Go compiler 1.17-
versions. In other words, the official standard Go compiler 1.17- versions interpret the auto-
completion rule incorrectly (the global X is used in the complete form of the Y specification,
but the local X should be used instead).
Source: https://github.com/golang/go/issues/49157
52
Chapter 4
Conversions Related
func main() {
var x []byte
var y Bytes
var z MyBytes
f(y)
f(z)
g(x)
g(z) // error: cannot use z (type MyBytes) as Bytes
g(Bytes(z))
h(x)
h(y) // error: cannot use y (type Bytes) as MyBytes
53
h(MyBytes(y))
}
func main() {
var x IntPtr
var y MyIntPtr
x = IntPtr(y) // error
y = MyIntPtr(x) // error
var _ = (*int)(y) // error
var _ = (*MyInt)(x) // error
}
Although the above 4 conversions may not achieved directly, they may be achieved indi-
rectly. This benefits from the fact that the following conversions are legal.
package main
func main() {
var x *int
var y *MyInt
x = (*int)(y) // okay
y = (*MyInt)(x) // okay
}
The reason why the above two conversions are legal is values of two unnamed pointers
types may be converted to each other’s type if the base types of the two types shares the
same underlying type. In the above example, the base types of the types of x and y are
int and MyInt, which share the same underlying type int, so x and y may be converted
to each other’s type.
Benefiting from the just mentioned fact, values of IntPtr and MyIntPtr may be also
converted to each other’s type, though such conversions must be indirectly, as shown in
the following code.
package main
54
type MyInt int
type IntPtr *int
type MyIntPtr *MyInt
func main() {
var x IntPtr
var y MyIntPtr
x = IntPtr((*int)((*MyInt)(y))) // okay
y = MyIntPtr(((*MyInt))((*int)(x))) // okay
var _ = (*int)((*MyInt)(y)) // okay
var _ = (*MyInt)((*int)(x)) // okay
}
func main() {
type C chan string
type Cw chan<- string
type Cr <-chan string
var c C
var w Cw
var r Cr
_, _ = w, r
}
Such conversions are rarely used in practice, but knowing more is not a bad thing, right?
55
4.4 The capacity of the result of a conversion from
string to byte slice is unspecified
The implementation of the addPrefixes function in the following code is unprofessional.
package main
func main() {
var bss = [][]byte {
[]byte("Java"),
[]byte("C++"),
[]byte("Go"),
[]byte("C"),
}
addPrefixes("> ", bss)
println(string(bss[0])) // > Co+a
println(string(bss[1])) // > Co+
println(string(bss[2])) // > Co
println(string(bss[3])) // > C
}
The outputs of the above program (with the official standard Go compiler 1.22 versions):
2 8
> Co+a
> Co+
> Co
> C
The outputs are not what we expect. Why? Because the capacity of the result of the
conversion []byte("> ") is 8 (which is actually compiler dependent). In the end, all of
the elements of bss share some leading bytes with the conversion result. Each append call
overwrite some bytes in the conversion result.
To fix the problem, we should clip the conversion result, so that the elements of bss doesn’t
share bytes. The fixed addPrefixes function implementation:
func addPrefixes(prefixStr string, bss [][]byte) {
var prefix = []byte(prefixStr)
prefix = prefix[:len(prefix):len(prefix)] // clip it
for i, bs := range bss {
bss[i] = append(prefix, bs...)
}
}
Then the outputs will become as expected:
56
> Java
> C++
> Go
> C
57
Chapter 5
Comparisons Related
func main() {
var x = []int{1, 2, 3, 4, 5}
var y = []int{1, 2, 3, 4, 5}
var z = []int{1, 2, 3, 4, 9}
58
• The first way: bytes.Compare(x, y) == 0.
• The second way: string(x) == string(y). Due to an optimization made by the
official standard Go compiler, no underlying bytes will be duplicated in this way. In
fact, the bytes.Equal function uses this way to do the comparison.
The two ways have no requirements on the lengths of the two operand byte slices.
func main() {
var x interface{} = []int{1, 2}
var y interface{} = map[string]int{}
var z interface{} = func() {}
type T2 struct {
_ []int
y bool
}
type T3 struct {
_ map[int]bool
z string
}
59
Lest the _ fields waste memory, their types should be zero-size types. For example, the
size of the type Ty is smaller than the type Tx in the following code.
package main
import "unsafe"
type Tx struct {
_ func()
x int64
}
type Ty struct {
_ [0]func()
y int64
}
func main() {
var x Tx
var y Ty
println(unsafe.Sizeof(x)) // 16
println(unsafe.Sizeof(y)) // 8
}
Please try to avoid putting a zero-size field as the final field of a struct type.
type T [2]interface{}
func main() {
var a = T{1, func(){}}
var b = T{2, func(){}}
println(a == b) // false
60
5.6 Struct values are compared field by field
Similarly, when comparing two struct values, their fields will be compared one by one. Once
two corresponding fields are found unequal, the whole comparison stops and a false result
is resulted. The whole comparison might also stop for a panic produced in comparing two
interfaces.
For example, the first comparison in the following code results in false, but the second one
causes a panic.
package main
type T struct {
x interface{}
y interface{}
}
func main() {
var a = T{x: 1, y: func(){}}
var b = T{x: 2, y: func(){}}
println(a == b) // false
type T struct {
_ int
x string
}
func main() {
var x = T{123, "Go"}
var y = T{789, "Go"}
println(x == y) // true
}
But please note that, as shown in a previous section, if a struct type contains a _ field of
an incomparable type, then the struct type is also incomparable.
61
Every two +Inf (or -Inf) values are equal to each other, but every two NaN values are not
equal.
package main
var a = 0.0
var x = 1 / a // +Inf
var y = x * a // NaN
func main() {
println(x, y) // +Inf NaN
println(x == x) // true
println(y == y) // false
}
As NaN values are not equal to each other, it is always a vain to loop up an entry from a
map by using a NaN key, which could be proved from the following code.
package main
var a = 0.0
var x = 1 / a // +Inf
var y = x * a // NaN
func main() {
var m = map[float64]int{}
m[y] = 123
m[y] = 456
m[y] = 789
q, ok := m[y]
println(q, ok, len(m)) // 0 false 3
}
In fact, comparing a NaN value with any value will result a false result:
package main
var a = 0.0
var y = 1 / a * a // NaN
func main() {
println(y < y) // false
println(y == y) // false
println(y > y) // false
62
var a = 0.0
var y = 1 / a * a // NaN
func main() {
var m = map[float64]int{}
m[y] = 1
m[y] = 2
m[y] = 3
delete(m, y)
delete(m, y)
delete(m, y)
for k, v := range m {
println(k, v)
}
}
The (possible) outputs of the above program:
NaN 3
NaN 1
NaN 2
Note: Go 1.21 introduced a clear built-in function, to clear all entries in a map, including
those with keys as NaN. A demo:
package main
var a = 0.0
var y = 1 / a * a // NaN
func main() {
var m = map[float64]int{}
m[y] = 1
m[y] = 2
m[y] = 3
for k := range m {
delete(m, k)
}
println(len(m)) // 3
clear(m)
println(len(m)) // 0
}
63
struct keys. A universal workable way is to check the result of key != key. If the result
is true, then key must contain NaN, so we should give up putting the entry into the map.
import "reflect"
func main() {
var x, y, z Node
x.peer = &x // form a cyclic reference chain
y.peer = &z // form a cyclic reference chain
z.peer = &y
println(reflect.DeepEqual(&x, &y)) // true
}
When using the reflect.DeepEqual function to compare two function values, the return
result is true only if the two functions share the identical type and they are both nil. For
example, the following program prints true then false.
package main
import "reflect"
func main() {
var x, y func()
println(reflect.DeepEqual(x, y)) // true
var z = func() {}
println(reflect.DeepEqual(z, z)) // false
}
When using the reflect.DeepEqual function to compare two slice values (of the same type
and with the same length), generally, their elements will be compared one by one. However,
if their corresponding first elements have the same address, then true is returned without
comparing their elements, even if their elements are self-unequal values (for example, non-
nil functions and NaNs).
For example, the following program also prints true then false.
package main
64
import "reflect"
func main() {
var f = func() {}
var a = [2]func(){f, f}
var x = a[:]
var y = a[:]
var z = []func(){f, f}
println(reflect.DeepEqual(x, y)) // true
println(reflect.DeepEqual(x, z)) // false
}
Similarly, if two map values are referencing the same underlying hashtable, the result is also
true if they are compared with the reflect.DeepEqual function, even if the hashtable
contains self-unequal values.
package main
import (
"math"
"reflect"
)
func main() {
nan := math.NaN()
println(reflect.DeepEqual(nan, nan)) // false
m1 := map[int]float64{1: nan}
m2 := map[int]float64{1: nan}
m3 := m1
import (
"bytes"
"reflect"
)
func main() {
var x = []byte{}
65
var y []byte
println(bytes.Equal(x, y)) // true
println(reflect.DeepEqual(x, y)) // false
}
func main() {
var x, y interface{} = A{}, B{}
println(x == y) // true (with Go toolchain 1.17-)
}
Source: https://github.com/golang/go/issues/24721
66
Chapter 6
import "sync/atomic"
type T struct {
67
x uint64
}
type S struct {
y int32
t T
}
func main() {
var t T
t.AddX(1) // safe, even on 32-bit architectures
var s S
s.t.AddX(1) // might panic on 32-bit architectures
}
One fact we should be aware of is that the official Go compilers (gc and gccgo) guarantee
that 32-bit and 64-bit words are always 4-byte aligned on any architectures. In fact, the
ever implementation of the sync.WaitGroup type relied upon this fact.
The sync.WaitGroup type needs two fields. Normally, it should be defined as
type WaitGroup struct {
state uint64
sema uint32
}
Here, the state field needs to participate 64-bit atomic operations. However, on 32-
bit architectures, its address is not guaranteed to be 8-byte aligned. So instead, the
sync.WaitGroup type was ever defined as
type WaitGroup struct {
state1 [3]uint32
}
At runtime, the state1 field of a sync.WaitGroup value might 4-byte aligned or 8-byte
aligned. If it is 8-byte aligned, the combination of the first two elements of the state1 field
is viewed as the original state field and the third element is viewed as the original sema
field; otherwise, the combination of the last two elements of the state1 field is viewed as
the original state field and the first element is viewed as the original sema field.
Note, since Go 1.19, two types, sync/atomic.Int64 and sync/atomic.Uint64, have been
supported. The alignments of values of the two types are always 8-byte aligned, on either
64-bit or 32-bit architectures. So since Go 1.20, the declaration of the sync.WaitGroup
type is declared as
type WaitGroup struct {
state atomic.Uint64
sema uint32
}
, to avoid checking alignments of WaitGroup values at runtime.
68
6.3 How to guarantee a struct field to be always 8-byte
aligned
Just use the trick shown below:
import "sync/atomic"
type T struct {
...
_ [0]atomic.Int64
X TypeOfX
...
}
By declaring an anonymous field of type [0]atomic.Int64 closely before the X field decla-
ration, the X field of any T value is guaranteed to be 8-byte aligned. The reason is compilers
must guarantee that the anonymous field of a T value is 8-byte aligned, even if the field
size is zero; consequently, the X field of the T value is also guaranteed to be 8-byte aligned,
whatever the type of the X field is.
type T struct{}
func main() {
var t T
_ = t // warning: assignment copies lock value to _
}
A struct type with a noCopy field (embedding or not) or an array type with noCopy
elements is also a noCopy type. For example:
package main
type T struct{}
69
type S struct {
t T
}
func main() {
var s S
_ = s // warning: assignment copies lock value to _
var a [8]T
_ = a // warning: assignment copies lock value to _
}
import (
"fmt"
"reflect"
u "unsafe"
)
var s = "abc"[0:0]
func main() {
header := (*reflect.StringHeader)(u.Pointer(&s))
if s == "" {
fmt.Printf("%#v\n", *header)
}
}
The reflect.StringHeader type represents the internal structure of the string type.
Run the program, the outputs are like:
reflect.StringHeader{Data:0x4957ec, Len:0}
From the outputs, we could find that the Data field of the zero string s are non-zero, which
doesn’t prevent the runtime from thinking the string s is a zero value. In fact, the zero
length is sufficient to indicate the string s is a blank string.
70
6.7 The address of a value might change at run time
In the official standard Go runtime implementation, the stack of a goroutine will grow or
shrink as needed at run time. The address of a value allocated on a stack will change when
the stack size changes.
For example, the following program very probably prints two different addresses.
package main
//go:noinline
func f(i int) byte {
var a [1 << 12]byte
return a[i]
}
func main() {
var x int
println(&x)
f(100) // make stack grow
println(&x)
}
var s = "1234567890"
func main() {
for condition() {
s += s
println(len(s))
}
}
It is a good idea to limit the number of loop steps to a reasonable number in debugging.
71
func main() {
for range [10]struct{}{} {
if condition() {
break
}
s += s
println(len(s))
}
}
import "runtime"
panic("bye")
}
func main() {
c := make(chan int)
go worker(c)
<-c
}
Source: https://github.com/golang/go/issues/35378
72
func main() {
defer func() {
println("Panic", recover().(int), "is recovered.")
}()
defer println("Now, panic 2 replaces panic 1.")
defer func() {
defer println("Now, 2 panics coexist.")
panic(2)
}()
defer println("Only one panic exists now.")
panic(1)
}
The outputs of the above program:
Only one panic exists now.
Now, 2 panics coexist.
Now, panic 2 replaces panic 1.
Panic 2 is recovered.
import "fmt"
func main() {
defer func() {
fmt.Print(recover())
}()
defer func() {
defer func() {
fmt.Print(recover())
}()
defer recover() // no-op
panic(2)
}()
panic(1)
}
The above program prints 21. If we change the ”no-op” line to a non-deferred call, then
2<nil> will be printed instead.
Please read the article explain panic/recover mechanism in detail for best explanations for
Go panic/recover mechanism.
73
Chapter 7
import (
"errors"
"fmt"
)
74
}
return nil
}
func main() {
println(errors.Is(Foo(), ErrNotImpl)) // false
println(errors.Is(Bar(), ErrNotImpl)) // true
}
In user code, we should try to use the errors.Is function instead of using direct compar-
isons to judge the cause of an error.
import "fmt"
func main() {
// 123 789 abc xyz
println(123, 789, "abc", "xyz")
// 123 789 abc xyz
fmt.Println(123, 789, "abc", "xyz")
// 123 789abcxyz
fmt.Print(123, 789, "abc", "xyz")
println()
// 123789abcxyz
print(123, 789, "abc", "xyz")
println()
}
import "reflect"
type I interface {
m()
75
M()
}
type T struct {}
func (T) m() {}
func (T) M() {}
func main() {
var t T
var i I = t
var vt = reflect.ValueOf(t)
var vi = reflect.ValueOf(&i).Elem()
println(vt.NumMethod()) // 1
println(vi.NumMethod()) // 2
}
func main() {
x = []MyByte(y) // error
y = []byte(x) // error
}
There is a hole to this rule. If the underlying type of the element type of a slice is
byte (such as the MyByte type shown in the above example), then we could use the
reflect.Value.Bytes methods to convert the (byte) slice to []byte. For example:
package main
import "reflect"
76
7.5 Don’t misuse the TrimLeft function as TrimPrefix
in the strings and bytes standard packages
The second parameter of the TrimLeft function is a cutset, any leading Unicode code points
in the first parameter contained in the cutset will be removed, which is quite different from
the TrimPrefix function.
The following program shows the differences.
package main
import "strings"
func main() {
var hw = "DoDoDo!"
println(strings.TrimLeft(hw, "Do")) // !
println(strings.TrimPrefix(hw, "Do")) // DoDo!
}
The same situation is for the TrimRight and TrimSuffix functions.
import (
"encoding/json"
"fmt"
)
type T struct {
HTML string `json:"HTML"`
}
func main() {
var t T
if err := json.Unmarshal([]byte(s), &t); err != nil {
fmt.Println(err)
return
}
fmt.Println(t.HTML) // bar
}
The docs of the json.Unmarshal function states ”preferring an exact match but also accept-
ing a case-insensitive match”. So personally, I think this is a bug in the json.Unmarshal
function implementation, but the Go core team don’t think so.
77
7.7 The spaces in struct tag key-value pairs will not be
trimmed
For example, the following program will print {" foo":""}. The misspelt omitempty
option for the Foo field is different from omitempty, and the tag key of the Foo field is "
foo", instead of "foo".
package main
import (
"encoding/json"
"fmt"
)
type T struct {
Foo string `json:" foo, omitempty"`
Bar string `json:"bar,omitempty"`
}
func main() {
var t T
var s, _ = json.Marshal(t)
fmt.Printf("%s", s) // {" foo":""}
}
78
}
os.Exit(0)
}
Please note that the log.Fatal function calls the os.Exit function, so deferred calls will
also not get executed after the log.Fatal function is called.
func main() {
os.Exit(realMain())
}
return 0
}
const (
ErrA errType = iota
ErrB
ErrC
errCount
)
79
var errDescriptions = [...]string {
ErrA: "error A",
ErrB: "error B",
ErrC: "error C",
}
By using this way, users of the foo package couldn’t modify the declared error values.
type (
typeErrA struct {
_ [0]*typeErrA
}
typeErrB struct {
_ [0]*typeErrB
}
typeErrC struct {
_ [0]*typeErrC
}
)
80