My Golang Handbook

My Golang Handbook

Variables

Basic

  • bool: a boolean value, either true or false
  • string: a sequence of characters
  • int: a signed integer
  • float64: a floating-point number
  • byte: exactly what it sounds like: 8 bits of data

var smsSendingLimit int
var costPerSMS float64
var hasPermission bool
var username string

Type sizes

Whole Numbers (No Decimal)
int int8 int16 int32 int64

Positive Whole Numbers (No Decimal)
uint uint8 uint16 uint32 uint64 uintptr

Signed Decimal Numbers
float32 float64

Imaginary Numbers (Rarely Used)
complex64 complex128

Constants

const pi = 3.14159

Constants can be computed
const firstName = "Lane"
const lastName = "Wagner"
const fullName = firstName + " " + lastName


Formatting Strings

Default Representation

s := fmt.Sprintf("I am %v years old", 10)
// I am 10 years old

s := fmt.Sprintf("I am %v years old", "way too many")
// I am way too many years old

String

s := fmt.Sprintf("I am %s years old", "way too many")
// I am way too many years old

Integer

s := fmt.Sprintf("I am %d years old", 10)
// I am 10 years old

Float

s := fmt.Sprintf("I am %f years old", 10.523)
// I am 10.523000 years old

// The ".2" rounds the number to 2 decimal places
s := fmt.Sprintf("I am %.2f years old", 10.523)
// I am 10.52 years old


Conditionals

if - else

if height > 6 {
fmt.Println("You are super tall!")
} else if height > 4 {
fmt.Println("You are tall enough!")
} else {
fmt.Println("You are not tall enough!")
}

The Initial Statement

if length := getLength(email); length < 1 {
fmt.Println("Email is invalid")
}

Switch

fallthrough - to fall through to the next case

func getCreator(os string) string {
var creator string
switch os {
case "linux":
creator = "Linus Torvalds"
case "windows":
creator = "Bill Gates"
// all three of these cases will set creator to "A Steve"
case "macOS":
fallthrough
case "Mac OS X":
fallthrough
case "mac":
creator = "A Steve"
default:
creator = "Unknown"
}
return creator
}


Functions

Definition

func addToDatabase(hp, damage int, name string, level int) {
// ?
}

Ignoring Return Values

func getPoint() (x int, y int) {
return 3, 4
}

// ignore y value
x, _ := getPoint()

Named Return Values

func getCoords() (x, y int){
// x and y are initialized with zero values
return // automatically returns x and y
}
Is the same as:

func getCoords() (int, int){
var x int
var y int
return x, y
}

Explicit Returns

func getCoords() (x, y int){
return x, y // this is explicit
}

func getCoords() (x, y int){
return 5, 6 // this is explicit, x and y are NOT returned
}

unc getCoords() (x, y int){
return // implicitly returns(blank return) x and y
}

Early Returns / Guard Clauses

func divide(dividend, divisor int) (int, error) {
if divisor == 0 {
return 0, errors.New("can't divide by zero")
}
return dividend/divisor, nil
}

Functions As Values

func add(x, y int) int {
return x + y
}

func mul(x, y int) int {
return x * y
}

func aggregate(a, b, c int, arithmetic func(int, int) int) int {
firstResult := arithmetic(a, b)
secondResult := arithmetic(firstResult, c)
return secondResult
}

func main() {
sum := aggregate(2, 3, 4, add)
// sum is 9
product := aggregate(2, 3, 4, mul)
// product is 24
}

Anonymous Functions

func conversions(converter func(int) int, x, y int) (int, int) {
convertedX := converter(x)
convertedY := converter(y)
return convertedX, convertedY
}

func double(a int) int {
return a + a
}


func main() {
// using a named function
newX, newY := conversions(double, 1, 2)
// using an anonymous function
newX, newY = conversions(func(a int) int {
return a + a
}, 1, 2)
}

Variadic

func concat(strs ...string) string {
final := ""
// strs is just a slice of strings
for i := 0; i < len(strs); i++ {
final += strs[i]
}
return final
}

func main() {
final := concat("Hello ", "there ", "friend!")
fmt.Println(final)
// Output: Hello there friend!
}

Defer

func GetUsername(dstName, srcName string) (uname string, err error) {
// Open a connection to a database
conn, _ := db.Open(srcName)
// Close the connection *anywhere* the GetUsername function returns
defer conn.Close()
uname, err = db.FetchUser()
if err != nil {
// The defer statement is auto-executed if we return here
return "", err
}
// The defer statement is auto-executed if we return here
return uname, nil
}

Block Scope

func main() {
{
age := 19
// this is okay
fmt.Println(age)
}
// this is not okay
// the age variable is out of scope
fmt.Println(age)
}


Struct

Definition

type car struct {
brand string
model string
doors int
mileage int
}

Nested Structs

type wheel struct {
radius int
material string
}

type car struct {
brand string
model string
doors int
mileage int
frontWheel wheel
backWheel wheel
}

Embedded Structs

type car struct {
brand string
model string
}

type truck struct {
car
bedSize int
}

Anonymous Structs

myCar := struct {
brand string
model string
} {
brand: "Toyota",
model: "Camry",
}

type car struct {
brand string
model string
doors int
mileage int
wheel struct {
radius int
material string
}
}

Methods

type rect struct {
width int
height int
}

func (r rect) area() int {
return r.width * r.height
}

var r = rect{
width: 5,
height: 10,
}

fmt.Println(r.area())

Empty Struct

// anonymous empty struct type
empty := struct{}{}

// named empty struct type
type emptyStruct struct{}
empty := emptyStruct{}


Interfaces

Definition

type shape interface {
area() float64
perimeter() float64
}

type rect struct {
width, height float64
}
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perimeter() float64 {
return 2*r.width + 2*r.height
}

type circle struct {
radius float64
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perimeter() float64 {
return 2 * math.Pi * c.radius
}

Type Assertions

type shape interface {
area() float64
}

type circle struct {
radius float64
}

c, ok := s.(circle) // s is instance of shape interface
if !ok {
// log an error if s isn't a circle
log.Fatal("s is not a circle")
}

radius := c.radius

Type Switches

func printNumericValue(num interface{}) {
switch v := num.(type) {
case int:
fmt.Printf("%T\n", v)
case string:
fmt.Printf("%T\n", v)
default:
fmt.Printf("%T\n", v)
}
}

func main() {
printNumericValue(1)
// prints "int"
printNumericValue("1")
// prints "string"
printNumericValue(struct{}{})
// prints "struct {}"
}


Errors

The Error Interface

type error interface {
Error() string
}

type userError struct {
name string
}

func (e userError) Error() string {
return fmt.Sprintf("%v has a problem with their account", e.name)
}

The Errors Package

package main

import (
"errors"
)

func divide(x, y float64) (float64, error) {
if y == 0 {
return 0.0, errors.New("no dividing by 0")
}
return x / y, nil
}

Panic

func enrichUser(userID string) User {
user, err := getUser(userID)
if err != nil {
panic(err)
}
return user
}

func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered from panic:", r)
}
}()
// this panics, but the defer/recover block catches it
// a truly astonishingly bad way to handle errors
enrichUser("123")
}


Loops

Basic Loop

for INITIAL; CONDITION; AFTER{
// do something
}

for i := 0; i < 10; i++ {
fmt.Println(i)
}
// Prints 0 through 9

for CONDITION {
// do some stuff while CONDITION is true
}

Continue

for i := 0; i < 10; i++ {
if i % 2 == 0 {
continue
}
fmt.Println(i)
}
// 1
// 3
// 5
// 7
// 9

Break

for i := 0; i < 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
// 0
// 1
// 2
// 3
// 4

Arrays Iteration

for index, value := range users {
fmt.Println(index, value)
}


Slices

Arrays

var myInts [10]int
// or to declare an initialized literal
myArray := [6]int{2, 3, 5, 7, 11, 13}

Slices

myArray := [6]int{2, 3, 5, 7, 11, 13}
mySlice := primes[1:4] // {3, 5, 7}

arrayname[lowIndex:highIndex]
arrayname[lowIndex:]
arrayname[:highIndex]
arrayname[:]

Make

// func make([]T, len, cap) []T
mySlice := make([]int, 5, 10)

// the capacity argument is usually omitted and defaults to length
mySlice := make([]int, 5)

Length

mySlice := []string{"I", "love", "go"}
fmt.Println(len(mySlice)) // 3

Capacity

mySlice := []string{"I", "love", "go", "fuck"}
fmt.Println(cap(mySlice)) // 4

Indexing

mySlice := []string{"I", "love", "go"}
fmt.Println(mySlice[2]) // go

mySlice[0] = "you"
fmt.Println(mySlice) // [you love go]

Spread Operator

func printStrings(strings ...string) {
for i := 0; i < len(strings); i++ {
fmt.Println(strings[i])
}
}

func main() {
names := []string{"bob", "sue", "alice"}
printStrings(names...)
}

Append

slice = append(slice, oneThing)
slice = append(slice, firstThing, secondThing)
slice = append(slice, anotherSlice...)

Range

fruits := []string{"apple", "banana", "grape"}
for i, fruit := range fruits {
fmt.Println(i, fruit)
}
// 0 apple
// 1 banana
// 2 grape

Slice of Slices

rows := [][]int{}

func createMatrix(rows, cols int) [][]int {
matrix := make([][]int, rows)
for i := 0; i < rows; i++ {
matrix[i] = make([]int, cols)
for j := 0; j < cols; j++ {
matrix[i][j] = i * j
}
}
return matrix
}

createMatrix(5, 10)

[0 0 0 0 0 0 0 0 0 0]
[0 1 2 3 4 5 6 7 8 9]
[0 2 4 6 8 10 12 14 16 18]
[0 3 6 9 12 15 18 21 24 27]
[0 4 8 12 16 20 24 28 32 36]


Maps

Definition

ages := make(map[string]int)
ages["John"] = 37
ages["Mary"] = 24
ages["Mary"] = 21 // overwrites 24

// or

ages = map[string]int{
"John": 37,
"Mary": 21,
}

Length

ages = map[string]int{
"John": 37,
"Mary": 21,
}
fmt.Println(len(ages)) // 2

Mutations

myMap[key] = elem // insert

elem = myMap[key] // get by key

delete(myMap, key) // delete by key

elem, ok := myMap[key] // check if a key exists

Nested

map[string]map[string]int
map[rune]map[string]int
map[int]map[string]map[string]int


Pointers

Init Empty Pointer

var p *int

fmt.Printf("value of p: %v\n", p)
// value of p: <nil>

Get Pointer

myStr := "hello" // myStr is just a string
myStrPtr := &myStr // myStrPtr is a pointer to myStr's address

fmt.Printf("value of myStrPtr: %v\n", myStrPtr)
// value of myStrPtr: 0x140c050

Get Original Value

myStr := "hello" // myStr is just a string

*myStrPtr = "world" // set myStr through the pointer
fmt.Printf("val of myStr: %s\n", *myStrPtr) // read myStr through pointer
// value of myStr: world

Pass by Reference

func increment(x *int) {
*x++
}

func main() {
x := 5
increment(&x)
fmt.Println(x)
// 6
}

Pointer to Struct

type Car struct {
Price int
}

func printPrice(car *Car) {
// price := *car.Price // encounter an error
price := car.Price
// OR
// price := (*car).Price
fmt.Println(price) // => 100000
}

func main() {
car := Car{ Price: 100000 }
printPrice(&car)
}

Receivers of Methods

type Car struct {
Price int
}

func (car *Car) setPrice(price int) {
car.Price = price
}

func main() {
car := Car{ Price: 100000 }
car.setPrice(7000)
fmt.Println(car.Price) // => 7000
}


Channels

Init Channel

// initilize a channel with integer type
myAwesomeChannel := make(chan int)

Buffered Channel

myAwesomeChannel := make(chan int, 10)

This means writing on a buffered channel only blocks when the buffer is full, and receiving from a buffered channel only blocks only when the buffer is empty

func main() {
ch := make(chan int, 3)
// write to channel
ch <- 2
ch <- 5
ch <- 87
// read from channel
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println("Processed")
}

Write/Read Channel

// send data to the channel
myAwesomeChannel <- 7

// read data from the channel
x := <- myAwesomeChannel

Close Channel

myAwesomeChannel := make(chan int)
close(myAwesomeChannel)

func main() {
ch := make(chan int, 3)
ch <- 2
ch <- 5
close(ch)
// ch <- 87 // error - the channel already closed
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 5
fmt.Println(<-ch) // 0
}