This article will list all kinds of details in Go. Some of these details are shown in other Go 101 articles, some are shown in the following sections.
default
branch in switch
and select
blocks can be put before all case
branches, after all case
branches, or between case
branches.
switch
block can't be duplicate, but boolean ones can.
switch
block are always evaluated to typed values.
switch
block is a typed value true
of the predeclared type bool
.
{
of an explicit code block can be put on the next line.
case
branch blocks must be explicit.
recover
calls may be NoOps.
os.Exit
function call and exit a goroutine with a runtime.Goexit
function call.
++
and the decrement --
is lower than the dereference operator *
and the address-taken operator &
, which are lower than the property selection operator .
in selectors.
append
function call may share some elements with the original slice, or not.
NaN
as keys to a map is like putting the elements in a black hole.
s
, the loop for i = range s {...}
is not equivalent to the loop for i = 0; i < len(s); i++ {...}
.
error
values returned by two errors.New
calls with the same argument are not equal.
reflect.DeepEqual(x, y)
and x == y
may be different.
os.IsNotExist(err)
instead of err == os.ErrNotExist
to check whether or not a file exists.
flag
standard package treats boolean command flags differently than integer and string flags.
[Sp|Fp|P]rintf
functions support positional arguments.
A Go source file can imports the same package multiple times, but the import names must be different. These same-package imports reference the same package instance.
For example:package main
import "fmt"
import "io"
import inout "io"
func main() {
fmt.Println(&inout.EOF == &io.EOF) // true
}
// import "x.y.z/mypkg"
following package mypkg
is meaningful for the standard Go compiler."x.y.z/mypkg"
.
package mypkg // import "x.y.z/mypkg"
...
default
branch in switch
and select
blocks can be put before all case
branches, after all case
branches, or between case
branches. switch n := rand.Intn(3); n {
case 0: fmt.Println("n == 0")
case 1: fmt.Println("n == 1")
default: fmt.Println("n == 2")
}
switch n := rand.Intn(3); n {
default: fmt.Println("n == 2")
case 0: fmt.Println("n == 0")
case 1: fmt.Println("n == 1")
}
switch n := rand.Intn(3); n {
case 0: fmt.Println("n == 0")
default: fmt.Println("n == 2")
case 1: fmt.Println("n == 1")
}
var x, y chan int
select {
case <-x:
case y <- 1:
default:
}
select {
case <-x:
default:
case y <- 1:
}
select {
default:
case <-x:
case y <- 1:
}
switch
block can't be duplicate, but boolean ones can.package main
func main() {
switch 123 {
case 123:
case 123: // error: duplicate case
}
}
But the following program compiles okay.
package main
func main() {
switch false {
case false:
case false:
}
}
For reasons, please read this issue. The behavior is compiler dependent. In fact, the standard Go compiler also doesn't allow duplicate string case expressions, but gccgo allows.
switch
block are always evaluated to typed values.123
in the following switch
block
is viewed as a value of int
instead of an untyped integer.
So the following program fails to compile.
package main
func main() {
switch 123 {
case int64(123): // error: mismatched types
case uint32(789): // error: mismatched types
}
}
switch
block is a typed value true
of the predeclared type bool
.true
.
package main
import "fmt"
func main() {
switch { // <=> switch true {
case true: fmt.Println("true")
case false: fmt.Println("false")
}
}
{
of an explicit code block can be put on the next line.package main
func main() {
var i = 0
Outer:
for
{ // okay on the next line
switch
{ // okay on the next line
case i == 5:
break Outer
default:
i++
}
}
}
What result will the following program print? true
or false
?
The answer is true
.
Please read line break rules in Go for reasons.
package main
import "fmt"
func False() bool {
return false
}
func main() {
switch False()
{
case true: fmt.Println("true")
case false: fmt.Println("false")
}
}
case
branch blocks must be explicit.func demo(n, m int) (r int) {
switch n {
case 123:
if m > 0 {
goto End
}
r++
End: // syntax error: missing statement after label
default:
r = 1
}
return
}
To make it compile okay, the case
branch code block should be explicit:
func demo(n, m int) (r int) {
switch n {
case 123: {
if m > 0 {
goto End
}
r++
End:
}
default:
r = 1
}
return
}
Alternatively, we can let a semicolon follow the label End:
:
func demo(n, m int) (r int) {
switch n {
case 123:
if m > 0 {
goto End
}
r++
End:;
default:
r = 1
}
return
}
Please read line break rules in Go for reasons.
package main
import "fmt"
func F() (r int) {
defer func() {
r = 789
}()
return 123 // <=> r = 123; return
}
func main() {
fmt.Println(F()) // 789
}
recover
calls may be NoOps.
We should call the recover
function at the right places.
Please read the right places to call the built-in recover
function for details.
os.Exit
function call and exit a goroutine with a runtime.Goexit
function call.
We can exit a program from any function by calling the os.Exit
function.
An os.Exit
function call takes an int
code as
argument and returns the code to operating system.
// exit-example.go
package main
import "os"
import "time"
func main() {
go func() {
time.Sleep(time.Second)
os.Exit(1)
}()
select{}
}
Run it:
$ go run a.go
exit status 1
$ echo $?
1
We can make a goroutine exit by calling the runtime.Goexit
function.
The runtime.Goexit
function has no parameters.
Java
word will not be printed.
package main
import "fmt"
import "runtime"
func main() {
c := make(chan int)
go func() {
defer func() {c <- 1}()
defer fmt.Println("Go")
func() {
defer fmt.Println("C")
runtime.Goexit()
}()
fmt.Println("Java")
}()
<-c
}
++
and the decrement --
is lower than the dereference operator *
and the address-taken operator &
, which are lower than the property selection operator .
in selectors.package main
import "fmt"
type T struct {
x int
y *int
}
func main() {
var t T
p := &t.x // <=> p := &(t.x)
fmt.Printf("%T\n", p) // *int
*p++ // <=> (*p)++
*p-- // <=> (*p)--
t.y = p
a := *t.y
fmt.Printf("%T\n", a) // int
}
package main
func main() {
}
const M = 2
var _ = 1.0 << M // compile okay. 1.0 is deduced as an int value.
var N = 2
var _ = 1.0 << N // fail to compile. 1.0 is deduced as a float64 value.
Please read this article for reasons.
package main
type MyInt int64
type Ta *int64
type Tb *MyInt
func main() {
var a Ta
var b Tb
// Direct conversion is impossible.
//a = Ta(b) // error: fail to compile
// But indirect conversion is possible.
y := (*MyInt)(b)
x := (*int64)(y)
a = x // <=> the next line
a = (*int64)(y) // <=> the next line
a = (*int64)((*MyInt)(b))
_ = a
}
package main
import "fmt"
func main() {
a := struct{}{}
b := struct{}{}
x := struct{}{}
y := struct{}{}
m := [10]struct{}{}
n := [10]struct{}{}
o := [10]struct{}{}
p := [10]struct{}{}
fmt.Println(&x, &y, &o, &p)
// For the standard Go compiler (1.11),
// x, y, o and p escape to heap,
// but a, b, m and n are allocated on stack.
fmt.Println(&a == &b) // false
fmt.Println(&x == &y) // true
fmt.Println(&a == &x) // false
fmt.Println(&m == &n) // false
fmt.Println(&o == &p) // true
fmt.Println(&n == &p) // false
}
The outputs indicated in the above code are for the standard Go compiler 1.11.
package main
func main() {
type P *P
var p P
p = &p
p = **************p
}
Similarly,
package main
func main() {
type S []S
type M map[string]M
type C chan C
type F func(F) F
s := S{0:nil}
s[0] = s
m := M{"Go": nil}
m["Go"] = m
c := make(C, 3)
c <- c; c <- c; c <- c
var f F
f = func(F)F {return f}
_ = s[0][0][0][0][0][0][0][0]
_ = m["Go"]["Go"]["Go"]["Go"]
<-<-<-c
f(f(f(f(f))))
}
For a pointer value, which type is either defined or not, if the base type of its (pointer) type is a struct type, then the pointer value can select the fields of the struct value it references. However, if the type of the pointer value is a defined type, the value can not select the methods of the value its references.
package main
type T struct {
x int
}
func (T) m(){} // T has one method.
type P *T // a defined one-level pointer type.
type PP *P // a defined two-level pointer type.
func main() {
var t T
var tp = &t
var tpp = &tp
var p P = tp
var pp PP = &p
tp.x = 12 // okay
p.x = 34 // okay
pp.x = 56 // error: type PP has no field or method x
tpp.x = 78 // error: type **T has no field or method x)
tp.m() // okay. Type *T also has a "m" method.
p.m() // error: type P has no field or method m
pp.m() // error: type PP has no field or method m
tpp.m() // error: type **T has no field or method m
}
Please read this for details.
Please read this for details.
Foo1
and the Foo2
functions are equivalent,
but the function Foo2
is much tidier than the function Foo1
.
func Foo1(m map[string]int) int {
if m != nil {
return m["foo"]
}
return 0
}
func Foo2(m map[string]int) int {
return m["foo"]
}
package main
func main() {
var m map[string]int // nil
delete(m, "foo")
}
append
function call may share some elements with the original slice, or not.Please read this for details.
package main
import "fmt"
func main() {
s := make([]int, 3, 9)
fmt.Println(len(s)) // 3
s2 := s[2:7]
fmt.Println(len(s2)) // 5
}
Please read this for details.
package main
import "fmt"
func main() {
var x []int // nil
a := x[:]
b := x[0:0]
c := x[:0:0]
// Print three "true".
fmt.Println(a == nil, b == nil, c == nil)
}
Please read this for details.
package main
func main() {
var s []int // nil
for range s {
}
var m map[string]int // nil
for range m {
}
}
01234
.
package main
import "fmt"
func main() {
var a *[5]int // nil
for i, _ := range a {
fmt.Print(i)
}
}
We can modify the length and capacity of a slice separately through the reflection way. Please read this for details.
var k = 1
var x = [2]int{k: 1} // error: index must be non-negative integer constant
var y = []int{k: 1} // error: index must be non-negative integer constant
Note, the keys in map composite literals are not required to be constants.
// error: duplicate index in array literal: 1
var a = []bool{0: false, 1: true, 1: true}
// error: duplicate index in array literal: 0
var b = [...]string{0: "foo", 1: "bar", 0: "foo"}
// error: duplicate key "foo" in map literal
var c = map[string]int{"foo": 1, "foo": 2}
This feature can be used to assert some conditions at compile time.
The reason is the elements of an array value and the array will be stored in the same memory block when the array is stored in memory. But the situation is different for slices.
An example:package main
func main() {
// Container composite literals and map elements are all unaddressable.
// Take container element addresses.
_ = &[]int{1}[0] // ok
_ = &[5]int{}[0] // error: cannot take the address of [5]int literal[0]
_ = &(&[5]int{})[0] // ok
_ = &(*&[5]int{})[0] // ok
// Modify container element values.
map[int]int{}[1] = 9
[]int{1,2,3}[1] = 9
[3]int{1,2,3}[1] = 9 // error: cannot assign to [3]int literal[1]
(&[3]int{1,2,3})[1] = 9
}
The reason is the same as the last detail.
An example:package main
func main() {
// Literal values and map elements are unaddressable in Go.
// The following lines fail to compile.
/*
_ = [...]int{6, 7, 8, 9}[1:3] // error: slice of unaddressable value
_ = &([...]int{6, 7, 8, 9}[0]) // error: cannot take element address
var ma = map[string][4]int{"abc": {0, 1, 2, 3}}
_ = ma["abc"][1:3] // error: slice of unaddressable value
_ = &(ma["abc"][0]) // error: cannot take element address
*/
// The following lines compile okay.
_ = []int{6, 7, 8, 9}[1:3]
_ = &([]int{6, 7, 8, 9}[0])
var ms = map[string][]int{"abc": {0, 1, 2, 3}}
_ = ms["abc"][1:3]
_ = &(ms["abc"][0])
}
NaN
as keys to a map is like putting the elements in a black hole.NaN != NaN
(see the above detail).
The elements with NaN
as key can only be found out in a for-range
loop.
package main
import "fmt"
import "math"
func main() {
var a = math.NaN()
fmt.Println(a) // NaN
var m = map[float64]int{}
m[a] = 123
v, present := m[a]
fmt.Println(v, present) // 0 false
m[a] = 789
v, present = m[a]
fmt.Println(v, present) // 0 false
fmt.Println(m) // map[NaN:<nil> NaN:<nil>]
delete(m, a) // no-op
fmt.Println(m) // map[NaN:<nil> NaN:<nil>]
for k, v := range m {
fmt.Println(k, v)
}
// the above loop outputs:
// NaN 123
// NaN 789
}
We should not assume the length and the capacity of the result slice are always equal.
In the following example, if the lastfmt.Println
line is removed,
the outputs of the two lines before it print the same value 32
,
otherwise, one print 32
and one print 8
.
(For the standard Go compiler 1.11.)
package main
import "fmt"
func main() {
s := "a"
x := []byte(s) // len(s) == 1
fmt.Println(cap([]byte(s))) // 32
fmt.Println(cap(x)) // 8
fmt.Println(x)
}
Some buggy code will be written if we assume the length and the capacity of the result slice are always equal.
s
, the loop for i = range s {...}
is not equivalent to the loop for i = 0; i < len(s); i++ {...}
.i
may be different for the two loops.
package main
import "fmt"
var i int
func fa(s []int, n int) int {
i = n
for i = 0; i < len(s); i++ {}
return i
}
func fb(s []int, n int) int {
i = n
for i = range s {}
return i
}
func main() {
s := []int{2, 3, 5, 7, 11, 13}
fmt.Println(fa(s, -1), fb(s, -1)) // 6 5
s = nil
fmt.Println(fa(s, -1), fb(s, -1)) // 0 -1
}
Please read this for details.
Please read this for details.
Please read this for details.
package main
func main() {
var x interface{} = []int{}
_ = x == x // panic
}
package main
type Foo interface {
foo()
}
type T int
func (T) foo() {}
func main() {
var x interface{} = T(123)
var _ Foo = x // error: interface {} does not implement Foo
var _ = Foo(x) // error: interface {} does not implement Foo
var _ = x.(Foo) // okay
}
package main
func main() {
var x interface{} = true
_, _ = x.(int) // assertion fails, but doesn't cause a panic.
_ = x.(int) // assertion fails, which causes a panic.
}
error
values returned by two errors.New
calls with the same argument are not equal.errors.New
function will copy the input string argument
and use a pointer to the copied string as the dynamic value of the returned error
value.
Two different calls will produce two different pointers.
package main
import "fmt"
import "errors"
func main() {
notfound := "not found"
a, b := errors.New(notfound), errors.New(notfound)
fmt.Println(a == b) // false
}
package main
func main() {
}
func foo(c <-chan int) {
close(c) // error: cannot close receive-only channel
}
package main
func main() {
var c = make(chan bool)
close(c)
select {
case <-c:
case c <- true: // panic: send on closed channel
default:
}
}
package main
func main() {
type T struct{}
type S = []int
}
Please read this FAQ item for details.
package main
import "fmt"
import "math"
func main() {
var a = math.Sqrt(-1.0)
fmt.Println(a) // NaN
fmt.Println(a == a) // false
var x = 0.0
var y = 1.0 / x
var z = 2.0 * y
fmt.Println(y, z, y == z) // +Inf +Inf true
}
foo
:
package foo
type I = interface {
about() string
}
type S struct {
a string
}
func (s S) about() string {
return s.a
}
and the following types are declared in package bar
:
package bar
type I = interface {
about() string
}
type S struct {
a string
}
func (s S) about() string {
return s.a
}
then,
S
from the two packages
can't be converted to each other.
S
from the two packages
denote two distinct method sets.
foo.S
doesn't implement the interface type bar.I
.
bar.S
doesn't implement the interface type foo.I
.
package main
import "包2/foo"
import "包2/bar"
func main() {
var x foo.S
var y bar.S
var _ foo.I = x
var _ bar.I = y
// The following lines fail to compile.
x = foo.S(y)
y = bar.S(x)
var _ foo.I = y
var _ bar.I = x
}
package main
type T struct{x, y int}
func main() {
// Each of the following three lines makes code fail to compile.
// Some "{}" confuse compilers.
/*
if T{} == T{123, 789} {}
if T{} == (T{123, 789}) {}
if (T{}) == T{123, 789} {}
*/
// We must add parentheses like the following two lines to
// make code compile okay.
if (T{} == T{123, 789}) {}
if (T{}) == (T{123, 789}) {}
}
package main
func f() {
f()
}
func main() {
f()
}
the running result:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
runtime stack:
...
Please read expression evaluation orders in Go for details.
reflect.DeepEqual(x, y)
and x == y
may be different.Please read this for details.
os.IsNotExist(err)
instead of err == os.ErrNotExist
to check whether or not a file exists.err == os.ErrNotExist
may miss errors.
package main
import (
"fmt"
"os"
)
func main() {
_, err := os.Stat("a-nonexistent-file.abcxyz")
fmt.Println(os.IsNotExist(err)) // true
fmt.Println(err == os.ErrNotExist) // false
}
flag
standard package treats boolean command flags differently than integer and string flags.-flag
, for boolean flags only.
-flag=x
, for any flag.
-flag x
, for non-boolean flags only.
And please note that, all items following a boolean flag with the first form are viewed as arguments.
package main
import "fmt"
import "flag"
var b = flag.Bool("b", true, "a boolean flag")
var i = flag.Int("i", 123, "an integer flag")
var s = flag.String("s", "hi", "a string flag")
func main() {
flag.Parse()
fmt.Print("b=", *b, ", i=", *i, ", s=", *s, "\n")
fmt.Println("arguments:", flag.Args())
}
If we run the following program with the below shown flags and arguments
./exampleProgram -b false -i 789 -s bye arg0 arg1
the output will be
b=true, i=123, s=hi
arguments: [false -i 789 -s bye arg0 arg1]
This output is obviously not what we expect.
We should pass the flags and arguments like./exampleProgram -b=false -i 789 -s bye arg0 arg1
or
./exampleProgram -i 789 -s bye -b arg0 arg1
to get the output we expect:
b=true, i=789, s=bye
arguments: [arg0 arg1]
[Sp|Fp|P]rintf
functions support positional arguments.
The following program will print 3, 2, 1
.
package main
import "fmt"
func main() {
fmt.Printf("%[3]v, %[2]v, %[1]v", 1, 2, 3) // 3, 2, 1
}
The Go 101 project is hosted on both Github and Gitlab. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.
Support Go 101 by playing Tapir's games.