The Go Language
Getting Started
First, download go for your machine, or using a package manager. There's installation instructions if you want to do any additional configuration.
on macOS
# macOS brew install goon Debian
snap install go
If you already have Go installed on your computer, you can use it to install other versions of Go.
go install golang.org/dl/go1.17@latest && go1.17 download
Be sure to remember to prepend ~/sdk/go1.17/bin to your shell's command search path, as this is where the binary executable will end up being installed in.
For the latest version of Go, on the development branch, you can follow a similar process using gotip.
go install golang.org/dl/gotip@latest && gotip download
You can actually check out the official tour of Go, as well, which you can run locally.
Install the Go tour:
go install golang.org/x/website/tour@latest
This will install the tour in $GOPATH/bin.
Running the tour:
tour
Your First Program
Writing your first program
/* ~/go/src/hello.go */ package main import "fmt" func main() { fmt.Printf("Hello world\n") }Running your first program
go run hello.goHello world
If you'd like to separate the compilation and execution steps, use the
buildsubcommandgo build hello.go ./hello.goHello world
A heads up, oddly enough, the type comes after the variable name, something I learned from reading an article on Go's declaration syntax
Strings
Creating a string representation of an interface:
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("The person named %v is %v years old", p.Name, p.Age)
}
Packages
Programs in go are made up of multiple packages, and will run starting from the code defined in the main package.
Program with multiple imported packages
/* ~/go/src/problems.go */ package main import ( "fmt" "math" ) func main() { problems = 99 fmt.Printf("I got %d problems but a glitch aint %d.", problems, (100-problems)) }
Slices
Slices are a bit tricky, it is explained very well in Rob Pike's blog post on slices.
Also, felt this was worth writing down for my own safe-keeping:
It is idiomatic to use a pointer receiver for a method that modifies a slice.
--Rob Pike
len()
For locally defined slices the length is cached, so there is no runtime overhead for calls to len(a) provided that a is not defined beyond the local scope. For globally defined slices, the length is not cached, as its length can be modified elsewhere, which requires it to be re-evaluated each time a call to len(a) is made 1.
Arrays
Arrays are used less often than slices, but sometimes you find yourself in a situation where you'd like to create a fixed size array, using the values contained within a slice. Here is the idiomatic way for you to do precisely that:
var fixed [3]int
sliced := []int{1, 2, 3, 5, 8, 13}
copy(fixed[:], sliced)
This syntax takes advantage of the faxt that copy will only copy the minimum of len(src) and len(dst) bytes.
Scanners
If you need to read a file in line by line, the most idiomatic way to do so is by using bufio.Scanner
file := os.Open("file.txt")
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var txtlines []string
for scanner.Scan() {
txtlines = append(txtlines, scanner.Text())
}
file.Close()
for _, eachline := range txtlines {
fmt.Println(eachline)
}
JSON
Unmarshalling JSON formatted data
Make an HTTP request, receive JSON in the body of the response, and unmarshall that JSON into a Go struct named result:
response, err := http.Get("example.com/api/gimmejson")
if err != nil {
return
}
defer func(Body io.ReadCloser) {
err = Body.Close()
if err != nil {
return
}
}(response.Body)
data, err := io.ReadAll(response.Body)
if err != nil {
return
}
err = json.Unmarshal(data, result)
return
Input/Output
Open a file for reading:
file, err := os.Open("file.txt") if err != nil { return }
os.Stdin and os.Stdout are the standard input and output streams, which means they can be passed in to functions like os.Read and os.Write just as you would with any other parameter.
Declaring a file permission mode:
var mode os.FileMode = 0644Setting the permissions on a file:
// Note: This step is not necessary, you can supply the octal code itself // directly to os.OpenFile to set the permissions. This is just an example. mode := os.FileMode(0644) file, err := os.OpenFile("file.txt", os.O_RDWR, mode) if err != nil { return }Truncating a file:
message := []byte("hello, world") os.Write(file, message)
Installing Go Modules
You can install Go code using the install subcommand.
# Option 1
go install example.com/user/hello@latest
# Option 2
go install .
# Option 3
go install
For convenience, go commands accept paths relative to the working directory, and default to the package in the current working directory if no other path is given. So in our working directory, the commands shown above are all equivalent:
Defer, Panic, and Recover
There's an interesting article about callback functions using defer, but I'll leave that for another time.
Vim Plugin
If you want to edit your Go projects in Vim, there's a very healthy ecosystem to support you.
" Install the libraries needed for `vim-go`
:GoInstallBinaries
" Getting help
:help vim-go
I've included some useful commands below:
" Run the code in the current buffer
:GoRun
" Compile the code
:GoBuild
" Install the coe
:GoInstall
" Test the code
:GoTest
" Test a single function
:GoTestFunc
" See dependencies of the current package
:GoDeps
" See all source files in cwd
:GoFiles
" Rename an identifier
:GoRename
" Format the document according to the go style guide
:GoFmt
" Resolve all needed package imports & remove all unused packages
:GoImports
" Import the package `math` #study #rise&grind
:GoImport math
" Drop the package `math` #jk2cool4school
:GoDrop math
" Pull up documentation for the function `Printf` package `fmt`
:GoDoc fmt Printf
Style
Below are some notes I took while reading "The Go Programming Language"
The letters of acronyms and initialisms like ASCII and HTML are always rendered in the same case, so you might want to call a function htmlEscape, HTMLEscape, or escapeHTML, but should avoid calling it escapeHtml.
Syntax and Semantics
A declaration names a program entity and specifies some or all of its properties. In Go, the four main types of declarations are var, const, type, and func, but every .go file begins with a package declaration, followed by import declarations, and finally, zero-or-more package-level declarations.
In Go, there is no such thing as an unitialized variable. If a value is not provided for a variable at its declaration, the variable will have its value initialized to the zero-value corresponding to that variable's underlying type.
The := operator performs short variable declaration. Unlike the = operator, which performs assignment, the := operator performs declaration, which is distinct from assignment.
The expression
&xshould be read as "address ofx"The expression
*intshould be read as "pointer toint"
The zero value for a pointer of any type is nil. If there is a variable p, which is a pointer type variable, the test p != nil is true if p points to a variable. Two pointers are equal if and only if they point to the same variable, or are both equal to nil.
The expression new(T) creates an unnamed variable of type T, initializes it to the zero-value of type T, and returns its address, which is a value of type *T.
Functions
Reverse a slice
func reverse(s []int) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } }Usage:
a := [...]int{0, 1, 2, 3, 4, 5} reverse(a[:])Appending the contents of list
Bto listA:var A []int{1, 2, 3} var B []int{4, 5, 6} A = append(A, B...) // [1, 2, 3, 4, 5, 6]
Linked List
Print out a list of coordinates:
//Coord contains the X and Y coordinates of a position within a grid type Coord struct { X int Y int } func main() { l := list.New() c := []Coord{ Coord{X: 1, Y: 2}, Coord{X: 2, Y: 3}, } for _, v := range c { l.PushBack(v) } for e := l.Front(); e != nil; e = e.Next() { fmt.Printf("%#v\n", e.Value) } }Output:
main.Coord{X:1, Y:2} main.Coord{X:2, Y:3}
Formatting Numbers
Note: The example below was written mainly just for practice. github.com/dustin/go-humanize provides a far more robust implementation of this functionality.
Print bytes in human readable format:
// The HumanReadable function accepts a large integer as input and // and returns the most human-readable string representation of // that number in terms of a binary SI unit prefix such as // kibi (KiB), mebi (MiB), gibi (GiB), etc. func HumanReadable(b int64) string { const unit = 1024 // If the number is sufficiently small, // no need to add a unit prefix if b < unit { return fmt.Sprintf("%d B", b) } div, exp := int64(unit), 0 for n := b / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf( "%.1f %ciB", float64(b) / float64(div), "KMGTPE"[exp], ) }
File I/O
Creating an empty file:
f, err := os.Create("file.txt") if err != nil { log.Fatal(err) } log.Println(f) f.Close()Truncating an existing file:
err := os.Truncate("file.txt", 100) if err != nil { log.Fatal(err) }This will truncate
file.txtto its first 100 bytes, erasing all content beyond the 100th bytes stored infile.txt. Similarly, to truncate a file to be empty, you can useos.Truncate("file.txt", 0).If
file.txtis less than 100 bytes to begin with, the original contents will persist, and the remaining bytes are filled with the zero-value for abyte.Getting metadata about a file:
info, err := os.Stat("file.txt") if err != nil { log.Fatal(err) } // Examples of what you can do with `info` fmt.Println("File name:", info.Name()) fmt.Println("Size in bytes:", info.Size()) fmt.Println("Permissions:", info.Mode()) fmt.Println("Last modified:", info.ModTime()) fmt.Println("Is Directory: ", info.IsDir()) fmt.Printf("System interface type: %T\n", info.Sys()) fmt.Printf("System info: %+v\n\n", info.Sys())Moving a file:
old := "dir1/old.txt" new := "dir2/new.txt" err := os.Rename(old, new) if err != nil { log.Fatal(err) }Deleting a file:
err := os.Remove("file.txt") if err != nil { log.Fatal(err) }Opening and closing a file:
// Open 'read.txt' in read-only mode. f, err := os.Open("read.txt") if err != nil { log.Fatal(err) } defer f.Close() // Open 'write.txt' in write-only mode. f, err = os.OpenFile("write.txt", os.O_WRONLY|os.O_CREATE, 0666)Checking if a file exists:
info, err := os.Stat("file.txt") if err != nil { if os.IsNotExist(err) { log.Fatal("file does not exist") } else { log.Fatal(err) } } fmt.Println("file exists")Checking file permissions:
info, err := os.Stat("file.txt") if err != nil { log.Fatal(err) } fmt.Println("Permissions:", info.Mode()) fmt.Println( "User has read access:", info.Mode().Perm(os.FileMode(0400)), ) fmt.Println( "User has write access:", info.Mode().Perm(os.FileMode(0200)), ) fmt.Println( "User has execute access:", info.Mode().Perm(os.FileMode(0100)), )Changing the timestamps of a file:
// Change perrmissions using Linux style err := os.Chmod("file.txt", 0777) if err != nil { log.Println(err) } // Change ownership err = os.Chown("file.txt", os.Getuid(), os.Getgid()) if err != nil { log.Println(err) } // Change timestamps twoDaysFromNow := time.Now().Add(48 * time.Hour) lastAccessTime := twoDaysFromNow lastModifyTime := twoDaysFromNow err = os.Chtimes("file.txt", lastAccessTime, lastModifyTime) if err != nil { log.Println(err) }Creating a hard link:
err := os.Link("file.txt", "hardlink.txt") if err != nil { log.Fatal(err) }Creating a symbolic link:
// Create a symlink err = os.Symlink("file.txt", "symlink.txt") if err != nil { log.Fatal(err) }Changing the owner of a file:
err = os.Lchown("file.txt", os.Getuid(), os.Getgid()) if err != nil { log.Fatal(err) }Copy a file:
// Open new file 'original.txt' original, err := os.Open("original.txt") if err != nil { log.Fatal(err) } defer original.Close() // Create new file 'replica.txt' replica, err := os.Create("replica.txt") if err != nil { log.Fatal(err) } defer replica.Close() // Copy the bytes to destination from source _, err := io.Copy(replica, original) if err != nil { log.Fatal(err) } // Commit the file contents and flush memory to disk err = replica.Sync() if err != nil { log.Fatal(err) }Seek to a position in a file:
file, _ := os.Open("test.txt") defer file.Close() // Offset is how many bytes to move // Offset can be positive or negative var offset int64 = 5 // Whence is the point of reference for offset // 0 = Beginning of file // 1 = Current position // 2 = End of file var whence int = 0 newPosition, err := file.Seek(offset, whence) if err != nil { log.Fatal(err) } fmt.Println("Just moved to 5:", newPosition) // Go back 2 bytes from current position newPosition, err = file.Seek(-2, 1) if err != nil { log.Fatal(err) } fmt.Println("Just moved back two:", newPosition) // Find the current position by getting the // return value from Seek after moving 0 bytes currentPosition, err := file.Seek(0, 1) fmt.Println("Current position:", currentPosition) // Go to beginning of file newPosition, err = file.Seek(0, 0) if err != nil { log.Fatal(err) } fmt.Println("Position after seeking 0,0:", newPosition)Write to a file:
// Open a new file for writing only file, err := os.OpenFile( "test.txt", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666, ) if err != nil { log.Fatal(err) } defer file.Close() // Write bytes to file byteSlice := []byte("Bytes!\n") bytesWritten, err := file.Write(byteSlice) if err != nil { log.Fatal(err) } log.Printf("Wrote %d bytes.\n", bytesWritten)Dump a byte slice to a file:
err := io.WriteFile("test.txt", []byte("Hi\n"), 0666) if err != nil { log.Fatal(err) }
In the examples below, assume that file has already been opened and is ready to be used.
Read up to
nbytes from a file:// Read up to len(b) bytes from the File // Zero bytes written means end of file // End of file returns error type io.EOF byteSlice := make([]byte, 16) bytesRead, err := file.Read(byteSlice) if err != nil { log.Fatal(err) } log.Printf("Number of bytes read: %d\n", bytesRead) log.Printf("Data read: %s\n", byteSlice)Read exactly
nbytes from a file:// The file.Read() function will happily read a tiny file in to a large // byte slice, but io.ReadFull() will return an // error if the file is smaller than the byte slice. byteSlice := make([]byte, 2) numBytesRead, err := io.ReadFull(file, byteSlice) if err != nil { log.Fatal(err) } log.Printf("Number of bytes read: %d\n", numBytesRead) log.Printf("Data read: %s\n", byteSlice)Read at least
nbytes from a file:byteSlice := make([]byte, 512) minBytes := 8 // io.ReadAtLeast() will return an error if it cannot // find at least minBytes to read. It will read as // many bytes as byteSlice can hold. numBytesRead, err := io.ReadAtLeast(file, byteSlice, minBytes) if err != nil { log.Fatal(err) } log.Printf("Number of bytes read: %d\n", numBytesRead) log.Printf("Data read: %s\n", byteSlice)Read all bytes from a file:
data, err := io.ReadAll(file) if err != nil { log.Fatal(err) } fmt.Printf("Data as hex: %x\n", data) fmt.Printf("Data as string: %s\n", data) fmt.Println("Number of bytes read:", len(data))Note:
os.File.Read(),io.ReadFull(), andio.ReadAtLeast()all require a fixed byte slice.Read a file into memory:
// Read file to byte slice data, err := io.ReadFile("file.txt") if err != nil { log.Fatal(err) } log.Printf("Data read: %s\n", data)Use a buffered reader:
// Open 'file.txt' wrapped by a buffered reader. file, err := os.Open("file.txt") if err != nil { log.Fatal(err) } br := bufio.NewReader(file) // Get the next bytes without advancing the cursor. data := make([]byte, 5) data, err = br.Peek(5) if err != nil { log.Fatal(err) } fmt.Printf("Peeked at 5 bytes: %s\n", data) // Read in data and advance the cursor. n, err := br.Read(data) if err != nil { log.Fatal(err) } fmt.Printf("Bytes read: %d: %s\n", n, data) // Read one byte. b, err := br.ReadByte() if err != nil { log.Fatal(err) } fmt.Printf("Read one byte: %c\n", b) // Read up to and including delimiter bts, err := br.ReadBytes('\n') if err != nil { log.Fatal(err) } fmt.Printf("Bytes read: %s\n", bts) // Read text up to and including delimiter line, err := br.ReadString('\n') if err != nil { log.Fatal(err) } fmt.Println("Bytes read: %s\n", line)Use a buffered writer:
// Create a buffered writer from the file bw := bufio.NewWriter(file) // Write bytes to buffer. written, err := bw.Write( []byte{65, 66, 67}, ) if err != nil { log.Fatal(err) } log.Printf("Bytes written: %d\n", written) // Write string to buffer. // Also available are WriteRune() and WriteByte() written, err = bw.WriteString("Buffered string\n") if err != nil { log.Fatal(err) } log.Printf("Bytes written: %d\n", written) // Write the current contents of buffer from memory to disk. bw.Flush()
Imports
import (
"bufio"
"bytes"
"io"
"os"
)
Templates
To do
Consider the following template file file.html, located within the directory tmpl:
<p>Hello, {{.Name}}! Welcome to {{.Location}}.</p>
The .Name and .Location variables can be replaced in a Go file using the template package:
type Doc struct {
Name string
Location string
}
func main() {
t, _ := template.ParseFiles("tmpl/file.html")
fmt.Println(dr.Name)
t.Execute(os.Stdout, dr)
}
The output should be:
<p>Hello, Doctor! Welcome to GitHub Hospital.</p>
Interfaces
To do
An interface defines a set of methods that, if implemented by a type, cause that type to be a valid instance of that interface.
Concurrency
channels, go routines,
Networking
Todo.
Reading/Writing
Todo.
Printing
Discovered in the article Effective Slices:
For maps, Printf and friends sort the output lexicographically by key.
When printing a struct, the modified format %+v annotates the fields of the structure with their names, and for any value the alternate format %#v prints the value in full Go syntax.
type T struct {
a int
b float64
c string
}
t := &T{ 7, -2.35, "abc\tdef" }
fmt.Printf("%v\n", t)
fmt.Printf("%+v\n", t)
fmt.Printf("%#v\n", t)
fmt.Printf("%#v\n", timeZone)
Produces the following output:
&{7 -2.35 abc def}
&{a:7 b:-2.35 c:abc def}
&main.T{a:7, b:-2.35, c:"abc\tdef"}
map[string]int{"CST":-21600, "EST":-18000, "MST":-25200, "PST":-28800, "UTC":0}
"Multiple len calls vs performance" StackOverflow. https://stackoverflow.com/questions/26634554/go-multiple-len-calls-vs-performance ↩︎