Skip to content
E. Lynette Rayle edited this page Nov 3, 2022 · 17 revisions

Reference

App Basics

Parts of an application

  • project directory (e.g. my_app)
    • main.go - required for executing an app
    • go.mod - defines module name, go version, and dependencies
  • sub-directories - define organization of code within the app

Naming Conventions/Requirements

Characteristics of the app structure:

  • MUST have a starting folder with the same name as the app (e.g. my_app)
  • MUST have starting point main.go file in the starting folder

Characteristics of the starting point for an app:

  • MUST filename: main.go
  • MUST package: main (i.e. package main)
  • MAY have imports to include packages (e.g. import "fmt")
  • MUST function: main (i.e. func main())
  • MAY have additional functions
  • MAY have type definitions

Characteristics of additional files:

  • MUST filename: all lower case, _ to separate words, end with .go
  • MAY location: in same directory as main.go or in subdirectory
  • TYPICAL package: same name as directory (not sure this is a requirement)

Running an app

Running without compiling

Running the app from the starting point (i.e. main.go in package main):

go run main.go

OR

go run .

Running compiled app

Compile

Build the app from the starting point (i.e. main.go in package main):

go build .

Creates executable app named the same module defined in go.mod. This is typically the same as the name of the directory holding the starting point main.go

$ pwd
~/projects/my_app
$ grep module go.mod
module my_app
$ ls
main.go
my_app
Run
./my_app

Language Basics

Declaring Variables

Reference:

NOTE: Double quotes are used for strings. Single quotes are used for a rune.

Strictly typed, but with some provisions for inferred typing. Either way, once typed, the type cannot be changed.

Strictly type:

var s string
s = "Hello World"              // setting value after declaration

var s string = "Hello Again"   // setting initial value at declaration

Inferred type:

s := "And Again"               // infers type is string from the value assigned
i := 1                         // infers type is int from the value assigned
f := 2.3                       // infers type is float64 from the value assigned

Multiple defined at once:

var (
    s = "Hello"                // infers type is string from the value assigned
    i = 1                      // infers type is int from the value assigned
	f = 2.3                    // infers type is float64 from the value assigned
)

Constants

Prefix constant declarations with the keyword const.

const s string = "Always Hello"
s = "Nice Try"                 // results in compile-time error `cannot assign to x` 

Multi-item Variables

Arrays

Characteristics:

  • assigned length at declaration
  • memory auto-allocates to the size of the array
  • length never changes
  • all values are the same type
var x [5]int                   // allocate a 5-item array of integers
x[0] = 1                       // array values are [1 0 0 0 0]
x[2] = 3                       // array values are [1 0 3 0 0]
x[4] = 5                       // array values are [1 0 3 0 5]
x[5] = 6                       // results in compile-time error `array index 5 out of bounds [0:5]`
xl := len(x)                   // returns 5 as the length of the array

y := [5]int { 1, 2, 3 }        // allocate a 5-item array initialized with values [1 2 3 0 0]
yl := len(y)                   // returns 5 as the length of the array

z := [4]int{
        1,
        2,
        3,
}                              // allocate a 4-item array initialized with values [1 2 3 0]
zl := len(z)                   // returns 4 as the length of the array

ze := [2]int{
        1,
        2,
        3,
}                              // results in compile-time error `index 2 is out of bounds (>= 2)`

Slices

Slices can be thought of as a slice out of an array.

Characteristics:

  • length is not assigned at declaration
  • a slice can be created from an existing array by referencing a range of the array (capacity = size of array)
  • a slice can be created from an inferred array, in which case the slice is the full array and is at capacity
  • a slice can be created using the make method and passing parameters type, initial size, and optionally capacity (where capacity = initial size if unspecified)
  • size can change upto the capacity
  • slice does not have to start at index 0 of the backing array

NOTE: When specifying a range, the bottom of the range is inclusive, but the top of the range is exclusive (e.g. [3:5] includes items 3 and 4, but not 5).

a := [5]int { 0, 1, 2, 3, 4 }  // allocate a 5-item array with initialized with values [0 1 2 3 4]
slc := a[0:3]                  // creates a 3-item slice with capacity 5 with values [0 1 2]
slc = [3:5]                    // sets slice to 2-items with capacity 5 and values [3 4]
al := len(a)                   // 5 - length of array a
slcl := len(slc)               // 2 - length of slice slc

inf_slc := []int { 0, 1, 2 }   // allocate a 3-item slice with capacity 3 and values [0 1 2]
sub_slc := inf_slc[1:3]        // creates a 2-item slice with capacity 3 and values [1 2]

min_slc := make([]int, 3)      // allocate a 3-item slice with capacity 3 and default values [0 0 0]

lrg_slc := make([]int, 5, 500) // allocate a 5-item slice with capacity 500 and all values initialized to default 0

Maps

Unordered collection of key-value pairs.

Characteristics:

  • key type is the same for all keys
  • value type is the same for all values
  • key type can be any type
  • value type can be any type
  • allocation with make creates a map with lenght 0
  • can add a new item in the map without manually allocating memory
  • length changes by adding/removing map items

Create with...

m := make(map[string]int)      // allocate a 0-item map with string keys and int values
m["January"] = 1               // add item to map with key "January" and value 1; map now has length 1
m["February"] = 2              // add item to map with key "February" and value 2; map now has length 2

metric := map[string]string {
        "mm": "millimeter", 
		"m":  "meter", 
		"km": "kilometer",
}                              // allocate a 3-item map with string keys and string values initialized with values

imonths := map[int][2]string {
1: [2]string{ "Jan", "January" },
2: [2]string{ "Feb", "February" },
}                              // allocate a 2-item map with int keys and 2-string array values initialized with values

smonths := map[[2]string]int {
	    [2]string{ "Jan", "January" }: 1,
        [2]string{ "Feb", "February" }: 2,
}                              // allocate a 2-item map with int keys and 2-string array values initialized with values

Access item with...

m["January"]                   // returns 1
metric["km"]                   // returns "kilometer"
imonths[2]                     // returns ["Feb" "February"]
smonths[[2]string{ "Jan", "January" }] // returns 1

Delete item with...

delete(metric, "mm")           // deletes item "mm" from metric map
metric["mm"]                   // accessing after delete returns blank for type (e.g. "" for string, 0 for int, etc.)

Structs

This section describes structs as a basic multi-value type. See the Advanced for more on structs.

Characteristics:

  • can be defined to hold any mix of types
var item struct {
        name    string
        desc    string
        price   float32
        on_hand int
        on_sale bool
}
item.name = "shirt"
item.desc = "a cool shirt"
item.price = 9.99
item.on_hand = 21
item.on_sale = false

Custom Defined Types

Characteristics:

  • can be used to give a type name to any existing type
  • can be used to give a type name to a struct (most common usage)
type my_int int                // creates a new type my_int that behaves the same as int (probably don't want to do this)
var i my_int
i = 3

type Item struct {
        name    string
        desc    string
        price   float32
        on_hand int
        on_sale bool	
}                              // creates a new type Item that has the fields defined in the struct 
shirt := Item{ name: "shirt", desc: "a cool shirt", price: 9.99, on_hand: 21, on_sale: false }
shoes := Item{ name: "shoes", desc: "cool pair of shoes", price: 74.99, on_hand: 4, on_sale: true }

Control

Conditionals

if-else

if i == 0 {
	fmt.Println "Can't divide me"
} else if i < 0 {
	fmt.Println "Imagine squaring my root"
} else {
	fmt.Println "I can do anything"
}

switch

switch snum {
case "one": fmt.Println(1)
case "two": fmt.Println(2)
case "three": fmt.Println(3)
default: fmt.Println("GO")
}

Looping

For

i := 1
for i <= 3 {
	fmt.Println(i)
	i += 1
}

for i := 1; i <= 3; i++ {
	fmt.Println(i)
}

Over array

ints := []int{1, 2, 3}
for _, i := range ints {
	fmt.Println(i)
}

Advanced

Structs and Functions as Object Oriented Programming

To support OOP,

  • struct
    • defined as a type that represents the class (e.g. Rectangle)
    • define public instance variables (e.g. Height int, Width int) - external callers can set/read directly
    • define private instance variables (e.g. area int) - external callers cannot set or read
  • functions
    • receive the struct's type as receiver (e.g. func (r *Rectangle) method_name(parm_name parm_type) return_type)
    • defines public methods (e.g. func (r *Rectanble) Area() int)
    • defines private methods (e.g. func (r *Rectanble) calculate_area() int)

Class

A class defines attributes and methods. Attributes are defined by the fields in a struct type. Methods are the functions that receive the struct type.

Question: For organizational purposes, is the struct and functions normally stored in a package.

Example: Define a class to describe rectangles

Class name: Rectangle

type Rectangle struct ...

Attributes

Characteristics:

  • attributes are the fields in the class' struct type
  • capitalizing the first letter of the field indicates the field is public and can be used anywhere the instance of the class is available.
  • starting a field with a lower case indicates that only methods for the class should read/write the value of that field.

Example: Define a class with public and private attributes

Class name: Rectangle Public attributes: Height, Width Private attributes: area

type Rectangle struct {
	Height int
	Width  int
	area   int
}

_NOTE: What this means is that Height and Width will be visible outside the package where Rectangle is defined, but area won't. So within the package, you can do anything with area. But outside the package, area won't be visible.

Methods

Methods are functions that receive the struct type representing the class. The receiver is specified before the method name. In the following example, (r Rectangle) is the receiver and identifies this function as a method of the Rectangle class (i.e. Rectangle struct type).

// Grow expands the height and width of a rectangle (public method)
func (r *Rectangle) Grow(delta_height, delta_width int) {
  r.Height += delta_height
  r.Width += delta_width
  r.area = 0
}

// Area returns the area of the rectangle (public method)
func (r *Rectangle) Area() int {
  if r.area <= 0 {
    r.calculate_area()
  }
  return r.area
}

// calculate_area calculates the area of the rectangle (private method)
func (r *Rectangle) calculate_area() {
    r.area = r.height * r.width	
}

NOTE: If you don't pass in a pointer to the Rectangle, then r will be a copy of the passed in Rectangle and any changes will not update the original.

_NOTE: Methods Grow and Area are public and will be visible outside the package where Rectangle is defined, but private method calculate_area won't. So within the package, you can directly call calculate_area. But outside the package, calculate_area method won't be visible.

Object

An object is an instance of a class (i.e. struct type) that has methods (i.e. func with the struct type as the receiver). In this example, rect is an instance of Rectangle.

rect := Rectangle { 2, 3, 0 }

_NOTE: Within the package, you have to include a value for area even though it is private. Not sure what happens if you create the Rectangle outside the package.

Class methods

There aren't any class methods per se, but if there is a single class in a package, then any public methods that do not receive the instance of the class are class methods.

_NOTE: Not sure if it is common practice for a package to define one-and-only-one class (i.e. struct type).

Class constants

Similar to class constants.

Organization

Modules

Referenced:

Using modules

  • import the module in code
  • download the dependency
    • OPTION 1: a single dependency
    $ go get github.com/org/repo/path/to/module
  • OPTION 2: all dependencies imported in code that are not yet downloaded (in current directory)
    $ go get .
  • list currently downloaded dependencies
    $ go list -m all

Defining a module

The module is the top directory of the project. The main.go file is typically (always???) in this directory. The name of the module is defined in go.mod.

module my_module

Packages

Reference:

Definition

"A package in Go is simply a directory/folder with one or more .go files inside of it."

Package Naming Conventions

  • All lower case.
  • Single word. Do NOT use snake case or camel case. (e.g. GOOD: datastore, BAD: data_store, dataStore).
  • Shorter is better.
  • Specific is better, but must be unique. (e.g. GOOD: datastore, BAD: common)
  • No plurals (e.g. GOOD: datastore, BAD: datastores)

Package Path Conventions

  • Don't expose internal directory organization in the import path (e.g. src, pkg). TODO: Not completely sure how to avoid this yet.

Defining in code

  • Comment describing the package. Will display in godoc and other documentation tools. The first sentence is critical, as some tools may only show that.
  • Declare package following Naming Conventions
  • Optionally, identify code location from which go get must grab your code when it is used as a dependency in an external application. Insures the dependency cannot be downloaded from multiple locations potentially causing namespace clashing and unexecutable code.
// Package datastore ... (does what)
package datastore // import github.com/repo/package

Development Process and Lifecycle

Inline Code Documentation

Reference:

Run local doc server: (port # is an example)

godoc -http=6060

What files get documented

  • everything in $GOROOT/src/pkg
  • everything in $GOPATH work spaces
  • -path flag
  • running: godoc . in a source directory

What to document

type, variable, constant, function, and package

Conventions

  • first sentence should encapsulate the purpose of the thing being documented and should stand on its own if the rest of the documentation were dropped (for packages, the first sentence will appear in the package list)
  • use sentences to describe
    • what it does (purpose)
    • details that might impact what happens
    • what will be returned

Example

// Fprint formats using the default formats for its operands and writes to w.
// Spaces are added between operands when neither is a string.
// It returns the number of bytes written and any write error encountered.
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
  • comments not directly above items listed in 'What to document' will be ignored
  • EXCEPTION - comments starting with // BUG(who) will be included as known issues and will be listed at the end of the documentation in the Notes -> Bugs section.
// Title treats s as UTF-8-encoded bytes and returns a copy with all Unicode letters that begin
// words mapped to their title case.
//
// BUG(rsc): The rule Title uses for word boundaries does not handle Unicode punctuation properly.
func Title(s []byte) []byte {

NOTE: Not clear what the who is. In the example, it is rsc, but there is nothing in the method that refers to rsc. Is this a person who identified the bug, or is responsible for a fix for the bug?

Controlling output

  • Same Paragraph: adjacent lines are in the same paragraph
  • New Paragraph: use blank line to start a new paragraph
  • Pre-formatted test: begin line with extra spaces
  • URLS: automatically convert to html links

Long documentation

If documentation is long, include a file in the package's directory called doc.go.

Deprecations

Include Deprecated: in method documentation and it will be identified as deprecated in documentation.

Example

// Title treats s as UTF-8-encoded bytes and returns a copy with all Unicode letters that begin
// words mapped to their title case.
//
// Deprecated: The rule Title uses for word boundaries does not handle Unicode
// punctuation properly. Use golang.org/x/text/cases instead.
func Title(s []byte) []byte {

Debugging

Reference: Golang Debugging with Delve

Setup

go get github.com/go-delve/delve/cmd/dlv

Also required:

  • xcode be installed xcode-select --install
  • go env GOBIN - includes path to where dlv command was installed (e.g. ~/go/bin)
  • $PATH includes GOBIN path (e.g. export PATH="$PATH:$GOBIN")

QUESTION: Install for every project? Or is this install once and used by every project?

Debugging

Reference: CLI list of commands

Start debugger: (substitute name of file you want to debug for main.go)

dlv debug main.go

Set breakpoint:

break ./main.go:10

Commands:

  • break ./main.go:10
  • print x - shows the value of variable x
  • call c.Errors - executes the method call c.Errors and shows the result
  • next - next line
  • step - step into function
  • stepout - step out of current function
  • restart - restart program from within the debugger
  • continue - continue running to next breakpoint

Testing

Reference:

Testing in VSCode

  • install GO extension
  • beaker icon in left tool menu will list tests
  • run tests from list of tests

Test basics

Naming and Documentation

  • filename: same as source with _test.go extension (e.g. hello.go is tested in hello_test.go)
  • import: import testing package (i.e. import "testing")
  • testname:
    • start with Test
    • followed by method name GenerateID
    • followed by brief expectation CalledTwice
    • for full name: TestGenerateIDCalledTwice
    • comment above func declaration should set the expectation of what the test does and expected results (e.g.
      // TestGenerateIDCalledTwice calls album.generateID twice and expects each id to be different
  • single parameter t of type T (i.e. func TestGenerateIDCalledTwice(t *testing.T))

Test Code

  • start with setup
    • set an expected value (if applies)
  • call method
    • set actual value (if applies)
  • check condition
    • if actual != expected // handle failure
    • if some_other_condition // handle failure
  • do nothing if passing
  • call one of the failure methods (see testing#T)
    • common to use Fatalf with a format string + args - logs the failure and stops execution immediately

Example testing actual return value against expected return value

// TestValidTitleWhenExists
func TestValidTitleWhenExists(t *testing.T) {
	expected = true
	actual = album.validTitle("GoodTitle")
	if actual != expected {
		t.Fatalf(`validTitle`)
    }
}

Example testing complex situation

Example run tests in parallel when --parallel flag + number of tests that can run at the same time (e.g. --parallel 4) AND tests are marked for parallel

t.Parallel()

Example skipping longer tests when --short flag is set during test command

if testing.Short() {
  t.Skip("Message describing why skipped.")
}

Example extra logging when -v flag set is set during test command

if testing.Verbose() {
  log.Println("Extra information to print for this test when -v flag is set on test command.")
}

Tests that create examples in go docs

These are external tests in order to require package name to use public methods.

  • package name is package where code is defined + _test (e.g. spdxexp_test)
  • begin test name with Example + method name (e.g. ExampleSatisfies)
  • print output to console (e.g. fmt.Println(spdxexp.Satisfies("MIT", []string{"MIT"}))
  • comment after print shows expected result (e.g. // Output: true)
package spdxexp_test

func ExampleSatisfies() {
  res := spdxexp.Satisfies("MIT", []string{"MIT"})
  fmt.Println(res)
  // Output: true
}

NOTE: Not sure the println is required.

In docs, this will look like...

Code:

res := spdxexp.Satisfies("MIT", []string{"MIT"})
fmt.Println(res)

Output:

true

Run Tests

Not sure of all options, but here are a few. Not completely sure this all works.

Run all

go test

Run one

go test -run TestName

Run tests matching runs all tests that have Name as part of their name.

go test -run=Name

Verbose to see which tests run and their results.

go test -v
go test -v -run TestName

Run all in package

go test -v ./path/to/package

Run one in package

go test -v ./path/to/package -run TestName

Questions:

  • Will go test run all tests in current directory and all subdirectories?
  • If there are multiple test files in the same directory, will go test -run TestName run all tests of that name in all test files in that directory?

Patterns

Generating Code

References:

Interfaces

Reference:

Context

Video: Advanced Golang: Channels, Context and Interfaces Explained

Channels

Video: Advanced Golang: Channels, Context and Interfaces Explained

Type Reflection

References:

Common ways to get type info using reflection:

var myvar interface{}
myvar = "foo"
myvarValue := reflect.ValueOf(myvar)    // returns reflect.Value
myvarType := reflectedValue.Type()      // returns reflect.Type
myvarType = reflect.TypeOf(myvar)       // returns reflect.Type
myvarKind := reflectedValue.Kind()      // returns reflect.Kind
strKind := reflect.String               // returns reflect.Kind

Using reflection to get field info for a struct

type WordCount struct {
  Words string
  Count int
}
wordCount := WordCount{"hello world", 2}

reflectedValue := reflect.ValueOf(wordCount).Elem()

for i := 0; i < reflectedValue.NumField(); i++ {
  field := reflectedValue.Field(i)
  fieldName := reflectedValue.Type().Field(i).Name
  fieldType := field.Type()
  fieldValue := field.String() // returns value as string no matter field type
                               // may want to use something like `strconv.FormatInt(field.Int(), 10)`

  fmt.Println("fieldName: ", fieldName, "   fieldType: ", fieldType, "   fieldValue: ", fieldValue)
}

Output:

fieldName: Words   fieldType: String   fieldValue: hello world
fieldName: Count   fieldType: int64   fieldValue: 2

Doing different things based on type:

Example switching based on kind

Good for base types

kind := reflectedValue.Kind()        // returns reflect.Kind
switch kind {
case reflect.Array, reflect.Slice:
  fmt.Println("do something when it is an array")
case reflect.String:
  fmt.Println("do something when it is a string")
case reflect.Int64:
  fmt.Println("do something when it is an integer")
case reflect.Bool:
  fmt.Println("do something when it is a boolean")
case reflect.TypeOf(MyStruct{}).Kind():
  fmt.Println("do something for a specific struct type")
case reflect.Struct:
  fmt.Println("do something for all structs")
default:
  fmt.Println("do something when something else")
}

Example switching based on type

Good for specific struct types

type := reflect.TypeOf(myvar)
switch type {
case reflect.TypeOf(StructType1{}):
  fmt.Println("do something when it is StructType1")
case reflect.TypeOf(StructType2{}):
  fmt.Println("do something when it is StructType2")
case reflect.TypeOf(String):
  fmt.Println("do something when it is a String")
default:
  fmt.Println("do something when something else")
}

Converting to string -- useful for debugging

Figuring out values and types of a struct variable and its parts:

type WordCount struct {
  Words string
  Count int
}
wordCount := WordCount{"hello world", 2}

// get values
reflect.ValueOf(wordCount).String()               // <pkgname.WordCount Value> OR result of String() method
reflect.ValueOf(wordCount.Words).String()         // hello world
reflect.ValueOf(wordCount.Count).String()         // <int Value>
reflect.ValueOf(wordCount.Count).Int()            // 2
fmt.Println(reflect.ValueOf(wordCount.Count).String()) // <int Value>
fmt.Println(reflect.ValueOf(wordCount.Count))     // 2

// get types
reflect.ValueOf(wordCount).Type().String()        // pkgname.WordCount
reflect.ValueOf(wordCount.Words).Type().String()  // String
reflect.ValueOf(wordCount.Count).Type().String()  // int
myvar_type_str := myvar_type.String()

fmt.Println("myvar type: ", myvar_type)           // Println automatically calls String()
fmt.Println("myvar type: ", myvar_type.String())  // string
Clone this wiki locally