Go Details 101

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.

A package can be imported more than once in a source file.

A Go source file can imports the same package multiple times, but the import names must be different. These same-pacakge 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
}

The comment // import "x.y.z/mypkg" following package mypkg is meaningful for the standard Go compiler.

For example, when the source files importing this package are compiled by the standard Go compiler, the import path of the following package must be "x.y.z/mypkg".
package mypkg // import "x.y.z/mypkg"
...

The default branch in switch and select blocks can be put before all case branches, after all case branches, or between case branches.

For example:
	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:
	}

The numeric constant case expressions in a switch block can't be deplicate, but boolean ones can.

For example, the following program fails to compile.
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.

The switch expressions in switch block are always evaluated to typed values.

For example, the switch expression 123 in the following switch block is viewed as a value of int instead of an untype 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
	}
}

The default switch expression of a switch block is a typed value true of the predeclared type bool.

For example, the following program will print true.
package main

import "fmt"

func main() {
	switch { // <=> switch true {
	case true:  fmt.Println("true")
	case false: fmt.Println("false")
	}
}

Sometimes, the open brace { of an explicit code block can be put on the next line.

For example:
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")
	}
}

Some case branch blocks must be explicit.

For example, the following program fails to compile.
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.

A nested deferred function calls can modify return result values of its innermost nesting function.

For example:
package main

import "fmt"

func F() (r int) {
	defer func() {
		r = 789
	}()

	return 123 // <=> r = 123; return
}

func main() {
	fmt.Println(F()) // 789
}

Some 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.

Exit a program with a 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.

An example:
// 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.

In the following example, the 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
}

The precedences of the increment operator ++ and the decrement -- are lower than the dereference operator * and the address-taken operator &, which are lower than the property accessment operator . in selectors.

For example:
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
}

The type deduction rule for the left untyped operand of a bit-shift operation depends on whether or not the right operand is a constant.

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.

Values of two pointer types with different underlying types can be converted to each other if the base types of their underlying types share the same underlying type.

An example:
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
}

Addresses of different zero-sized values may be equal, or not.

Whether or not the addresses of two zero-sized values are equal is compiler and compiler version dependent.
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.

The base type of a pointer type may be the pointer type itself.

An example:
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))))
}

A detail about selector shorthands.

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 access the fields of the struct value it references. However, if the type of the pointer value is a defined type, the value can not access 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
}

Sometimes, nested composite Literals can be simplified.

Please read this for details.

In some scenarios, it is ok to use array pointers as arrays.

Please read this for details.

Retreiving elements from nil maps will not panic. The result is a zero element value.

For example, the 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"]
}

Deleting an entry from a nil map will not panic. It is a no-op.

For example, the following program will not panic.
package main

func main() {
	var m map[string]int // nil
	delete(m, "foo")
}

The result slice of an append function call may share some elements with the original slice, or not.

Please read this for details.

The length of a subslice may be larger than the base slice the subslice derives from.

For example,
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.

Deriving a subslice from a nil slice is ok if all the indexes used in the subslice expression are zero. The result subslice is also a nil slice.

For example, the following program will not panic at run time.
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.

Ranging over a nil maps or a nil slices is ok, it is a no-op.

For example, the following program compiles okay.
package main

func main() {
	var s []int // nil
	for range s {
	}

	var m map[string]int // nil
	for range m {
	}
}

Range over a nil array pointer is ok if the second iteration variable is ignored or omitted.

For example, the following program will print 01234.
package main

import "fmt"

func main() {
	var a *[5]int // nil
	for i, _ := range a {
		fmt.Print(i)
	}
}

The length and capacity of a slice can be modified seperately.

We can modify the length and capacity of a slice seperately through the reflection way. Please read this for details.

The indexes in slice and array composite literals must be constants and non-negative.

For example, the following code fails to compile.
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.

The constant indexes or keys in slice/array/map composite literals can't be duplicate.

For example, the following code fails to compile.
// 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.

Elements of unaddressable arrays are also unaddressable, but elements of unaddressable slices are always addressable.

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
}

It is ok to derive subslices from unaddressable slices, but not ok from unaddressable arrays. It is ok to take addresses for elements of unaddressable slices, but not ok for elements of unaddressable arrays.

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])
}

Putting elements with NaN as keys to a map is like putting the elements in a black hole.

This reason is 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
}

The capacity of the result slice of a conversion from a string to byte/rune slice may be larger than the length of the result slice.

We should not assume the length and the capacity of the result slice are always equal.

In the following example, if the last fmt.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.

For a slice s, the loop for i = range s {...} is not equivalent to the loop for i = 0; i < len(s); i++ {...}.

The respective final values of the iteration variable 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
}

A multi-result function call can't mix with other expessions when the call is used as the sources in an assignment or the arguments of another function call.

Please read this for details.

Some function calls are evaluated at compile time.

Please read this for details.

Each method corresponds to an implicit function.

Please read this for details.

Comparing two interface values with the same dynamic uncomparable type produces a panic.

For example:
package main

func main() {
	var x interface{} = []int{}
	_ = x == x // panic
}

Type assertions can be used to convert a value of an interface type to another interface type, even if the former interface type doesn't implement the latter one.

For example:
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
}

Whether or not the second optional result of a type assertion is present will affect the behavior of the type assertion.

If the second optional result presents in a failed type assertion, the type assertion will not produce a panic. Otherwise, a panic will occur. For example:
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.
}

Two error values returned by two errors.New calls with the same argument are not equal.

The reason is the 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
}

Receive-only channels can't be closed.

For example, the following code fails to compile.
package main

func main() {
}

func foo(c <-chan int) {
	close(c) // error: cannot close receive-only channel
}

Sending a value to a closed channel is viewed as a non-blocking operation, and this operation causes a panic.

For example, in the following program, the second case branch will get selected, it will produce a panic at run time.
package main

func main() {
	var c = make(chan bool)
	close(c)
	select {
	case <-c:
	case c <- true: // panic: send on closed channel
	default:
	}
}

Types can be declared within function bodies.

Types can be declared in function bodies. For example,
package main

func main() {
	type T struct{}
	type S = []int
}

For the standard compiler, zero-sized fields in a struct may be treated as one-byte-sized value.

Please read this FAQ item for details.

NaN != NaN, Inf == Inf.

This follows IEEE-754 standard and is consistent with most other programming languages:
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
}

Non-exported method names and struct field names from different packages are viewed as diffferent names.

For example, if the following types are declared in package 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,
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
}

Parentheses are required in several rare scenarios to make code compile okay.

For example:
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}) {}
}

Stack overflow is not panic.

Stack overflow in Go is fatal erorr. Once a stack overflow happens, program will crash with no recovery ways.
package main

func f() {
	f()
}

func main() {
	f()
}
the running result:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
...

Some expression evaluation orders in Go are compiler implementation dependent.

Please read expression evaluation orders in Go for details.

The results of reflect.DeepEqual(x, y) and x == y may be different.

Please read this for details.

We should use os.IsNotExist(err) instead of err == os.ErrNotExist to check whether or not a file exists.

Using 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
}

The flag standard package treats boolean command flags differently than integer and string flags.

There are three forms to pass flag options.
  1. -flag, for boolean flags only.
  2. -flag=x, for any flag.
  3. -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.

An example:
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. Cryptocurrency donations are also welcome:
Bitcoin: 1xucQbv5jujFPPwhyg395ri5yV71hx9g9
Ethereum: 0x5dc4aa2c2bbfaadae373dadcfca11b3358912212