About

Language Name

Go and Golang are the same language.

The language is most correctly called Go.

Language Features

Statically typed

Compiles to machine code

Post-OOP: neither Object Oriented nor Functional, but has similarities to both

History

Developed by engineers at Google

Aimed to:
- compile fast
- compile fully to machine code, so it will run fast
- be strongly typed
- be concurrent by default (built-in thread management)
- use garbage collection (aimed to pause as little as possible)
- be simple (syntax, maintencance, etc)

Designed to be good at:
- general purpose, many applications
- web services (deliver data)
- web applications (deliver webpages)

Turned out to also be good for:
- task automation (syntax as light as some scripting languages)
- machine learning (syntax light enough to compete with python)

Getting Started

Hello World


package main

import "fmt"

func main() {
    fmt.Println("Hello world")
}

File Extensions

Go files have the extension ".go"

Online

Online editor to practice Go in:
https://go.dev/play/

Locally

Install Go:
- Download Go from https://go.dev/
- try to use the default installation location, to avoid integration issues later
- Open Command Prompt
- verify installation worked by running "go version"

Install an IDE:
- Visual Studio Code is recommended by the walkthrough I'm watching
- Install Go plugin
- Extensions (left-hand nav pane) > search for Go > install the Go Plugin
- click Ctrl+Shift+P to open Command Window > search for "Go: Install/Update Tools" > select all the suggested tools > run this to install the tools

If the tool starts acting up, run "Go: Install/Update Tools" again as a first step.

Start an application
- Visual Studio Code > new file
- Save file as "main.go"
- type in the Hello World program
- run it with command prompt > "go run main.go"
- program is compiled and executed


Spacing

Go is formatted with tabs and line breaks.

The tabs are not required, but they are highly recommended.

The line breaks are required if you don't end your lines with semicolons.
The compiler does automatically add the endline semicolons.
The recommended style seems to be to not write the semicolons yourself.

The javascript-like curly braces is required.

func abc() {
}


Documentation

Basic

Comments on packages and public members are automatically included in the module documentation.

Run "go doc {qualified name}" to test your documentation.

Comments

Single line

// comments
// comments

Multiline

/*
comments
comments
*/

Style

Use complete sentences.
Start the first sentence with the element's name (or an article, then the name).
Write the first sentence as a "short description" of the element.

Package

You can include documentation throughout your code files.

// license comments
// license comments
// license comments

// Package {name} {intent}
// doc comments
// doc comments
package foo

You can also include a "doc.go" to hold all the documentation for a directory/package. Use this for long package comments.

// license comments
// license comments
// license comments

/*
    Package {name} {intent}
    doc comments
    doc comments
*/
package foo

Members

You can add comments/documentation to any member (variable, type, function, method).

Capitalization

Uses camel-case, such as MyWidget.

Start with a capital letter
- Public Scope
- available to all package consumers

Start with a lowercase letter
- Package Scope
- available within the package

Modules

A project space.

A module contains packages.

A space to organize your project. Replacing the "Workspace".
- Make a folder to house your source code.
- Add a "go.mod" file to this folder.
- see CLI for initializing this file
- "go.mod" will containing configuration for the module

Packages

Basic

Organize source code into manageable units.

A package includes all source files in one directory, with the exception of subdirectories (which form their own packages).
- Putting files in a directory is sufficient to declare a package (other than main).
- Folder name must match the package name.

Package Elements:
- Package declaration
- Documentation
- Imports
- Var and Const Blocks
- Types and Interfaces
- Functions

Everything declared in a package is accessible withing the package.

The package declaration is at the top of a code file.

package main

Package Types

Library Package:
- Designed to be consumed by other packages
- Package name must match the directory name
- Should provide a focused set of related features

Main Package:
- Defines an application entry point
- Contains a "main" function
- Package name must be "main", and does not need to match the directory name
- Can be in any directory
- Should focus on application setup and initialization

Naming Conventions

All Lowercase
No Underscores
Short
Clear
Nouns
Abbreviate Judiciously (see Clear)
Don't Lockdown Generic Names (such as user, action, event)

Avoid multi-word package names, but just concat the words together if you must.

With package.element names, avoid stutter (like http.HTTPServer, json.JSONEncoder). The package name is required for all usages of its elements, so take advantage of that when naming the elements.

Import

Import process
1. Import packages this one depends on
2. Initialize package-level variables
3. Run init functions

Example of importing a package from your own module

//golangPractice/models/widget.go
package models

type Widget struct {
    ID        int
}

//golangPractice/main.go
package main

import (
    "golangPractice/models"
)

func main() {
    x := models.Widget
}

Internal

The directory name "internal" is a special case - these packages will not be made available outside of your module.

When a package is under an "internal" directory, it can only be imported by packages that share the immediate ancestor of the internal directory.
- Ex: "/a/b/c/internal/d/e/f" can only be imported by packages under "/a/b/c"

Design

Provide a clear solution to a problem.
- Single Responsibility
- Cohesive API

Focus on the consumer.
- Simple to use
- Minimize API surface
- Encapsulate changes (enable non-breaking changes)

Maximize reusability.
- Reduce dependencies
- Minimize scope

Interface strategies
- Concrete type arguments are great for accepting configuration
- Interface type arguments are great for defining behavior
- Concrete return types are great for returning data
- Follow the convention of returning Errors, avoid using Panics

Import

Import functionality from libraries so you can use them in your code.

import "fmt"
import "os"

Import Block

import (
    "fmt"
    "os"
)

All uses of a library require the qualified name:

import "fmt"
import "net/http"

func main() {
    fmt.Println("abc")
    http.ListenAndServe(":8080", nil)
}

Alias to resolve naming conflicts

import (
    "encoding/json"
    myjson "myproject/x/json"
)

func main() {
    json.Marshal(...)
    myjson.Foo()
}

Import a package just for the init functions:

import (
    "fmt"
    _ "somepackage"
)
Otherwise you'll get a compile-time error for importing a package without using it.

Relative imports: navigating the directory structure with . and ..
- Not valid in workspaces or modules.
- Intended for rapid prototyping.
- Probably don't need this, since modules have been invented.

Vendor directories: managing multiple versions of the same library.
- Apply to workspaces only (not to modules).

Main

Every program needs an entry point (aka starting point). That is the "main" function.

func main() {
}

You need a "main" package, and it needs to contain a "main" function. The filename is "main.go".

Operators

Logical

&&
||
!

Comparison

==
!=
<
<=
>
>=

Assignment

:=
=
+=
-=

Increment, Decrement

++
--

Note that these form statements, not expressions.
So i++ is valid as an entire statement, but x := i++ is not valid.

Blank Identifier _

AKA Write-Only Variable

Accept a value that you do not use - avoid compiler errors

a, _ := returnMultipleValues()

for _, v := range myCollection {
}

Spread ...

Convert a collection into a series of arguments.


users = append(users[:i], users[i+1:]...) //remove one element from a slice

numbers := []int{1, 2, 3}
fmt.Println(numbers...)

Variables

Declaration


var x int
x = 1

Expliciti Syntax, Explicitly Types

var x int = 1

Implicit Initialization Syntax, Implicitly Typed

x := "bob" //a string
You can only use this assignment operator to declare the variable

Multiple Assignments

x, y := 4, "smith"

Implied types

type MyType struct {
    a, b, c int //all three variables are ints
}

Local variables must be used, or you will get a compiler error.

Types

Default Values

nil - pointers, errors
0 - integers, other number types
empty string - string

Primitives

These are Value Types. The variable's memory slot holds the value directly.

bool //true, false
int
string //"double quotes"

float32 (32 bits)
float64 (64 bits)

complex64
complex128

x := complex(3, 4) //3+4i

String formatting

func ToString(sv SemanticVersion) string {
    return fmt.Sprintf("%d.%d.%d", sv.major, sv.minor, sv.patch)
}

Pointers

Pointer variables hold the memory address of the value.

Pointers are declared with an asterisk(*) before the primitive type.

var p *string
fmt.Println(p) //outputs "<nil>" for empty


var p *string = new(string) //initialize the memory
*p = "apple" //can only set the value after initializing the memory
fmt.Println(p) //outputs the memory address, not the primitive value
fmt.Println(*p) //outputs "apple"


x := "apple"
p := &x //set pointer p to the address of x
in this example, editing primitive x will change the value p dereferences to

Golang does not allow Pointer Arithmetic.

Pointer Operator: the asterisk in: var p *string
Dereferencing Operator: the asterisk in: *p = "apple" AND fmt.Println(*p)
Address Of Operator: the ampersand in: p := &x

Constants

Constant values cannot change after they are initialized.
Constant values must be initialized when they are declared.
Constant values must be able to be determined at compile time.


const x = 1
const y int = 2

The expression on the right-side of a constant assignment is a Constant Expression.

Constant Blocks

Constants can be declared at the package level
(You can also have Variable Blocks using var)


package main

import "fmt"

const pi = 3.14

func main() {
}

Constant Block

const (
    pi = 3.14
    x = "apple"
)

Iota: starting from 0 and incrementing by 1 each time it is called

package main

import "fmt"

const (
    a = iota
    b = iota
)

func main() {
    fmt.Println(a, b) //outputs 0 1
}

Unspecified constant expressions default to the same as the previous line

const (
    a = iota + 6 //set to 6
    b            //set to 7
)

You can specify multiple Constant Blocks.
Iota resets for each Constant Block.

Error

Error is a pointer data type, and is a built-in interface type.
The type is called "error" so don't use that as a variable name.

Standard usage is for a function to return a tuple of (success, failure)

success, err := os.Open("my_file.txt")
if err != nil {
    log.Fatal(err)
}
The idea is that the caller can decide if they can handle the error or not.

Example function using this pattern

import "errors"

func divide(x int, y int) (int, error) {
    if y == 0 {
        return -1, errors.New("cannot divide by 0")
    }
    return x/y, nil
}

Message formatting

return User{}, fmt.Errorf("user with id '%v' not found", id)

Errors can be used to return "I am done" sort of information, such as when you are looping through a file and hit "end of file". You expect to hit the end of the file on every usage, so it is more informative than an error. Success/Failure becomes Continue/Stop.

Interface


type myInterface interface {
    function1() int
    function2() float64
}

Interfaces are used implicitly.
You do not declare that a type is implementing an interface, you just implement it and the compiler recognizes it.

I think this tests if a variable currently typed as one interface can be cast to another interface

asInterfaceB, is := asIntefaceA.(InterfaceB)
if !is:
    //return error

Inherited interfaces

type aaa interface {
    functionA() int
}

type bbb interface {
    aaa
    functionB() int
}

Type Alias


type MyString string
Enables segregation of attached methods.
Enables attaching methods to types from other packages.

Casting


var x int = 1
fmt.Println(float32(x) + 2.5)

var s string = "abc"
fmt.Println([]byte(s))

Converting

String to integer

i := strconv.Atoi("15")


Collection Types

General Use

Length

x := []int{1, 2, 3}
y := len(x)

Is this an array or a slice?

import "reflect"

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var b = a[:]
fmt.Println(reflect.TypeOf(a)) //outputs [8]int so it is an array
fmt.Println(reflect.TypeOf(b)) //outputs []int so it is a slice

Arrays

Arrays are fixed-sized, indexed, and all the elements have the same type.
Array sizes must be Constant at compile-time. See Slices if the length is determined at run-time.


var x [3]int //holds 3 integers
x[0] = 15
x[1] = 25
x[2] = 35
fmt.Println(x) //outputs [15 25 35]
fmt.Println(x[0]) //outputs 15


x := [3]int{15, 25, 35}
fmt.Println(x) //outputs [15 25 35]

Array with length set to however many elements it starts with

x := [...]int{1,2,3,4,5,6,7,8}

Array of pointers

x := [3]*MyStruct

Slices

Slices are dynamically-sized, indexed, and all the elements have the same type.
Slices are built on top of underlying Arrays.

Slice syntax is [start:end].


x := [3]int{15, 25, 35} //Array
y := x[:] //Slice
fmt.Println(y) //outputs [15 25 35]

y := make([]int, 5) //Slice of length 5

y := make([]int, 0, 5) //Slice of length 0 and capacity 5
The slice points to the array, so editing x will update y.


y := []int{15, 25, 35} //Slice initialized with underlying Array

Append an element

y := []int{15, 25, 35}
y = append(y, 45)
y = append(y, 55, 65)
The underlying array changes are handled automatically. It will allocate memory for new arrays as needed.

Working with the indexes

y := []int{15, 25, 35}
a = y[:] //the whole thing
b = y[1:] //1st index through the end
d = y[1:2] //just index 1, index 2 is not included
e = y[:3] //since the last index specified is not included, using the array length is valid
Does not support negative indexes.

Append

x := []int{1, 2, 3}
y := []int{4, 5}
println(append(x, 4))
println(append(x, 4, 5))
println(append(x, y...))

Copy - copy from source to destination, return the number of elements copied



Sort - sorts the slice in place

sort.Slice(mySlice, func(indexA, indexB int) bool {
    return mySlice[indexA].Field < mySlice[indexB].Field
})

Maps

Maps are a collection of key/value pairs.


x := map[string]int{"a":1} //string is the Key type, int is the Value type
x["a"] = 2
fmt.Println(x["a"]) //outputs 2

Delete

x := map[string]int{"a":1, "b":2}
delete(x, "a")
fmt.Println(x) //outputs map[b:2]

Structs

Structs are a collection of multiple types of data (fields).
(The lesson I'm watching does call it a Collection type)

Structs can be defined within a function, or at the package level.

Define the struct type, then create an instance of it.

type widget struct {
    ID int
    FirstName string
    LastName string
}
var x widget
fmt.Println(x) //outputs {0 } //int defaulted to 0, strings defaulted to empty

x.ID = 1
x.FirstName = "Bob"
x.LastName = "Smith"
fmt.Println(x) //outputs {1 Bob Smith }
fmt.Println(x.LastName) //outputs Smith

Shorter initialization syntax

x := widget{ID:1, FirstName:"Bob", LastName:"Smith"}

y := widget{ID:1, 
    FirstName:"Bob", 
    LastName:"Smith", //this lines needs a comma or the closing curly brace
    }

Structs can be empty, when you just been a type to bind methods to

type EmptyStruct struct {}

You can immediately take the address of a new struct instance

func foo() *MyType {
    return &MyType {
        myField: 1,
    }
}

define a struct for one-time use

scenarios := []struct {
    inputA int
    inputB int
    expect int
}{
    { inputA:1, inputB:2, expect:3 },
    { inputA:-1, inputB:2, expect:1 },
    { inputA:0, inputB:2, expect:2 },
}

Functions

Basic

Basic syntax

func functionName() {
}

Calling a function

func main() {
    i := 1
    foo(i, 5);
}

func foo(index int, retryCount int) {
    fmt.Println("foo", index, retryCount)
}
Parameters are what is in the function declaration.
Arguments are passed when you call a function.

Parameters of the same type can be implied

func foo(index, retryCount, other int) {
}
When a parameter type is not explicit, it is assumed to be the same as the next parameter with an explicit type.

Returning a value

func foo() bool {
    return true
}

See Tuples for returning multiple values

a, b := func()

func foo() bool, int {
    return true, 5
}

Functions are first-class citizens - they can be treated as variables

x := foo //function stored as a variable
println(x(1, 2)) //invoke the function later
println(runner(x))//pass it as an argument

func sum(a int, b int) int {
    return a + b
}

func runner(f func(int, int) int) {
    f(1, 2)
}

var f func() int //variable of type func accepting no arguments and returning an int
f := foo
f()

func foo() int {
    return 4
}

Return Multiple Values (Tuples)

Returning and assigning a tuple

func main() {
    a, b, c := foo()
}

func foo() (string, int, int) {
    return "Bob", 1, 15
}
You can only pass tuples, you cannot instantiate a tuple variable nor assign the whole tuple to one variable.

See "Error" for conventional Go pattern for returning multiple values.

Named Return Variables


func divide(a, b int) (answer int, err error) {
    if b == 0 {
        err = errors.New("cannot divide by zero")
        return
    }
    answer = a / b
    return
}
The empty "return" will return the current values of the named return variables

Public/Private

Functions that start with a captial letter are Public outside a package.

Functions that start with a lowercase letter are Private within a package.

This is the convention the compiler understands.

Method

Bind functions to types to turn them into Methods. This adds behavior to the type.
Methods can access and edit private fields on a type.

You can only define a method on types that are declared in your package.

Value Receiver: The methods works with a copy of the value.

myType.MethodName()

func (paramName MyType) MethodName() {
    fmt.Println(paramName)
}
That first thing after the "func" keyword is the "method receiver".

Pointer Receiver: Pass in structs/etc as pointers, so that when you modify the object it is not modifying a copy of the object.

myType.MethodName() //note that the call is made in the same way

func (paramName *MyType) MethodName() {
    paramName.field++
}

Constructor

For initializing data.
Looks like a Factory pattern function, used by convention in Go programs.

Create a new instance of the type and return a pointer to it

func NewMyType() *MyType {
    return &MyType {
        myField: 1,
    }
}
The "New" prefix is convention, it is not required.
Convention is also to return pointers, not structs.

Init

Init functions will initialize application state when it starts up. It is recommened to use this only when absolutely necessary.

The init function is defined in a package block.
It will be run exactly once, even if the package is imported multiple times.


func init() {
}

You can define multiple (at least 2) init functions in one file. They will be run in the order they are defined in.

Warning: there is no promise on what order init functions from separate files will be called in.

You cannot explicitly call an init function within your application.

Variadic

A variadic function can be called with any number of trailing arguments.

fmt.Println(1, 2, 3, 4, 5, 6, 7, 8) //etc

Declaration

func sum(numbers ...int) int { //accepts any number of integer arguments, as an array
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}

The variadic parameter must be the last parameter.

Overloading

Overloading here means multiple functions with the same name and different parameter lists.
Golang does not support function or method overloading.

Note that method names do not conflict with function names, and method names only conflict when attached to the same type.

Anonymous

Anonymous functions can accept arguments and return values, or not.

Run immediately

func foo() {
    func() { //anonymous function definition
        println("running")
    }() //this last () tells it to run/invoke the function
}

Store as a variable and invoke later

func foo() {
    f := func() {
        println("running")
    }
    f()
}

Return a function

addExpression := mathExpression()
println(addExpression(2, 3))

func mathExpression() func(int, int) int {
    return func(a int, b int) int {
        return a + b
    }
}
Function "mathExpression" returns a func that accepts two int arguments and returns an int.

Maintaining State

AKA Stateful Functions


generator := idGenerator()
println(generator()) //outputs 1
println(generator()) //outputs 2

func idGenerator() func() int {
    id := 0 //the state of outer variables will be preserved
    return func() int {
        id += 1
        return id
    }
}
Each instance from calling "idGenerator()" creates a separate instance of the closure.

Sometimes state is shared when you don't mean it to be. All these closures end up operating on the final value of "i":

var funcs []func() int
for i := 0; i < 5; i++ {
    funcs = append(funcs, func() int {
        return i * 10
    })
}
for _, f := range funcs {
    println(f()) //outputs a lot of 50s
}
You need to create a new copy of "i", like this:

var funcs []func() int
for i := 0; i < 5; i++ {
    cleanI := i
    funcs = append(funcs, func() int {
        return cleanI * 10
    })
}
for _, f := range funcs {
    println(f()) //outputs 0 10 20 30 40
}

Defer

Ensure a function is invoked later.
Usually used for cleanup, such as closing a database connection, even when an error has occurred.

The deferred function will be run at the end of the enclosing function.

package main

import (
    "fmt"
    "os"
)

func main() {
    f := createFile("filename")
    defer closeFile(f)
    writeFile(f) //even if an error occurs during writing, the file will still be closed
}

func createFile(p string) *os.File {
    fmt.Println("creating")
    f, err := os.Create(p)
    if err != nil {
        panic(err)
    }
    return f
}

func writeFile(f *os.File) {
    fmt.Println("writing")
    fmt.Fprintln(f, "data")
}

func closeFile(f *os.File) {
    fmt.Println("closing")
    err := f.Close()
    if err != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", err)
        os.Exit(1)
    }
}
Defers are pushed onto a stack. When there are multiple defers in the same function, the last one deferred will run first.

Defers will still be run when a Panic occurs.

Flow Controls

For

Loop till condition

var i int //defaults to 0
for i < 5 {
    i++ //increment operation
}

Loop till condition with post clause

for i := 0; i < 5; i++ { //i only exists within the scope of the loop
}

var i int //i exists outside the loop
for ; i < 5; i++ {
}

Infinite loop, explicit

for {
}

Break out of loop early

var i int
for i < 5 {
    break
}

Continue with next iteration of the loop

var i int
for i < 5 {
    i++
    continue
    println(i) //this line is never run
}

For Range

Loop over collections

x := []int{1, 2, 3}
for i, v := range x { //i is index, v is value
    println(i, v)
}

x := []int{1, 2, 3}
for i := range x { //i is index, value is ignored - useful for maps
    println(i)
}

x := []int{1, 2, 3}
for _, v := range x { //index is ignored, v is value
    println(v)
}

Note that the "value" is a copy - editing it will not update the original value.

If Else


if i < 5 {
}
if x == y {
}
if 1 != 2 {
}


if x == y {
} else {
}


if x == y {
} else if x < 5 {
} else { //final else statement is not required
}

If Assignment Scope

x := getValue()
if x == 0 {
}

if x := getValue(); x == 0 {
}
Both of these examples are valid. The second one does not clutter the current function scope with variable "x".

Switch

The tabbing looks weird to me, but it is the convention.


x := "b"
switch x {
case "a":
    println("found a")
case "b":
    println("found b")
case "c":
    println("found c")
}
Each case statement has an implicit break - so cases do not fall through to the next one.

Explicit fallthrough

switch x {
case "a":
    println("found a")
    fallthrough
case "b":
    println("found b")
}

Default case

switch x {
case "a":
    println("found a")
case "b":
    println("found b")
default:
    println("found default")
}

Panic/Recover

Similar to exceptions in other languages - the program does not know how to proceed from an error.


panic("Database connection failed")
This will be output to stdout with a stack trace
Code execution stops at a panic

When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic. The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes.

Recover is a built-in function that regains control of a panicking goroutine. It is only used within "defer" functions.

defer func() {
    _ = dbConnection.Close() //try to cleanup normally
    if r:= recover(); r != nil { //check if a panic is occurring
        println("recovered from panic", r) //print what was passed into the panic
    }
}

Pointers

Pass By

"When you pass a pointer as an argument, what happens under the hood is that a copy of that pointer is created and passed to the underlying function. It should not be confused with pass-by-reference."

demonstration

package main

import (
    "fmt"
)

type Point struct {
    x int
    y int
}

func (p Point) String() string {
    return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

func modifyValue(point Point) {
    point.x += 10
}

func modifyPointer(point *Point) {
    point.x = 5
    point.y = 5
}

func modifyReference(point *Point) {
    point = &Point{5, 5} //the new pointer now points to a new object
}

func main() {
    p := Point{0, 0}
    fmt.Println(p) // prints (0, 0)
    
    modifyValue(p)
    fmt.Println(p) // prints (0, 0)
    
    modifyPointer(&p)
    fmt.Println(p) // prints (5, 5)
    
    p = Point{0, 0}
    modifyReference(&p)
    fmt.Println(p) // prints (0, 0)
}

Range

The "range" operator returns copies of values, not pointers to the values.

demonstration

package main

import "fmt"

type customer struct {
    name string
}

func main() {
    customers := []customer{customer{name: "Daisy"}}
    for _, c := range customers {
        c.name = "Bob" //this is editing a copy of the customer
    }
    fmt.Println(customers) //outputs [{Daisy}]
}

I/O

Built-in


println("abc")
println("abc", 1)

Fmt.Println


fmt.Println("abc") //outputs abc

x := 3
fmt.Println(x) //outputs 3

a, b := 1, 2
fmt.Println(a, b) //outputs 1 2
Command Prompt (CLI)

Help

list out all options

go

print the version number

go version

display help files for the CLI

go help test
go help {etc}

display documentation from a package (based on the comments)

go doc {qualified name}
go doc json.Decoder
go doc json.Decoder.Decode

Module

initialize a module with a "go.mod" file

go mod init {name of module}
if you want the module to retrieve files from git

go mod init github.com/pluralsight/webservice

Run

compile and run the selected file

go run {main filename}
go run main.go
go run {module name}
go run .
the compiled code is stored in a temp location, it is not left in the current folder

Build

compile and keep the result, also run

go build .
the executable file defaults to {current_folder_name}.exe
the executable is specific to the OS it was compiled on

Test

run tests

//run all tests in current and descendant directories
go test ./...

//run all tests in current directory
go test
go test .

//run some package's tests
go test {package_name} {package_name} {etc}
option "-v" for verbose
option "-run {regexp}" for only test names that match the pattern
option "-cover" for a code coverage report
option "-coverprofile {filename}" to save coverage report to the file (read it with "go tool cover -func {filename}"
option "-bench" to include benchmark tests in the run
option "-bench -benchtime 10s" to include benchmark tests and set the time for them to run
see "go help testflag" for all options


go test -run=nope ./...
I think the "-run=" accepts regex, so this is just a piece of text that isn't matching anything

RegExp

working example

package main

import (
    "fmt"
    "regexp"
)

func main() {
    r, _ := regexp.Compile("\"effective\":\"([0-9]{4}-[0-9]{2}-[0-9]{2})\"")
    input := "abc \"effective\":\"2021-07-01\" def"
    fmt.Println(r.FindString(input)) //full text match
    fmt.Println(r.FindStringSubmatch(input)[1]) //capture group match
}

Web Service

small working example of a web service


//golangPractice/main.go
package main

import (
    "golangPractice/controllers"
    "net/http"
)

func main() {
    controllers.RegisterControllers()
    http.ListenAndServe(":3000", nil) //port 3000
}

//run and navigate to localhost:3000/users to test it


//golangPractice/models/user.go
package models

type User struct {
    ID        int
    FirstName string
    LastName  string
}

var (
    users  []*User
    nextID = 1
)

func GetUsers() []*User {
    return users
}

func AddUser(user User) (User, error) {
    user.ID = nextID
    nextID++
    users = append(users, &user)
    return user, nil
}



//golangPractice/controllers/front.go
package controllers

import "net/http"

func RegisterControllers() {
    uc := newUserController()
    http.Handle("/users", *uc)
    http.Handle("/users/", *uc) //includes routes with more stuff past "users"
}

//see front controller / back controller pattern


//golangPractice/controllers/user.go

package controllers

import (
    "encoding/json"
    "golangPractice/models"
    "net/http"
    "regexp"
    "strconv"
)

type UserController struct {
    userIDPattern *regexp.Regexp
}

//implements an interface, so RegisterControllers can use it
func (uc UserController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path == "/users" {
        switch r.Method {
        case http.MethodGet:
            uc.getAll(w, r)
        case http.MethodPost:
            uc.post(w, r)
        default:
            w.WriteHeader(http.StatusNotImplemented)
        }
    } else {
        matches := uc.userIDPattern.FindStringSubmatch(r.URL.Path)
        if len(matches) == 0 {
            w.WriteHeader(http.StatusNotFound)
        }
        id, err := strconv.Atoi(matches[1])
        if err != nil {
            w.WriteHeader(http.StatusNotFound)
        }
        switch r.Method {
        case http.MethodGet:
            uc.get(id, w)
        case http.MethodPut:
            uc.put(id, w, r)
        case http.MethodDelete:
            uc.delete(id, w)
        default:
            w.WriteHeader(http.StatusNotImplemented)
        }
    }
}

func (uc *UserController) getAll(w http.ResponseWriter, r *http.Request) {
    encodeUsersAsJson(models.GetUsers(), w)
}

func (uc *UserController) get(id int, w http.ResponseWriter) {
    user, err := models.GetUserByID(id)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    encodeUserAsJson(user, w)
}

func (uc *UserController) post(w http.ResponseWriter, r *http.Request) {
    user, err := uc.parseRequest(r)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("cannot parse User object"))
        return
    }
    user, err = models.AddUser(user)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte(err.Error()))
        return
    }
    encodeUserAsJson(user, w)
}

func (uc *UserController) put(id int, w http.ResponseWriter, r *http.Request) {
    user, err := uc.parseRequest(r)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("cannot parse User object"))
        return
    }
    if id != user.ID {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("user id does not match url id"))
        return
    }
    user, err = models.UpdateUser(user)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte(err.Error()))
        return
    }
    encodeUserAsJson(user, w)
}

func (uc *UserController) delete(id int, w http.ResponseWriter) {
    err := models.RemoveUserById(id)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte(err.Error()))
        return
    }
    w.WriteHeader(http.StatusOK)
}

func (uc *UserController) parseRequest(r *http.Request) (models.User, error) {
    decode := json.NewDecoder(r.Body)
    var user models.User
    err := decode.Decode(&user)
    if err != nil {
        return models.User{}, err
    }
    return user, nil
}

//the lesson had a generic handler for all json, using interface{}, but it wasn't compiling for me
func encodeUsersAsJson(data []*models.User, w http.ResponseWriter) {
    js, err := json.Marshal(data)
    writeJsonResponse(js, err, w)
}

func encodeUserAsJson(data models.User, w http.ResponseWriter) {
    js, err := json.Marshal(data)
    writeJsonResponse(js, err, w)
}

func writeJsonResponse(js []byte, err error, w http.ResponseWriter) {
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte(err.Error()))
    }
    w.Header().Set("Content-Type", "application/json")
    w.Write(js)
}

func newUserController() *UserController {
    return &UserController{
        userIDPattern: regexp.MustCompile(`^/users/(\d+)/?`),
    }
}
Unit Tests

Tests

Verify behavior.
Go does not use Assertions, just normal comparison logic.
By default, tests run sequentially.

File name: main.go and main_test.go
Location: in the same folder
Package: white box testing: main and main (get access to internals of the package)
Package: black box testing: main and main_test

Black box testing is considered best practice here.


package main_test

import "testing"

func TestAddition(t *testing.T) {
    got := 2 + 2
    expected := 4
    if got != expected {
        t.Errorf("expected '%v', got '%v'", expected, got)
    }
}
The "Test" prefix on the function name is required by the test runner. So "Test" and then an upper-case letter.
Test functions must accept just a *testing.T parameter.

Visual Studio Code lets you run a test right in the IDE.
You can also run them all from the CLI with "go test".

See package testing/quick for black-box testing.
See package testing/io for reader/writer testing.
See package net/http/httptest for request/response testing, and for local end-to-end tests.

Outside of the standard libraries, see Testify, Ginkgo, GoConvey, httpexpect, gomock, and go-sqlmock.

Test files are not included in production executables.

Immediate failures - current test exits immediately

t.FailNow()

t.Fatal(args ...interface{})

t.Fatalf(format string, args ...interface{})

Non-Immediate failures - test has failed and continues to run

t.Fail()

t.Error(args ...interface{})

t.Errorf(format string, args ...interface{})

Table-Driven Test

func TestAdditionTableDriven(t *testing.T) {
    scenarios := []struct {
        inputA int
        inputB int
        expect int
    }{
        { inputA:1, inputB:2, expect:3 },
        { inputA:-1, inputB:2, expect:1 },
        { inputA:0, inputB:2, expect:2 },
    }
    for _, scenario := range scenarios {
        got := scenario.inputA + scenario.inputB
        if got != scenario.expect {
            e.Errorf("input a '%v' b '%v', expected '%v', got '%v'", scenario.inputA, scenario.inputB, scenario.expected, got)
        }
    }
}

Write out a message without failing: see testing.T.Log and Logf.
Mark a function as not-a-test: see testing.T.Helper.
Skip a test: see testing.T.Skip, Skipf, and SkipNow.
Use callback functions to run sub-test suites: see testing.T.Run.
Mark tests that can be run in parallel: see testing.T.Parallel.

Benchmark Tests

Verify performance.


package main_test

func BenchmarkFoo(b *testing.B) {

}
Be default, the timer runs the whole time the test is running. To control that, see b.StartTime, b.StopTimer, and b.ResetTimer.

b.N = number of seconds to keeping running the test for
b.RunParallel can run another function in parallel, such as a Test that stresses the system

Run these with "go test -bench"

For profiling:
"go test -benchmen"
"go test -trace {filename}"
"go test -{type}profile {filename}"

Example Tests

For documentation.
Examples will be executed (to confirm they are runnable) and will be included in "go doc" documentation.


package main_test

import "module/foo"

func ExampleFoo() {
    foo.Bar()
    // Output: foo bar
}

Naming conventions:
- ExampleFuncName
- ExampleFuncName_second
- ExampleFuncName_third
- ExampleTypeName
- ExampleTypeName_MethodName
- Example //for the whole package

Errata

Don't have non-test files referencing public values in test files. The compiler will tell you the values are not available, due to the order of compilation. (Plus, why would did we try to do that at all?)