Introduction
General
Packages & Modules
- Every Go program is made up of packages
- Programs start running in function
main
of package main
import
- When importing a package, you can refer only to its exported (capitalized) names
Functions
func ExportedFunction() {}
func unexportedFunction() {}
- A function can take zero or more arguments
- A parameter’s type is declared after its name:
x int, y int
- When consecutive parameters share a type, you can omit the type declaration from all but the last one:
x, y int
- A function can return any number of results
- Return types are declared in
()
: (string, string)
- Values can be return explicitly:
return x, y
- Or implicitly if the returned variables are named in the return type: if
(x, y int)
, then return
will implicitly return x
and y
Variables
var names types
- The
var
statement declares a list of variable names optionally followed by their types and/or initial values: var c, python, java bool
- A
var
statement can occur at the package or function level
- A
var
declaration can optionally include initial values, in which case the explicit types can be omitted since the variables types are inferred from the values on the right hand side: var c, python, java = true, false, "no!"
- Variables declared with no initial value are assigned their “zero value” (
false
for booleans, 0
for numbers, ""
for strings)
- A variable’s type can never change (Go is a “strongly typed” language)
:=
short assignment statement
- An alternative to a
var
declaration with an implicit type
- e.g.
var i int = 42
can be replaced with i := 42
- Only available inside functions (since every package-level statement must begin with a keyword)
const names types
- Constants are declared like variables, with a few exceptions:
- They must be initialized with a value
- Their value can never change
- They can be character, string, boolean, or numeric values
- They cannot be declared using the
:=
syntax.
- Naming:
- PascalCase or camelCase
- A variable is exported if its name begins with a capital letter (e.g.
YouCanImportMe
)
- Any “unexported” names are not accessible from outside the package (e.g.
youCannotImportMe
)
Types
- The expression
T(v)
converts the value v
to the type T
- Explicit conversion to a compatible type is required when assigning values of one type to a variable with another, or when operating on values of different types (e.g. with
+
)
Booleans
Numbers
- When you need a non-floating-point number, you should use
int
unless you have a specific reason to use a sized or unsigned integer type
int
, int8
, int16
, int32
, int64
, uint
, uint8
, uint16
, uint32
, uint64
, uintptr
float32
, float64
complex64
, complex128
- The
int
, uint
, and uintptr
types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems
Strings
string
- Stored as array of bytes, not characters
- Special (non-ASCII) characters (e.g. emoji) may comprise multiple bytes
- UTF-8
byte
= alias for uint8
rune
= alias for int32
that can represents a Unicode code point
- Implications for index lookups
Pointers
- A pointer holds the memory address of a value
- The type
*T
is a pointer to a T
value; its zero value is nil
- The
&
operator generates a pointer to its operand
- The
*
operator denotes the pointer’s underlying value; this is known as “dereferencing” or “indirecting”
- Golang pointers explained, once and for all • JamieDev 📺
Structs
- A
struct
is a collection of fields
- Struct fields are accessed using a dot
- Struct fields can be accessed through a struct pointer
- To access the field
X
of a struct when we have the struct pointer p
we could write (*p).X
. However, that notation is cumbersome, so the language permits us instead to write just p.X
, without the explicit dereference
Arrays & Slices
- The type
[n]T
is an array of n
values of type T
- e.g. var a [10]int
- An array’s length is part of its type, so arrays cannot be resized
- A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array
- In practice, slices are much more common than arrays
- The type
[]T
is a slice with elements of type T
- A slice is formed by specifying two indices, a low and high bound, separated by a colon:
a[low : high]
- This selects a half-open range which includes the first element, but excludes the last one
- e.g.
a[1:4]
creates a slice which includes elements 1 through 3 of a
Generics
- Type placeholders that enable functions to apply the same logic to multiple possible input types
Flow Control Statements
- Conditionals
if short statement; condition {} elseif condition {} else condition {}
()
are not needed around conditions, but {}
are required around each block
- Any variables declared in the optional short statement can be referenced in the following
if
/else
conditions and blocks
- Loops
- Go has only one looping construct, the
for
loop
for init; condition; post {}
()
are not needed, but {}
are required
- The init and post statements can be omitted (resulting in what other languages call a
while
loop)
- The condition expression can also be omitted, resulting in an infinite loop
- Switches
- A
switch
statement is a shorter way to write a sequence of if - else
statements
- It runs the first case whose value is equal to the condition expression, then automatically skips the rest
- In effect, the
break
statement that is needed at the end of each case in other languages is provided automatically in Go
- A switch without a condition is the same as
switch true
, which can be a clean way to write long if-then-else chains
- Defers
- A defer statement defers the execution of a function until the surrounding function returns
- 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
- Defer, Panic, and Recover • The Go Blog 📖
Interfaces & Methods
reader io.Reader
writer io.Writer
Concurrency
- Concurrency vs parallel execution
- Goroutines
- WaitGroup
- Mutexes
- Lock/Unlock
- RLock/RUnlock
- Be careful to include as few lines as possible inside the lock to avoid unnecessary blocking time
- Confinement
- Avoiding the bottlenecks caused by locking by instead “confining” the operations that need to be safe to a different subset of the data than is accessed by the other Goroutines
- Improve Go Concurrency Performance With This Pattern • An example of refactoring from locks to each Goroutine operating on a reference to a specific index in the slice all the Goroutines reference • Kantan Coding 📺
- Channels
- Goroutines that pass data around
- They hold data
- They are thread safe (avoiding race conditions when reading/writing data to memory)
- Allow listening for data to be added or removed from a channel and blocking code execution until those events occur
- Buffer channel = channel that can store multiple values
- golang context package explained: the package that changed concurrency forever • Kantan Coding 📺
Debugging
fmt.Printf("%T %v %q", x, y, z)
%T
= variable’s type
%v
= non-string variable’s value
%q
= string variable’s value
Building Web Apps
Comparison to Rust
Inbox