-
Notifications
You must be signed in to change notification settings - Fork 0
Golang
Reference
-
golang.org (official website)
- Docs (language spec) (tutorials)
- Packages (search) (standard library)
- Sandbox
- A Tour of Go
- github/golang (go language)
- Project Layout
- Go Frameworks
- Understanding Modules and Packages
- 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
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 the app from the starting point (i.e. main.go
in package main
):
go run main.go
OR
go run .
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
./my_app
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
)
Prefix constant declarations with the keyword const
.
const s string = "Always Hello"
s = "Nice Try" // results in compile-time error `cannot assign to x`
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 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
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.)
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
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 }
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 snum {
case "one": fmt.Println(1)
case "two": fmt.Println(2)
case "three": fmt.Println(3)
default: fmt.Println("GO")
}
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)
}
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
)
- receive the struct's type as receiver (e.g.
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 ...
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 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.
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.
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).
Similar to class constants.
Referenced:
- Go Modules Reference (official go docs)
- How to Use Go 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
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
Reference:
"A package in Go is simply a directory/folder with one or more .go files inside of it."
- 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)
- Don't expose internal directory organization in the import path (e.g. src, pkg). TODO: Not completely sure how to avoid this yet.
- 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
Reference:
- Godoc: documenting Go code (go blogs)
- example doc.go
Run local doc server: (port # is an example)
godoc -http=6060
- everything in $GOROOT/src/pkg
- everything in $GOPATH work spaces
- -path flag
- running:
godoc .
in a source directory
type, variable, constant, function, and package
- 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?
- 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
If documentation is long, include a file in the package's directory called doc.go
.
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 {
Reference: Golang Debugging with Delve
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?
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 callc.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
Reference:
- Example of Hello World simple test (official go tutorial)
- testing pkg (standard library)
- Testing in Go: Test Doubles by Example
- install GO extension
- beaker icon in left tool menu will list tests
- run tests from list of tests
Naming and Documentation
- filename: same as source with
_test.go
extension (e.g.hello.go
is tested inhello_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.
- start with
// TestGenerateIDCalledTwice calls album.generateID twice and expects each id to be different
- single parameter
t
of typeT
(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
- common to use
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.")
}
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
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?
References:
Reference:
Video: Advanced Golang: Channels, Context and Interfaces Explained
Video: Advanced Golang: Channels, Context and Interfaces Explained
References:
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
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
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")
}
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