General notes about learning Go, especially while reading through Learning Go - 2nd edition. I’m trying to keep the notes here minimal.
Commands
- specify the name of the binary that is created -
go build - o hello
- run a go file without creating a binary -
go run hello.go
Go runtime
- The Go runtime provides things like memory allocation & garbage collection, concurrency support, networking, etc.
- It is directly compiled into every Go binary, so we don’t need to install anything extra to run a Go program, which makes it easer to distribute Go programs
- Drawack - it increases the size of the binary
Predeclared Types
- it’s possible to put underscores in the middle of an integer or floating point literal to improve readablilty, for example to group thousands
10_2500
- a
rune
literal represents a character and is surrounded by single quotes. Special ones:'\t'
for tab'\n'
for newline'\\'
for backslash
string
literal -"text in double quotes"
or araw string literal
in backquotes`text in a raw string literal`
. In a raw string literal we can use backslashes, double quotes, and newlines without escaping them.byte
is an alias foruint8
,int
isint32
orint64
depending on the CPU architecture,uint
is always 0 or positive. Unless we have a good reason to use some other type, just useint
.- Integers have the normal operators
+ - * /
and<< >>
for bit-manipulation, also for examplex += 5
to immediately assign the variable - Floating point types
float32
&float64
, generally - usefloat64
. Floating point numbers cannot represent a decimal value exactly, don’t use them to represent things like money for example. string
- can be compared using==
, checking for difference using!=
or ordering with> >= < <=
. They are concatenated using+
. Strings are immutable - the value of a string variable can be changed but not the value of the string that is assigned to itrune
- anint32
in the background, used to refer to a single character.- Go doesn’t have automatic type conversion, this must be done manually. Example:
var x int = 10 var y float64 = 30.2 var sum1 float64 = float64(x) + y var sum2 int = x + int(y) fmt.Println(sum1, sum2)
- No type (other than
bool
) can be converted tobool
. If we want to use another data type as a boolean, we have to use one of the comparison operators, for examplex == 0
ors == ""
- Literals are untyped. So if the type or a varaible is compatible with the literal, we can just assign it.
var x float64 = 10
orvar y float64 = 200.3 * 5
. However, we can’t for example assign a literalstring
to afloat
orint
and vice versa. Also we still need to consider size limitations.
Variables
-
Declaring variables:
var x int = 10
var x = 10
var x int
var x, y int = 10, 20
var x, y int
var x, y = 10, "hello"
-
Or, using a declaration list:
var ( x int y = 20 z int = 30 d, e = 40, "hello" f, g string )
-
Within a function - we can use short declaration:
x := 10
x,y := 10, "hello"
- When initializing a variable to its zero value, we should use
var x int
to make it clear that this is intended - Also, when we want to use a type that isn’t the default type for the constant, use the long form
var x byte = 20
- Generally, only declare multiple variables on one line when assigning multiple values returned from a function
- Overall, try not to use variables outside of functions
- Every declared local variable must be read - compile time error otherwise
Constants
- Set as
const x int64 = 10
, can be declared at the package level or within a function and it is not possible to change the value after it has been assigned - Can hold only values that the compiler can figure out at compile time
- Mostly used as a way to give names to literals
- Can be
untyped
, the type is inferred -const x = 10
(so this could be used as int, float64 or byte for example) - Unread constants are allowed, but simply won’t be included in the compiled binary
Naming schemes
- Use camelCase when an identifier name consists of multiple words
- Constants are not written in all uppercase (because the first letter is used to determine if the item is accessible outside the package)
- Within a function - favor short names. In for loops it is common to see single-letter variable names (for example k and v for key and value, or i and j for index variables)
- In a package block - use more descriptive names
Composite types
Arrays
- Arrays - rarely used directly:
var x [3]int
- this creates an array of three ints and initializes the zero value for int.var x = [3]int{10, 20, 30}
- When using the array literal to initialize an array, we don’t have to specify the number of elements and can instead use
...
:var x = [...]int{10, 20, 30}
- Arrays are equal if they are the same size and contain equal values
- Accessing elements in an array:
x[0] = 10
. Getting the length of an array:len(x)
- Cannot read past the end of an array or use a negative index
- Go considers the size of an array to be part of the type of the array. So
[3]int
is a different type to[4]int
. This also means that the size must be known at compile time - So in general, don’t use arrays directly unless there is a specific reason
Slices
- A
slice
can grow and shrink as needed because the length is not part of the type.var x = []int{10, 20, 30}
. (an array would use[...]int{10, 20, 30}
). var x = []int{1, 5: 4, 6, 10: 100, 15}
var x [][] int
- a slice of slices (2D slice)- We access elements in a slice the same way as an array:
x[0] = 10
,len(x)
var x []int
- this creates anil
slice, which has a length and capacity of 0.- A slice is not comparable, so we cannot use
==
to compare two slices. We can compare tonil
though. - However there are helper functions. The
slices
package in the standard library has aslices.Equal
function that takes two slices as parameters & can be used to compare two slices. len
also works on slices and return the number of elements in the sliceappend
is used to add elements to a slice:var x []int
x = append(x, 10)
- adds one element. So append takes the slice as the first parameter and then one or more elements to add.x = append(x, 20, 30, 40)
- adds multiple elements, and we can also append to a non-empty slice- We can also append on slice to another:
y := []int{50, 60, 70}
x = append(x, y...)
sub-chapter:- the
...
is needed to indicate that we want to append all elements of y to x
- Slices have a
capacity
- the number of elements that can be stored in the underlying array. When we append to a slice and exceed the capacity, a new underlying array is created with a greater capacity, the elements are copied over, the new element is added and the slice now refers to the new underlying array. Generally it doubles the capacity when it needs to grow. cap
returns the capacity of a slice- If we know how many elements we need, it’s more efficient to specifiy the lenght or capacity when creating the slice:
x := make([]int, 10)
- creates a slice of length 10 and capacity 10- Here all elements are initialized to the zero value of the element type (0 for int)
- Running
x = append(x, 20)
here would increase the length to 11 and the capacity to 20. Append always increases the length of a slice. x := make([]int, 5, 10)
- creates a slice of length 5 and capacity 10
- Emptying a slice
s := []string{"first", "second", "third"}
clear(s)
- length remains the same, but all elements are set to the zero value of the element type
Stopped at sub-chapter: “Declaring your slice”