The Basics: Part I
Variables
Simple declarations
var x int = 42
var (
y = 2
z = 2.01
)
The short variable declaration operator
x := 42
This is designed to allow for concise declaration and initialization of variables within the scope of a function.
Debugging method: printing out a variable’s type and value:
x := 42.0
fmt.Printf("%T %[1]v", x)
The output would be float64 42. In Go’s fmt package, the notation [] is for specifying argument indexes. In this case, the [1] in the formatting verb %v makes it use the first argument provided to %T.
Initialization
In Go, all variables are initialized upon declaration, and, by default, “zero”.
Numerical types get 0, string gets "", an empty string with the length of 0. bool gets false, and everything else gets nil.
Constants
Only numbers, strings and booleans can be constants and they are immutable.
const (
a = 1
b float64 = 2.0
c = "c"
d = a == b // false
)
Program: calculating averages
var sum float64
var n int
for {
var val float64
if _, err := fmt.Scanln(&val); err != nil {
break
}
sum += val
n++
}
fmt.Printf("The average is %v", sum/float64(n))
To end the for-loop and get the result, simply hit control+D(EOF) to cause an error.
Strings
In Go, these types are related to strings: byte(synonym for uint8), rune(synonym for int32 for characters, 4 bytes) and string, which is immutable.
Strings in Go are all Unicode. They are,
- physically, a sequence of bytes(UTF-8 encoding).
- logically, a sequence of runes(Unicode).
Bytes vs. Runes & String length
To compare, we can test with the code below.
s := "Hello, 世界"
fmt.Printf("%T %[1]v %d\n", []rune(s), len([]rune(s)))
fmt.Printf("%T %[1]v %d\n", []byte(s), len([]byte(s)))
fmt.Println(len(s))
// Output
[]int32 [72 101 108 108 111 44 32 19990 30028] 9
[]uint8 [72 101 108 108 111 44 32 228 184 150 231 149 140] 13
13
We are casting the string to slice of runes and bytes, then printing out their values.
The string cast to runes is a slice of the Unicode code points for each character in the string, and the string cast to bytes is a slice of the byte values for each character, UTF-8 encoded.
And, based on the output, the length of a string is the length of a slice of bytes that’s necessary to encode the string in UTF-8.
String structure
In Go, the internal string representation is a pointer and a length(a descriptor).
Strings can share underlying storages. For example:
s := "hello, world"
hello := s[:5]
world := s[7:]
hello and world are substrings in string s. When creating these strings, part of the string s is indexed, and they point to the same characters in memory and can be reused.
When slicing a string using
s[start:end], The start index is inclusive, while the end index is exclusive.
Also, strings are passed by reference, thus they are not copied.
func main() {
s := "the quick brown fox"
pluralize(s)
fmt.Println(s)
}
func pluralize(str string) {
str += "es"
}
However, this does NOT work and will print the quick brown fox.
Although strings are passed by reference in Go, they are still immutable. str += "es" creates a new string in memory and points str to it, while s still points to the original value.
Program: Replacing substring
if len(os.Args) < 3 {
_, _ = fmt.Fprintln(os.Stderr, "Invalid syntax.")
os.Exit(-1)
}
var s string
old, new := os.Args[1], os.Args[2]
scan := bufio.NewScanner(os.Stdin)
for scan.Scan() {
s += scan.Text()
}
fmt.Println(strings.ReplaceAll(s, old, new))
The program is fairly simple.
Running the program, instead of typing the input, we could also supply a text file like this:
go run . <search> <replace> < textfile
or, like this:
cat textfile | go run . <search> <replace>
Composite Types
Arrays
Arrays are typed by size, and passed by value.
var a [3]int{0, 0, 0}
func do (a [3]int) {
a[0] = 1
}
do(a)
fmt.Println(a) // {0, 0, 0} (unchanged)
Slices
Much like strings, slices have descriptors(pointer and length), and they point to somewhere in the memory. Slices are passed by reference.
A slice has an array behind it(a pointer that points to a backing array), and is copied when it outgrows the array. Similarly, when it’s modified, another chunk of memory is allocated, then assigned back to the original slice with a modified descriptor.
var a = []int{1, 2}
a = append(a, 3)
b := a // {1, 2, 3}
By b := a, b becomes an alias of a. The a’s descriptor(not value) is copied to b, and they point to the same address in memory and share storage. And, also because slices are mutable, we have:
func do(a []int) {
a[0]--
}
do(b) // a: {0, 2, 3}
b[0]-- // a: {-1, 2, 3}
a[0] == -1 // true
“The off-by-one bug”
Slices are indexed like [8:11], but the end index is exclusive(written [8, 11) in math). I found the figure below help a lot.

Slices vs. Arrays
Most Go APIs takes slices as inputs, not arrays.
| Slices | Arrays |
|---|---|
| Variable length | Fixed length |
| Passed by reference | Copied and passed by value |
| Not comparable | Comparable (==) |
| Cannot be used as map key | Can be used as map key |
Maps
In Go, the type used for a map key must be comparable, that’s not slices or functions, and also not maps, maps cannot be compared.
Maps are passed by reference(descriptor).
var m map[string]int // nil
fmt.Println(m["the"]) // 0
m["the"] = 1 // PANIC
This declares a map variable without creating a map. m is a nil map. Nil maps can be read from, but cannot be assigned any values, or it will panic.
p := make(map[string]int) // or: p := map[string]int{}
m = p
m["the"] = 1
fmt.Println(p["the"]) // 1
n = map[string]int{
"the": 1
}
m == n // Syntax Error
Maps have a special two-result lookup function, the second return value indicates if the key is defined.
if w, ok := m["the"]; ok {
// w is not the default value
}
Nil pointers
nil is a predeclared identifier which represents the concept of absence. nil is generally safe for built-in functions in Go.
Link: GopherCon 2016: Francesc Campoy - Understanding nil
Program: word count
scan := bufio.NewScanner(os.Stdin)
scan.Split(bufio.ScanWords)
words := map[string]int{}
for scan.Scan() {
words[scan.Text()]++
}
type kv struct {
key string
val int
}
var ss []kv
for word, count := range words {
ss = append(ss, kv{word, count})
}
sort.Slice(ss, func(i, j int) bool {
return ss[i].val > ss[j].val
})
for _, kv := range ss {
fmt.Println(kv.key, kv.val)
}
fmt.Println(len(ss), "unique words.")
Next: The Basics: Part II