basics
data structures
basic types
- bool
- byte
- int int8 int16 int32 int64
- uint uint8 uint16 uint32 uint64 uintptr
- float32 float64
- rune
- string
- complex64 complex128
array
https://go.dev/blog/slices-intro
Go’s arrays are values. An array variable denotes the entire array; it is not a pointer to the first array element (as would be the case in C). This means that when you assign or pass around an array value you will make a copy of its contents
An array's length is part of its type, so arrays cannot be resized
b := [2]string{"Penn", "Teller"}
b := [...]string{"Penn", "Teller"}
slice
https://go.dev/blog/slices-intro
Unlike an array type, a slice type has no specified length
The zero value of a slice is nil. The len and cap functions will both return 0 for a nil slice.
The start and end indices of a slice expression are optional; they default to zero and the slice’s length respectively:
A slice is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity
To increase the capacity of a slice one must create a new, larger slice and copy the contents of the original slice into it
To append one slice to another, use ... to expand the second argument to a list of arguments
letters := []string{"a", "b", "c", "d"}
func make([]T, len, cap) []T
var s []byte
s = make([]byte, 5, 5)
// s == []byte{0, 0, 0, 0, 0}
s := make([]byte, 5)
len(s) == 5
cap(s) == 5
b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
// b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b
// b[:2] == []byte{'g', 'o'}
// b[2:] == []byte{'l', 'a', 'n', 'g'}
// b[:] == b
t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t
func append(s []T, x ...T) []T
a := make([]int, 1)
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}
a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}
a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])"
// a == []string{"John", "Paul", "George", "Ringo", "Pete"}
map
var m map[string]int
m = make(map[string]int)
m["route"] = 66
i := m["route"]
n := len(m)
delete(m, "route")
i, ok := m["route"]
for key, value := range m {
fmt.Println("Key:", key, "Value:", value)
}
commits := map[string]int{
"rsc": 3711,
"r": 2138,
"gri": 1908,
"adg": 912,
}
m = map[string]int{}
basic syntax
for basic
for i := 0; i < 10; i++ {
sum += i
}
for range
for k, v := range m {
...
}
for condition only
for sum < 1000 {
sum += sum
}
for only
for {
...
}
if
if x < 0 {
return sqrt(-x) + "i"
}
If with a short statement
if v := math.Pow(x, n); v < lim {
return v
}
switch
A switch statement is a shorter way to write a sequence of if - else statements
Go only runs the selected case, not all the cases that follow
Another important difference is that Go's switch cases need not be constants, and the values involved need not be integers.
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
Switch with no condition
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
Defer
https://go.dev/blog/defer-panic-and-recover
The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns
Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.
- A deferred function’s arguments are evaluated when the defer statement is evaluated
- Deferred function calls are executed in Last In First Out order after the surrounding function returns
- Deferred functions may read and assign to the returning function’s named return values
defer fmt.Println("world")
type switch
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case float64:
fmt.Println(k, "is float64", vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
type assertion
If i does not hold a T, the statement will trigger a panic.
To test whether an interface value holds a specific type, a type assertion can return two values
t := i.(T)
t, ok := i.(T)
type conversion
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
Constants
const Pi = 3.14
Pointers
Unlike C, Go has no pointer arithmetic
i, j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i
Struct Literals
You can list just a subset of fields by using the Name: syntax
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
function
Multiple results
func swap(x, y string) (string, string) {
return y, x
}
Named return values
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
Function literals
https://go.dev/ref/spec#Function_literals
Function literals are closures: they may refer to variables defined in a surrounding function
f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(replyChan)
closure
func makeHandler(fn func (http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Here we will extract the page title from the Request,
// and call the provided handler 'fn'
}
}
variable declarations
var i, j int = 1, 2
k := 3
method
Go does not have classes. However, you can define methods on types.
A method is a function with a special receiver argument
a method is just a function with a receiver argument
You can declare a method on non-struct types, too
You can only declare a method with a receiver whose type is defined in the same package as the method
Methods with pointer receivers can modify the value to which the receiver points
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
Interfaces
An interface type is defined as a set of method signatures.
A value of interface type can hold any value that implements those methods
A type implements an interface by implementing its methods. There is no explicit declaration of intent, no "implements" keyword
type Stringer interface
type error interface
io.Reader: func (T) Read(b []byte) (n int, err error)
Interface values with nil underlying values
If the concrete value inside the interface itself is nil, the method will be called with a nil receiver
Note that an interface value that holds a nil concrete value is itself non-nil
The empty interface
The interface type that specifies zero methods is known as the empty interface
An empty interface may hold values of any type. (Every type implements at least zero methods.)
generics
type Number interface {
int64 | float64
}
func SumNumbers[K comparable, V Number](m map[K]V) V {
...
}
concurrency
Goroutines
Goroutines run in the same address space, so access to shared memory must be synchronized. The sync package provides useful primitives