Syntax
Types
The primitive types in Go are:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64
float32 float64
complex32 complex64
byte // alias for uint8
rune // alias int32
Variables declared without an explicit initial value are given their zero value (0, false, "").
Assignment between items of different types requires explicit conversion.
The expression T(v) converts the value v to the type T.
Variables and Constants
The var statement declares a list of variables. The type is the last element.
If the variable is intialized, the type can be omitted.
var x, y, z int
var x, y, z = 1, 2, 3
Inside a function, there is the syntactic sugar := which can be used in place of var. Outside of a funciton, every statement needs to begin with a keyword, so it is not possible to use :=.
func main() {
k := 0
}
Constants are declared with the const keyword. They cannot be declared using the := syntax.
Functions
In Go, the type of a parameter comes after the parameter name.
func add(x int, y int) int {
return x + y
}
If consecutive parameters share the same type, you can share the type declaration.
func sub(x, y int) int {
return x - y
}
A function can return any number of results.
func swap(x, y string) (string, string) {
return y, x
}
Go's return values can be named. A "naked" return will return the variables declared inside of the body.
func split(sum int) (x, y int) {
x = sum % 10
y = sum / 10
return
}
Closures
Functions are first-class citizens. It is possible to return an anonymous function which is some closure over a location variable.
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
nextInt := intSeq()
nextInt()
nextInt()
nextInt()
Control Flow
if statements can start with a short statement to execute before the condition.
if v := math.Pow(2, 8); v < 256 {
fmt.Println("Less than 256")
} else {
fmt.Println("Greater than 256")
}
The only looping construct is the for loop. for loops can act equivalently to while and loop in other languages.
sum := 0
// for loop
for i := 0; i < 10; i++ {
sum += i
}
// while loop
for sum < 1000 {
sum += sum
}
// loop forever
for {
}
The range keyword can be used inside of a for loop to iterate over elements in various data structures.
| Data Type | First Value | Second Value |
|---|---|---|
| List | Index (int) | Element value |
| Map | Key | Element value |
| String | Byte Index (int) | Character (rune) |
| Integer | Current number (starts at 0) | None |
for index, val := range arr {}
for key, val := range map {}
for index, char := range "str" {}
for i := range 10 {}
A switch statement can match on the condition expression. A switch without a condition is the same as switch true, and it can be used to write long if-then-else chains.
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS")
case "linux":
fmt.Println("Linux")
default:
fmt.Printf("%s\n", os)
}
A defer statement pushes a function call onto a stack, which executes when the surrounding function returns. The deferred calls are executed in LIFO order.
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
Arrays and Slices
The type [n]T is an array of length n with elements of type T. Arrays cannot be resized.
var a [10]int
primes := [6]int{2, 3, 5, 7, 11, 13}
The type []T is a slice with elements of type T. A slice is a dynamically-sized view into the elements of an array. Its length can be extended and reduced at will, so long as it has sufficient capacity.
Changing the elements of a slice modifies the corresponding elements of its underlying array.
var s []int = primes[1:4] // reference elems 1 to 3
s = primes[:4] // extend its length
s[0] = 0 // mutate
A slice literal is like an array literal without the length. The code below builds an array of type [5]int, and then builds a slice that references it.
[]int{1, 2, 3, 4, 5}
Slices can be created with a make function which takes in teh type, length, and capacity. make is how you create dynamicall-sized arrays.
a := make([]int, 5)
b := make([]int, 0, 5)
Slices can be extended with append, which takes in the slice and values to be appended. If the capacity of s cannot fit all the given values, a bigger array will be allocated and the returned slice will point to the newly allocated array.
Maps
The type map[T1]T2 is a map from keys of type T1 to values of type T2.
Map literals have all the keys listed.
var m = map[string]int{
"Alice": 5,
"Bob": 10
}
The make function can return a map of the given type, intiialized.
var m map[string]int
m = make(map[string]int)
m["Alice"] = 5
m["Bob"] = 10
To test that a key is present:
elem, ok = m[key]
Pointers
Similar to C, Go has pointers.
i := 42
p = &i
*p += 43
If you take the address of a local variable, the compiler will automatically heap-allocate it.
Structs
The type keyword is used to declare custom data types or create type aliases.
type UserID string
type Velocity = float64 // Velocity and float64 are exactly the same type
A struct is a collection of fields. It is possible to just list a subset of fields using the Name: syntax.
type Point struct {
X, Y float64
}
fmt.Println(Point{1, 2})
fmt.Println(Point{X: 1}) // Y: 0 is implicit
Methods
Go does not have classes, but you can define methods on types. A method is a function with a special receiver argument that goes between func and the method name.
You can only declare a method with a receiver whose type is defined in the same package as the method.
type Point struct {
X, Y float64
}
func (p Point) Abs() float64 {
return math.Sqrt(p.X * p.X + p.Y * p.Y)
}
Methods can also have a pointer as a receiver. This allows the method to modify the value that its receiver points to. It also avoid copying the value on each method call.
func (p *Point) Scale(f float64) {
p.X = p.X * f
p.Y = p.Y * f
}
Go has a number of syntactic shortcuts to help with handling pointers:
- To access the field
Xof a struct when we have the struct pointerptr, we could write(*ptr).X. However, Go allows us to just writeptr.X. - If we pass a struct
pto a method that takes a pointer receiver, Go will automatically interpretp.method()as(&p).method(). - If we pass a struct pointer
ptrto a method that takes a value receiver, Go will automatically interpretptr.method()as(*ptr).method().
var p = Point{1, 1}
p.Scale(5) // OK
ptr := &p
ptr.Scale(5) // OK
Interfaces
An interface type is a set of method signatures. A value of an interface type is any value that implements those methods.
A type implements an interface by implementing its methods. It is not necessary to declare that it "implements" the interface.
type I interface {
M()
}
type T struct {
S string
}
// T implements I
func (t T) M() {
fmt.Println(t.S)
}
Generics
Functions can be made generic using type parameters.
For example, the declaration below states that s is a slice of any type T that fulfills the constraint comparable. x is also a value of the same type. comparable is a constraint that states that it should be possible to use == and != on value of this type.
func Index[T comparable](s []T, x T) int
In addition to generic functions, there are also generic types.
For example, the declaration below describes a single-linked list that holds any type of value.
type List[T any] struct {
next *List[T]
val T
}
Errors
To return a basic, static text error message, use the errors package.
errors.New("cannot divide by zero")
To return a formatter error message, use fmt.Sprintf.
key := "Alice"
fmt.Errorf("overlapping command for %s", key)
Project Structure
Packages
Every Go program is made of packages. Programs start running in package main.
package main
If a variable begins with a capital letter, it is exported.
Modules
A module is a collection of related packages that are versioned and distributed together as a single unit. It is defined by a specific file tree that contains a go.mod file at its root.
Command commands:
go mod initinitializes a new modulego get <path>@<version>updates a specific dependencygo list -m alllists all dependenciesgo mod tidyautomatically adds missing dependencies and prunes unused onesgo mod vendorcopies all modules required to build the project into a local directory namedvendor/
A workspace allows you to work on multiple modules without having to edit individual go.mod files.
References: A Tour of Go