Go
Installation
Linux
- Download the archive from
https://golang.org/dl/.
# rm -rf /usr/local/go && tar -C /usr/local -xzf go1.23.3.linux-amd64.tar.gz
- Add the following to your
.bashrcor.zshrc:
export PATH="$PATH:/usr/local/go/bin"
Different versions
go install golang.org/dl/go1.18@latest
go1.18 download
There is now a wrapper script that can be used to install different versions of
Go. The script is called go1.18 and is located in the bin directory of the
Go installation.
Packages
- Go programs are made up of packages.
- Programs start running in package
main. - The filename is the package name.
Imports
- Import packages with
import "fmt". - Multiple packages can be imported.
- Factored import statement stylistically superior:
import (
"fmt"
"math"
)
Exported names
- A name is exported if it begins with a capital letter.
Printlninfmt.Printlnis an exported name.- When importing a package, you can refer only to its exported names.
- Any “unexported” names are not accessible from outside the package.
Functions
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
- A function can take zero or more arguments.
- The type comes after the variable name.
Omitted type
- When two or more consecutive named function parameters share a type, you can omit the type from all but the last.
func add(x, y int) int {
return x + y
}
Multiple results
- A function can return any number of results.
func swap(x, y string) (string, string) {
return y, x
}
Named return values
- Go’s return values may be named. If so, they are treated as variables defined at the top of the function.
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
- These names should be used to document the meaning of the return values.
- A return statement without arguments returns the named return values. This is known as a “naked” return.
- Naked return should be used only in short functions, as it can harm readability in longer functions.
Variables
-
The
varstatement declares a list of variables; as in function argument lists, the type is last. -
A
varstatement can be at package or function level.
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
Initializers
- A
vardeclaration can include initializers, one per variable.
var i, j int = 1, 2
- If an initializer is present, the type can be omitted; the variable will take the type of the initializer.
var i, j = 1, 2
Short variable declarations
- Inside a function, the
:=short assignment statement can be used in place of avardeclaration with implicit type.
k := 3
- Outside a function, every statement begins with a keyword (
var,func, and so on) and so the:=construct is not available.
Basic types
bool:trueorfalse.string: a sequence of UTF-8 encoded bytes.int: a signed integer type that is 32 or 64 bits in size, depending on the platform. The recommended default for integer types isint.unint: an unsigned integer type that is 32 or 64 bits in size, depending on the platform.int8,int16,int32,int64: signed integers of 8, 16, 32, or 64 bits.uint8,uint16,uint32,uint64: unsigned integers of 8, 16, 32, or 64 bits.float32,float64: 32-bit or 64-bit floating-point numbers.complex64,complex128: complex numbers with float32 or float64 real and imaginary parts.byte: alias foruint8.rune: alias forint32. Represents a Unicode character.uintptr: an unsigned integer large enough to store the uninterpreted bits of a pointer value.
Zero values
-
Variables declared without an explicit initial value are given their zero value.
0for numeric types.falsefor the boolean type.""(the empty string) for strings.
Type conversions
- The expression
T(v)converts the valuevto the typeT.
var i int = 42
var f float64 = float64(i)
Type inference
- When declaring a variable without specifying an explicit type (either by using
the
varkeyword or the:=operator), the variable’s type is inferred from the value on the right-hand side.
i := 42
f := 3.142
g := 0.867 + 0.5i
If the right-hand side of the declaration is untyped, the new variable is of the same type:
i := 42
j := i
Constants
- Constants are declared like variables, but with the
constkeyword. - Constants can be character, string, boolean, or numeric values.
- Constants cannot be declared using the := syntax.
- An untyped constant takes the type needed by its context.
const Pi = 3.14
For loops
A for loop has three components separated by semicolons:
- The init statement: executed before the first iteration
- The condition expression: evaluated before every iteration
- The post statement: executed at the end of every iteration
The init statement will often be a short variable declaration, and the variables declared there are visible only in the scope of the for statement.
import "fmt"
func main() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
The example prints the numbers 0 to 9.
The init and post statements are optional.
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
You can drop the semicolons in the for loop.
for sum < 1000 {
sum += sum
}
This is equivalent to a while loop in other languages.
It’s possible to omit the loop condition, creating an infinite loop.
for {
}
Range
The range form of the for loop iterates over an array or slice.
// index and value
for i, v := range slice {}
// index only
for i := range slice {}
// value only
for _, v := range slice {}
You can also iterate over a map.
// key and value
for key, value := range theMap {}
// key only
for key := range theMap {}
// value only
for _, value := range theMap {}
It is also possible to iterate over a channel.
for v := range channel {}
Iterating over a channel is equivalent to receiving from a channel until it is closed.
for {
v, ok := <-theChan
if !ok {
break
}
}
```go
## If statements
- Go's `if` statements are like `for` loops; the expression need not be
surrounded by parentheses.
```go
if x < 0 {
return sqrt(-x) + "i"
}
-
Like
for, theifstatement can start with a short statement to execute before the condition. -
Variables declared by the statement are only in scope until the end of the
if.
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// can't use v here, though
Switch statements
- Switch cases evaluate cases from top to bottom, stopping when a case succeeds.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
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 without a condition is the same as switch true.
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
Defer statements
-
A
deferstatement defers the execution of a function 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.
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
Pointers
-
Go has pointers. A pointer holds the memory address of a value.
-
The
&operator generates a pointer to its operand.
i := 42
p = &i
- The
*operator denotes the pointer’s underlying value.- This is known as dereferencing or indirecting.
fmt.Println(*p) // read i through the pointer p
*p = 21 // set i through the pointer p
Use cases
-
Passing a pointer to a function.
-
For large structs, it’s more efficient to pass a pointer than a copy of the struct.
-
Mutating the value of a function argument.
-
-
APIs: for consistency.
-
For true absence of a value.
nilis the zero value for pointers, interfaces, maps, slices, channels, and functions.
Structs
- A struct is a collection of fields.
type Vertex struct {
X int
Y int
}
- Struct fields are accessed using a dot.
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
- Struct fields can be accessed through a struct pointer.
v := Vertex{1, 2}
p := &v
p.X = 1e9
- To access the field
Xof a struct when we have the struct pointerp, we could write(*p).X. However, that notation is cumbersome, so the language permits us instead to write justp.X, without the explicit dereference.
Arrays
- An array’s length is part of its type, so arrays cannot be resized.
// example 1
var a [2]string
a[0] = "Hello"
a[1] = "World"
// example 2
var a [10]int
// example 3
primes := [6]int{2, 3, 5, 7, 11, 13}
Slices
- A slice is a dynamically-sized, flexible view into the elements of an array. A slice does not store any data, it just describes a section of an underlying array.
// this is an array
primes := [6]int{2, 3, 5, 7, 11, 13}
// this is a slice containing 3, 5, and 7.
var s []int = primes[1:4]
-
Changing the elements of a slice modifies the corresponding elements of its underlying array.
-
Other slices that share the same underlying array will see those changes.
-
Declaring a new slice populated by elements will create a new array and copy the elements into it.
// creates a new array containing these three elements
[]bool{true, true, false}
-
Slices can contain any type, including other slices.
-
Slice indices start at 0 and end at the length of the slice.
Omitted low or high bounds
-
When slicing, you may omit the high or low bounds to use their defaults instead.
-
The default is zero for the low bound and the length of the slice for the high bound.
-
Given an underlying array
aof length10:
a[0:10]
a[:10]
a[0:]
a[:]
Empty slices
// example 1
var myslice []int
// example 2
myslice := []int{}
-
In most instances the two examples above are equivalent, however, the first has
nilvalue and the second has a length of 0. The json.Marshal function will encode the former asnulland the latter as[]. -
A nil slice has a length and capacity of 0 and has no underlying array.
Length and capacity
-
A slice has both a length and a capacity.
-
The length of a slice is the number of elements the slice points to. If an underlying array has a length of
10but the slice range only covers half of the items, the length of the slice will be5. -
The capacity of a slice is the number of elements in the underlying array. If the array has a length of
10, the capacity of the slice will be10. -
The length and capacity of a slice
scan be obtained using the expressionslen(s)andcap(s). -
The length of a slice
scan be increased by re-slicing it, provided it has enough capacity. -
The capacity of a slice
scannot be increased. You must create a new slice with a larger capacity and copying the elements of the original slice into it. In practice, theappendfunction discussed later will do this for you.
Creating a slice with make
- The make function creates an array full of zeros and returns a slice that refers to that array.
a := make([]int, 5) // len(a)=5
- To specify a capacity, pass a third argument to make.
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
Appending to a slice
-
The
appendfunction appends elements to a slice. -
If the slice has enough capacity, the underlying array is reused.
var s []int
// add a single element
// works on empty slices
s = append(s, 0)
// we can add more than one element at a time
s = append(s, 2, 3, 4)
Methods
-
Methods are similar to functions but specify an additional receiver parameter straight after the
funckeyword. -
The receiver can be a value or a pointer.
-
Methods can be defined on any type in the same package, not just structs.
-
Methods with pointer receivers can modify the value to which the receiver points.
-
Methods enable access to the fields of the receiver.
-
Methods cannot be created for types defined in other packages, including built-in types like
intorstring.- To create a method for a built-in type, you can define a new custom type and create a method for that type.
Difference between declaring methods and functions
This is a method:
func (p \*person) changeName(newName string) {
The receiver is (p *person).
This is a function:
func changeName(p \*person, newName string) {
Methods are invoked using dot notation .:
a := person{name: "a"} a.changeName("John")
When to use methods
- Use methods when managing state.
- Code organization: group related functions together.
- Interface implementation: methods are used to implement interfaces.
Other differences between methods and functions
| Aspect | Method | Function |
|---|---|---|
| Contains a receiver | Yes | No |
| Methods with the same name but different types | Allowed | Not allowed |
| Usage as first-order objects | Cannot be used | Can be used |