Skip to content
/ goref Public

🐹 A Golang concentrate for refreshing your memory on the go programming language

License

Notifications You must be signed in to change notification settings

bcomnes/goref

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

goref 🐹

A quick reference for the go language.

Other than specific citations, most of the following is simmered down from the following sources:

Install

Install using a rolling package manager:

$ brew install go #OSX
C:> choco install golang #Windows
$ sudo pacman -S go #Arch Linux

The prebuilt binaries are also a good option for linux. Don't get stuck using an old version.

golang.org/dl

You also need to install git and hg. Quite a few official packages are versioned with hg.

Beta versions

Beta versions of go are installed with go:

$ go get golang.org/dl/go1.11beta2
$ go1.11beta2 download

(ref)

Configure

Go expects two primary ENV vars to be set:

$GOROOT

GOROOT needs to be set to where you installed go. In most cases, this will automatically be set by the package manager, or if you installed the prebuilt bins to the default location. Otherwise you need to set this in your dotfiles. See golang.org/doc/install#tarball_non_standard

$GOPATH

The GOPATH path used to be required to be set. Now it defaults to ~/go. You are still typically expected to add ~/go/bin to your $PATH however. This seems to be the convention:

$ mkdir -p ~/go/{bin,src} ; echo "export GOPATH=\$HOME/go" >> ~/.bashrc ; echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.bashrc

This should add the following lines to your .bashrc

# add to ~/.bashrc to make the above action stik
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

The GOPATH enables the go get command which downloads and build packages from git repositories. They get built and installed to your GOPATH. You pretty much always want to run the bins they come along with so adding $GOPATH/bin to the PATH is critical.

For this document assume:

$GOPATH = ~/go

Go Packages and Modules

Go has two concepts. Go packages are folders that contain one or more go files. A module (starting with vgo and go 1.11) is a versioned collection of packages.

Basic Go Packaging

Read the entire "How to Write Go" document, but here are the basics:

Go with modules

Develop your code any where you want. Dependencies are cached in $GOPATH/src/mod.

Go without modules

(This advise is no longer required or recommended)

Develop your code in the src folder corresponding to where you host your code:

~/go/src/github.com/bcomnes/project-name

You can quickly create new projects by creating the repo on github, then go get [repo-qualified-name] the newly created repo:

go get github.com/bcomnes/project-name
  • Go programmers typically keep all their Go code in a single workspace.
  • A workspace contains many version control repositories (managed by Git, for example).
  • Each repository contains one or more packages.
  • Each package consists of one or more Go source files in a single directory.
  • The path to a package's directory determines its import path. ---How to Write Go Code

Packages

A package is a folder with a collection of go files. Each file in the package folder is required to define the same package name header:

package foo

Having a different package declaration name than siblings results in an error. Every package file must have the same package name as their siblings in the folder that they live in.

Folders inside of a package can house a subpackage.

/package/
  foo.go
  bar.go
  /subpackage/
    beep.go
    boop.go

Package import paths

Package file names are mostly irrelevant. Packages are imported by their import path, which is the URL where the code is hosted:

import (
  "fmt"
  "github.com/bcomnes/package-folder"
)

where github.com/bcomnes/package-folder has the following files:

/package-folder/
  package-file1.go
  package-file2.go

When you import a package by its import path, the package name from the package declaration at the top of the package files becomes the prefix at which you can access everything that is exported from that package. e.g.

If package-file1.go from github.com/bcomnes/package-folder had the following contents:

//package-file1.go
package foo

import "fmt"

func Say( ){
  fmt.Println("Hi from foo")
}

then another go package importing this would import it like this:

package bar

import "github.com/bcomnes/package-folder"

foo.Say() //Hi from foo

The package name foo is silently and invisibly dropped into the scope of the importing package as a package prefix. You can name your imports by giving them a name upon importing

package bar

import pf "github.com/bcomnes/package-folder"

pf.Say() //Hi from foo

Its generally a good idea to keep your package folder name the same as the package name, but know that you cannot depend on this to be true necessarily.

Go's convention is that the package name is the last element of the import path: the package imported as "crypto/rot13" should be named rot13. --How to Write Go Code#PackageNames

Package names

Package names should be sort, contain no spaces, underscores, capitals. Basically take every word of the name, lowercase it, and make it into one long single word. Its the go way!

Package exports

Packages export things to importers by capitalizing the first letter of the variable or function they are exporting. Package files have intrinsic access to the variables and types declared anywhere else in the package siblings. This is unfortunate, so please take this in mind and make declarations obvious, and be judicious when creating many files in a single package.

//package-file1.go
package foo

import "fmt"

func Exported( ){
  fmt.Println("Hi from foo")
}

func notexported( ){
  fmt.Println("No hi from foo")
}

There are some benefits tho this, however, when writing tests, package level tests have privileged access to unexported values and functions.

Executable (main) packages

Packages that generate executable commands must always have the package main header.

package main

package main must contain a main() function in at least one package file, which is called when you execute the program.

package main

import "fmt"

func notmain() {
  fmt.Println("I don't run unless called!")
}

func main() {
  // Hi I'm the entry point
  fmt.Println("Hello world")
}

Many packages will leave their main package in the root of their project repo, because their primary product is a binary program installed with the go get command.

The name of the binary will be the the last element of the import path. For example, if the import path is foo/bar, then the binary retrieved with go get will be placed in $GOPATH/bin/bar.

Package Testing

Go includes first class testing tools. To write a test for a package, create a file with the same name but suffixed with [file-name-to-test]_test.go.

/package
  foo.go
  foo_test.go

A test is a function that takes a test assertion pointer *testing.T:

// math_test.go
package math

import "testing"

func TestAverage(t *testing.T) {
  var v float64
  v = Average([]float64{1,2})
  if v != 1.5 {
    t.Error("expected 1.5, got ", v)
  }
}

Notice that Average is not imported. Since tests are apart of the package collection itself, all top level functions and variables available to tests as well as every other file in the package. On some level, this is nice since we can test un-exported (uncapitalized) functions without having to expose them publicly. Its unfortunate because we have to be aware of invisible namespace overlap between files.

To run tests, type:

$ go test

All tests found in the current package folder will be run. Tests are identified by the public Test prefix on functions. Typically you put the name of the function you are testing after Test like TestAverage for testing the Average function.

External test packages

External test packages also are named foo_test.go, but also include the _test suffix in the package name:

package math_test

These can only access exported values from the package, and are also useful for resolving import cycles.

If you want to declare that a file only tests the exported API of a package, or want to restrict yourself to that, that's the job of foo_test. -- Patrick Stephen (#[email protected])

Some people recommend that you name internal tests foo_internal_test.go (package foo), and external test packages foo_test.go(package foo_test).

Test files that declare a package with the suffix "_test" will be compiled as a separate package, and then linked and run with the main test binary. Test packages

Table driven tests

A common testing pattern is to write a test that takes a struct of inputs and outputs, and loop through an array of these.

package math_test // external package

import "testing"

type testpair struct {
  values []float64
  average float64
}

var tests = []testpair{
  { []float64{1,2}, 1.5 },
  { []float64{1,1,1,1,1,1}, 1 },
  { []float64{-1,1}, 0 },
}

func TestAverage(t *testing.T) {
  for _, pair := range tests {
    v := Average(pair.values)
    if v != pair.average {
      t.Error(
        "For", pair.values, "expected", pair.average, "got", v
      )
    }
  }
}

Testable examples

Examples are also tests in go.

They reside in external test packages (package foo_test), and follow a similar name convention as a regular test. The Output comment determines if the test passes or fails.

package stringutil_test

import (
    "fmt"

    "github.com/golang/example/stringutil"
)

func ExampleReverse() {
    fmt.Println(stringutil.Reverse("hello"))
    // Output: olleh
}

Package level examples can be achieved by leaving off the suffix of the Example function:

// example_test.go
package flattree_test

import (
  "fmt"

  "github.com/bcomnes/flattree"
)

func Example() {
  var list = make([]uint64, 16, 50)

  i := flattree.Index(1, 0) // get array index for depth: 0, offset: 0
  j := flattree.Index(3, 0) // get array index for depth: 1, offset: 0

  // use these indexes to store some data

  list[i] = i
  list[j] = j
  parent := flattree.Parent(j, 0)
  list[parent] = parent

  fmt.Println(i)
  fmt.Println(j)
  fmt.Println(parent)
  fmt.Println(list)
  // Output:
  // 1
  // 7
  // 15
  // [0 1 0 0 0 0 0 7 0 0 0 0 0 0 0 15]
}

Godoc Documentation

It is conventional to include a package level doc.go file which includes README like comments on the package:

// Copyright 2018 Bret Comnes. All rights reserved.
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file.

/*
Package flattree implements a series of functions to map a binary tree to a list.
You can represent a binary tree in a simple flat list using the following structure
     3
   1   5
  0 2 4 6 ...
This module exposes a series of functions to help you build and maintain this data structure.
See also
flat-tree for node: https://github.com/mafintosh/flat-tree.
print-flat-tree: https://github.com/mafintosh/print-flat-tree.
flat-tree for rust: https://github.com/mafintosh/flat-tree-rs.
*/
package flattree

vgo: Go Modules and Dependencies

This section is a WIP

vgo and Go 1.11 introduce modules. Modules are a way to version a collection of packages.

Get the beta

$ go get -u golang.org/dl/go1.11beta2
$ go1.11beta2 download

Get vgo:

$ go get -u golang.org/x/vgo

Usage

$ go help mod # print help docs
$ go mod -pacakges
$ go get -u path # update dep to the latest
$ go mod -fix # fix up go.mod file. runs automatically
$ go mod -sync # sync go.mod with code
$ go mod -vendor # clears then moves all deps into the vendor folder
$ go mod -verify # verify all deps have not been modified since downloading
Low level
# low level commands
# use -v for more info
$ go mod -init # initialize a new module creating a go.mod file
$ go mod -init -module set/module/path # set module path in go.mod
$ go mod -module change/module/path # change module path in go.mod
$ go mod -require [email protected] # add a requirement to go.mod
$ go get path@version # use this instead of mod -require
$ go mod -droprequire path # drop a requirement from go.mod
$ go get path@none # use this instead of mod -droprequire
$ go mod -exclude [email protected] # add a exclude to go.mod
$ go mod -dropexclude [email protected] # remove a exclude to go.mod
$ go mod -replace=old@v=new@w # temporary overrides. can use ../relative/paths if @v is omitted
$ go mod -dropreplace=old@v
$ go mod -fmt # reformat old go.mod file
$ go mod -graph # prints the module requirement graph
$ go mod -json # prints the module requirement graph
$ go list -m -json all # full set of modules available to a build

Conventions

Create a normal set of packages in a repo. Run go mod -init to create the go.mod file as well as the go.sum file.

The go.sum file is not a lock file:

The go command maintains, in the main module's root directory alongside go.mod, a file named go.sum containing the expected cryptographic checksums of the content of specific module versions. Each time a dependency is used, its checksum is added to go.sum if missing or else required to match the existing entry in go.sum. -- Paul Jolly (#[email protected])

As you edit your module, you create releases by tagging your code with semver tags (e.g. v1.0.5). Version v0.*.* and v1.*.* are both considered v1 modules and don't have semantic version import path prefixes.

You may need to update your ~/.netrc with a github personal access token. The CLI will provide instructions on how to do this if needed.

Breaking changes

When you need to make a breaking change, first update the go.mod file to reflect the new semantic version import path:

module github.com/user/package
// becomes
module github.com/user/package/v2

Make your changes, sync the go.mod file, test. When you are ready, tag your release v2.0.0 and push your changes and tags.

Branches and sub-folders are NOT required, however you can use them.

go release

TODO: Document go release when it is available.

Consuming breaking changes

Go with modules supports partial module replacement. Modules can consume multiple versions of the same dependency using semantic version imports. Simply insert the major version number prefixed with a v before any submodule import path.

package main

import (
  "fmt"
  "github.com/example/somemodule/somepackage"
)

becomes

package main

import (
  "fmt"
  "github.com/example/somemodule/v2/somepackage"
)

Thats it! Building will download and update your go.mod file. You have to update the import path everywhere its imported throughout your modules and packages.

Dependencies can have different major versions of modules than your package and it all works.

Semantic import versioning uses minimal version selection.

Dep: the go package manager (Depreciated)

Use dep to manage dependencies in a go project. See docs for more info.

Built by a community member Sam Boyer as the proto-vgo dependency manager for go. Many projects were using glide, a project that preceded and worked very similarly to dep, and also took some wind out of deps uptake.

A presentation about this was given at GoSF but was lost and may be re-recorded. (source)

New project

$ dep init

Newly cloned project

$ git clone
$ cd repo
$ dep ensure

Add dependency

$ dep ensure -add github.com/pkg/errors

Vendoring (Historical)

Vendoring is up in the air still but generally here are the best leads:

More info

Basic Go CLI

Run go files with go run:

$ go run /path/to/foo.go

Get docs using godoc:

$ godoc fmt Println
func Println(a ...interface{}) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output. Spaces are always added between operands and a newline
    is appended. It returns the number of bytes written and any write error
    encountered.

Download and build dependencies with go get:

$ go get github.com/ipfs/go-ipfs/cmd/ipfs

go get has a few flags:

  • -h: display extended help info
  • -d: download package only (no install)
  • -t: download package with tools necessary for testing
  • -u: update package and dependencies

Other doc tools

Common Types

Go is a typed language. You can specify to the compiler the datatype of the variable. If you try to assign type a to a variable of type b, the compiler throws an error.

Numbers

  • uint8 (u means unsigned e.g. no +/-)
  • uint16
  • uint32
  • uint64
  • int8
  • int16
  • int32
  • int64
  • float32
  • float64
  • complex64
  • complex128

Strings

Double quotes ("string") requires escaped whitespace:

x := "String \n with \n newlines and \t tabs"

Backticks (string) can contain whitespace

x := `String
with
whitespaces`

Simple concatination of strings can be done with the + operator:

x := "string1 "
y := "string2"
z := x + y // "string1 string2"

Booleans

Same as JS:

  • && and
  • || or
  • ! not

Type Conversions

Types can be converted different types by running the variable through the desired type as a function.

For example, turning an int into a float64

var x int = 32
var xFload64 = float64(x)

Variables

Generally try to create variables by inferring their type using the := operator:

x := "This results in x being a string"

You can think of this as stating:

Create a variable who's type and value is the following -- Rob Pike: Advanced Topics in Programming Languages: Concurrency/message passing Newsqueak

Here is an example go program:

package main

import "fmt"

func main() {
  var x string
  x = "Hi I'm a string"
  var y = "I'm an inferred string"
  z := "Beep another inferred string"
  i := "var not required!"
  fmt.Println(x) // Hi I'm a string
  fmt.Println(y) // I'm another string
  fmt.Println(z) // Beep another string
  fmt.Println(i) // var not required!
}

Variables are created using the var keyword, followed by the variable name (x) followed by the variable type.

var and the := assignment operator can infer the correct type in most cases when a type is omitted.

// Simple typed variables
var x string = "hello i'm variable of type string"
var y int16 = 32
var z = "var can infer type"
i := 32

Naming variables

Variable names must start with a letter and can contain letters, numbers and the underscore symbol (_).

Unused variables throw errors and warnings. If a variable is named (_), the go compiler will not complain if it is unused. There should never be unused named variables.

Simple operators

Simple operators work on most variables of the same type.

  • +: addition
  • -: subtraction
  • *: multiplication
  • /: division
  • %: modulo (remainder)
  • x += x = x + x increment
  • x -= x = x - x decrement
  • == equal
  • != not equal

Scope

Go is "lexically scoped using blocks".

Variables exist inside the braces({}) where they are defined, as well as in any child braces({}).

A block is the code inside a pair of braces({}). Package level variables exist for all files in a package. Exported package variables can be imported by external package consumers.

Constants

Declare constants using the const keyword in the same manner as the var keyword.

const x string = "go ahead, just try to reassign me"

Try to avoid using constants where configuration and configurable defaults would also work. E.G. you never need to configure something like Pi(π) (defined as const in the math package).

var definition shorthand

The following is a common code pattern when defining variables.

var (
  a = 5
  b int64 = 6
  c = "a string"
)

This pattern works with other keywords that are used similarly to the var keyword.

Basic fmt usage

Basic line printing

  • fmt.Println('print something to stdout'): log to stdout with a newline at the end.
  • fmt.Println('hi my name is', name): appending string together with a space.
  • fmt.Print('no newline'): print without a trailing newline.

Printf has many "verbs" that can be used to display different data types in strings:

  • %d - decimal integer
  • %x, %o, %b - integer in hex, octal, binary
  • %f, %g, %e - floating-point number: 3.141593 3.141592563589793 3.141593e+00
  • %t - boolean: true false
  • %c - rune (Unicode code point)
  • %s - string
  • %q - quoted string "abc" or run 'c'
  • %v - any value in natural format
  • %T - type of any value
  • %% - literal percentage sign (no operand)

From GOPL book 1st ed. Ch1 p.10

See golang.org/pkg/fmt/ for more.

Process input

  • fmt.Scanf("%f", &var_name): Wait for stdin and write to &var_name.
    • See pointers for info on the & prefix.
package main

func main() {
  fmt.Print("Enter a number: ")
  var input float64
  fmt.Scanf("%f", &input)

  output := input * 2

  fmt.Println(output)
}

Control Structures

Go has 3 simple control structures that can be used in many ways.

for loops

Go only has 1 type of loop: the for loop. As with most languages, for loops repeat a block of code multiple times.

package main

import "fmt"

func main() {
  first()
  second()
  three()
}

func first() {
  // minimal for loop
  i := 1
  for i <= 10 {
    fmt.Println(i)
    i += 1
  }
}

func second()  {
  // A more common an concise declaration
  for i := 11; i <= 20; i++ {
    fmt.Println(i)
  }
}

func three()  {
  for i := 19; i >= 0; i-- {
    fmt.Println(i)
  }
}

If we have an iterable variable like an array or slice we can use the range keyword as a shorthand in our for loop.

package main

import "fmt"

func main() {
  x := [5]float64{1,2,3,4,5}
  var total float64 = 0

  for _, value := range x {
    total += value
  }

  fmt.Println(total / float64(len(x))) // 3
}

if statements

If blocks work similarly to js, except there are some minor syntax differences:

package main

import "fmt"

func main() {
  for i := 0; i <= 10; i++ {
    if i % 2 == 0 {
      fmt.Println(i, "divisible by 2")
    } else if i % 3 {
      fmt.Println(i, "divisible by 3")
    } else {
      fmt.Println(i, "beepin beep")
    }
  }
}

switch blocks

Switches look for a matching case top down and break once a match is found. There is no fallthrough [Citation Needed].

Switches also support a default case if no match is found.

Generally avoid switch statements.

package main

import "fmt"

func main() {
  mySwitch(0)
  mySwitch(1)
  mySwitch(2)
  strSwitch("boop")
  strSwitch("foo")
}

func mySwitch(i int) {
  switch i {
  case 0: fmt.Println("0")
  case 1: fmt.Println("2")
  case 2: fmt.Println("2")
  }
}

func strSwitch(i string) {
  switch i {
  case "boop": fmt.Println("beep")
  case "foo": fmt.Println("bar")
  }
}

Arrays

Arrays are an ordered (numbered) sequence of elements of a single type with a fixed length. Once created, they cannot be resized.

package main

import "fmt"

func main() {
  var x [5]int
  x[4] = 100 // set 5th element (i = 4) to 100
  fmt.Println(x) // [0 0 0 0 100]
  var length = len(x)
  fmt.Println(length)
}

The length of an array is accessed using the built in len(array) function returning the length as an int.

Arrays can also be created with the following shorthand:

x := [5]int{ 1, 2, 3, 4, 5 }
y := [5]float32 {
  98,
  93,
  80,
  52,
  56, // Trailing comma REQUIRED
  //45, // to allow commenting out elements
}

Use range in a for loop to iterate over an array:

x := float64[5]
// ... assign some stuff
for i, el := range x {
  // i is index
  // el is i'th element
}

Slices

Similar to type inference, you can create an array and infer its length upon creation. This is generally preferred over specifying length manually the same way := is preferred when possible.

arr := []float64{1,2,3,4}
// creates a slice with an underlying array of length 4

When you create an array this way, you are actually creating a slice.

Slices are 'slices' of arrays. Slices can be shorter than their underlying array and change in length (increase and decrease) but cannot exceed the length of the underlying array.

var x []float64
// Creates a slice x of length 0
fmt.Println(len(x)) // 0

You can also make slices using the make keyword:

x := make([]float64, 5)
// creates a slice with underlying array length of 5
y := make([]float64, 5, 10)
// A slice of length 5 with an underlying array of length 10

slice

(Image taken from Go In Action book.)

Slices are pretty complicated, but easy to use. The Go Programming Language and Go In Action books both have excellent in depth explanations of how they work internally, and some underlying subtleties in how they behave.

Slicing

Using the slice syntax, you can slice up existing arrays and create a slice of a range.

// array[low:high]
arr := []float64{1,2,3,4,5,6,7,8,9}
s := arr[0:5] // [1 2 3 4 5]
t := arr[1:6] // [2 3 4 5 6]

The low index is where the slice starts and the high index is where the slice stops. The high index is not included.

Appending

The append function accepts a slice and additional values to append. It returns a new slice with with those values appended.

slice := []float64{1,2,3,4,5} // [1 2 3 4 5]
biggerSlice := append(slice, 6,7,8,9) // [1 2 3 4 5 6 7 8 9]

Copying

The copy function copies as much as one slice into another slice:

func copyTest() {
	slice1 := []int{1, 2, 3, 4}
	slice2 := make([]int, 2)
  var slice3 []int
	copy(slice2, slice1)
  copy(slice3, slice1)
  slice2[0] = 2
	fmt.Println(slice3, slice2, slice1) // [] [2 2] [1 2 3 4]

	slice4 := slice1 // Assignments are copy by reference
	slice4[0] = 10
	fmt.Println(slice4, slice1) // [10 2 3 4] [10 2 3 4]
}

Fun Fact!

Go slices come from an idea in B, the precursor to C.

If you want to understand how slices work in #golang, look to B. -- @davecheney

Maps

AKA: "Hash Table", "Dictionary" or "Associative Array". An unordered collection of keys-value pairs.

x := make(map[string]int)
/*    ^    ^     ^    ^
      |    |     |    |
    init   |     |    |
         keyword |    |
                 |    |
                 |    |
             key type |
                      |
                 value type
*/

Maps need to be initialized before they can be used using the make keyword. The shorthand method is preferred in general.

Functions like len work on maps.

Keys can be deleted using the delete(map, keyName) function.

Values are accessed similar to how we access arrays using the map["key"] syntax.

package main

import "fmt"

func main() {
	x := make(map[string]string)
	x["key"] = "10"
	fmt.Println(x["key"]) // "10"
	fmt.Println(len(x)) // 1
	x["foo"] = "bar"
}

Accessing keys that don't exist returns the zero value of the value type:

y := make(map[string]string)
z := make(map[int]int)
fmt.Println(y["foo"] == "") // true
fmt.Println(z[10] == 0) // true

Testing for key existence is easy due to a secondary Boolean existence return value:

h := make(map[string]string)
h["beep"] = "boop"

noise, ok := h["beep"]
fmt.Println(noise, ok) // boop true
noNoise, ok  := h["foo"]
fmt.Println(noNoise, ok) // '' false

When used in an if condition:

h := make(map[string]string)
h["beep"] = "boop"

if noise, ok := h["beep"]; ok {
  fmt.Println(noise) // boop
}

if noise, ok := h["bar"]; ok {
  // Doesn't run
  fmt.Println(noise)
}

Map shorthand

The preferred way of making maps is with the shorthand syntax:

myMap := map[string]string {
  "hey": "hi",
  "beep": "boop",
  "foo": "bar",
  "bleep": "blop", // trailing comma required
}
fmt.Println(myMap) // map[hey:hi beep:boop foo:bar bleep:blop]

Maps of maps

We can create maps of maps like this:

mapOfMap := map[string]map[string]int {
  "hey": {
    "beep": 10,
    "boop": 20,
  },
  "hi": {
    "beep": 30,
    "boop": 40
  }
}
fmt.Println(mapOfMap) // map[hey:map[beep:10 boop:20] hi:map[boop:40 beep:30]]

Note: Maps of maps used to require a more verbose syntax:

oldMapOfMap := map[string]map[string]int {
  "hey": map[string]int{
    "beep": 10,
    "boop": 20,
  }
}
fmt.Println(mapOfMap) // map[hey:map[beep:10 boop:20] hi:map[boop:40 beep:30]]

This is no longer so as of Go 1.7 and a little earlier. Just make sure you are running the latest go.

Functions

A simple function example looks like:

func average(xs []float64) float64 { panic("Not Implemented") }
/*^    ^     ^      ^        ^     ^
  |    |     |      |        |     |
keyword|     |      |        |     |
       |     |      |        |     |
     name    |      |        |     |
           arg1  arg-type    |     |
                             |     |
                       return-type |
                                   |
                            function-
*/

The combination of the function's arguments(AKA parameters) and return type is known as the function signature.

Calling a function in go pushes the function onto the execution callstack. When the function returns, it is popped off the callstack and returns a value or set of values to the previous function on the callstack.

callstack

(Image from golang-book.com)

Named Return Types

Return types can be optionally named. Named return types are intrinsically returned when the function ends:

func f2() (r int) {
  r = 1
  return
  // r is returned with whatever value is assigned to it.
}

Multiple return values

Returning multiple return values is as easy as declaring them in the return section of the function declaration.

func f3() (int, int) {
  return 5, 6
}

func main() {
  x, y := f3()
  fmt.Println(x, y) // 5, 6
}

Multiple return values are often used to indicate errors or success values:

x, err := f()
x, ok := g()

Variadic Functions

You can indicate the last function argument can take a variable number of values for that argument. Prefix the type of the last argument with a .... You can access these arguments with range.

func sum(args ...int) int {
  total := 0
  for _, v := range args {
    total += v
  }
  return total
}

func main () {
  fmt.Println(add(1,2,3)) // 6
}

Closures

You can create function closures in go by creating a function assigned to a local variable of an enclosing function. You can also return the inner function, which will retain access to local variables of the returned outer function. This allows for stateful functions and private variables.

func makeEvenGenerator() func() uint {
  i := uint(0)

  even := func () (ret uint) {
    ret = i
    i += 2
    return
  }

  return even
}

func main() {
  nextEven := makeEvenGenerator()
  fmt.Println(nextEven()) // 0
  fmt.Println(nextEven()) // 2
  fmt.Println(nextEven()) // 4
}

Recursion

Go supports recursion. You can call functions inside themselves.

func factorial(x uint) uint {
  if x == 0 {
    return 1
  }
  return x * factorial(x - 1)
}

Defers

The defer statement can be used to schedule a function call to run when the current function scope is returned from.

func first() {
  fmt.Println("1st")
}

func second() {
  fmt.Println("2nd")
}

func main() {
  defer second()
  first()
}

// Prints "1st" then "2nd"

Use this when opening resources that need to be closed, like files.

f, _ := os.Open(filename)
defer f.Close()

Panic and Recover

Panic and Recover are like throw and try/catch in javascript. When you panic in a function, it immediately returns from the function, just like throw. We can catch the panic using defer:

func main() {
  defer func() {
    str := recover()
    fmt.Println(str) // PANIC
  }()
  panic("PANIC")
}

Pointers

Pointers let us pass references to variable instances around by referring to locations in memory.

func zero(xPtr *int) {
  *xPtr = 0
}

func main() {
  x := 5
  zero(&x)
  fmt.Println(x) // 0
}

You can require a pointer argument by putting the * in front of the argument type.

To dereference a pointer means to access its value. This is done by putting a * in from of the argument name in the function body.

To create a pointer and pass it as a pointer argument in a function, prefix the variable with a & as you pass it into the funcion.

func main() {
  x := 5 // type of int
  y := &x // returns a pointer to x of type *int
  fmt.Println(*y) // will print 5.  *y returns the value stored at x
}
x int -> &x -> func(x *int) -> *x

- &x get pointer to variable x
- *x access/dereference variable at pointer x

new

The new function takes a type as an argument, creates a variable of that type and returns a pointer to that variable.

func one(xPtr *int) {

}

func main() {
  xPtr := new(int)
  one(xPtr)
  fmt.Println(*xPtr) // 1
}

Structs

Structs are types with named fields.

type Circle struct {
  x float64
  y float64
  r float64
}
type Circle struct {
  x, y, r float64
}

Initialize like any other type. Fields default to their respective zero value of their type.

var c Circle // or
c := new(Circle) // returns a pointer *Circle

We can also give values on initialization:

c := Circle{x: 0, y: 0, r: 5}
// or
c := Circle{0, 0, 5}

Fields

Field access is done with the . operator.

fmt.Println(c.x, c.y, c.r)
c.x = 10
c.y = 5

Methods

You can add methods to Structs / Types. These are functions that can be called off type instances, and can self reference the internal struct. To add a method, declare a function with a receiver between the func keyword and the function name. The method attaches itself to the type of the receiver.

/*    Receiver
         |
         |
    _____|______
    |           |               */
func (c *Circle) area() float64 {
  return math.Pi * c.r * c.r
}/*                 ^
                    |
                    |
field access auto-dereferences the receiver  */
c.area() //calls the method on c
type Rectancle struct {
  x1, y1, x2, y2 float64
}

func (r *Rectancle) area() float64 {
  l := distance(r.x1, r.y1, r.x1, r.y2)
  w := distance(r.x1, r.y1, r.x2, r.y1)
  return l * w
}

func distance(x1, y1, x2, y2 float64) float64 {
  a := x2x1
  b := y2y1
  return math.Sqrt(a*a + b*b)
}

Pointer types vs types.

Methods can be attached to types and pointer types.

Methods attached to types will copy of each argument value. This means it will not be able to change values or be expensive if arguments are large structures.

Methods attached to pointer types can modify values and are cheaper to call since no copying is done.

By convention all methods should be pointer receivers or receivers, but not both.

Example: If p is a variable of type Point, but the method requires a \*Point receiver, the compiler will translate

p.ScaleBy(2)

into

&p.ScaleBy(2)

or visa versa

pptr.Distance(q)
// turns into
(*pptr).Distance(q)

Embedded Types

You can create struct fields in other structs:

type Person struct {
  Name string
}

func (p * Person) Talk() {
  fmt.Println("Hi, my name is ", p.name)
}

type Android struct {
  Person Person // <--- Android has a person
  Model string
}
a := new(Android)
a.Person.Talk()

// or
type Android struct {
  Person // <--- Android is a person.  Embedded/Anonymous type
  Model string
}
a := new(Android)
a.Person.Talk()
a.Talk() // Works because it IS a person in this case.

Interfaces

Interfaces are like meta structs. Interfaces define a method set that contains the list of methods that a type must implement to qualify for the interface type.

type Shape interface {
  area() float64
}

We can use interface types as arguments to functions:

func totalArea(shapes ...Shape) float64 {
  var area float64
  for _, s := range shapes {
    area += s.area()

  }
  return area
}

Go routines

Go provides concurrency primitives, a primary selling point of the language. Any function call can be made async/concurrent by prefixing the call with the go keyword.

package main

import "fmt"

func f(n int) {
  for i:= 0; i < 10; i++ {
    fmt.Println(n, ":", i)
  }
}

func main() {
  go f(0) // runs concurrently with˅
  var input string
  fmt.Scanln(&input)
}

Channels

Channels are how messages are passed between concurrent go routines.

// make a new Channel
var c chan string = make(chan string)
// Write to Channel
c <- "ping"
// write chan to variable
x := c

You make a channel while starting go routines, and pass them in.

package main

import (
  "fmt"
  "time"
)

func pinger(c chan string) {
  for i := 0 ; ; i++ {
    c <- "ping" // write to channel
  }
}

func ponger(c chan string) {
  for i := 0 ; ; i++ {
    c <- "pong" // write to channel
  }
}

func printer(c chan string) {
  for {
    msg := <- c // block this go routine waiting for messages
    fmt.Println(msg) // prints ping pong ping pong....
    time.Sleep(time.Second * 1)
  }
}

func main() {
  var c chan string = make(chan string)

  go pinger(c)
  go ponger(c)
  go printer(c)

  var input string // blocks main from ending till a character is typed
  fmt.Scanln(&input)
}

Channels can only hold one message at a time (per routine) and you can use that synchronize two routines.

Channel Direction

func pinger (c chan<- string) // Send only
func printer (c <-chan string) // Read only

Select

select is like a switch for Channels. It reads from the first channel available with a message and randomly selects any others that are ready, otherwise it blocks.

package main

import (
  "fmt"
  "time"
)

func sender1(c chan<- string) {
  for {
    c <- "from 1"
    time.Sleep(time.Second * 2)
  }
}

func sender2(c chan<- string) {
  for {
    c <- "from 2"
    time.Sleep(time.Second * 3)
  }
}

func selector(chan1 <-chan string, chan2 <-chan string) {
  for {
    select {
    case msg1 := <- chan1:
      fmt.Println(msg1)
    case msg2 := <-chan2:
      fmt.Println(msg2)
    }
  }
}

func main() {
  c1 := make(chan string)
  c2 := make(chan string)

  go sender1(c1)
  go sender2(c2)
  go selector(c1, c2)

  var input string
  fmt.Scanln(&input)
}

Timeouts can be created with time.After. Defaults can also be declared

select {
case msg1 := <- c1:
  fmt.Println("Message 1", msg1)
case msg2 := <- c2:
  fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
  fmt.Println("timeout")
default:
  fmt.Println("not ready") // This spams in a for loop
}

Buffered Channels

You can write more than one message to a buffered channel:

c := make(chan int, 1)
/*                  ^
                    |
                    |
      number of messages to buffer */

Error handing

New errors can be created by the errors package

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("emit macho dwarf: elf header corrupted")
	if err != nil {
		fmt.Print(err)
	}
}

TODO: The following sections

Readers

Writers

Encoding / Decoding

Network Services

package main

import (
  "context"
  "flag"
  "log"
  "net/http"
  "os"
  "os/signal"
  "time"
)
// Gracefull shutdown example
var (
  listenAddr string
)

func main() {
  flag.StringVar(&listenAddr, "listen-addr", ":5000", "server listen address")
  flag.Parse()

  logger := log.New(os.Stdout, "http: ", log.LstdFlags)

  done := make(chan bool, 1)
  quit := make(chan os.Signal, 1)

  signal.Notify(quit, os.Interrupt)

  server := newWebserver(logger)
  go gracefullShutdown(server, quit, done)

  logger.Println("Server is ready to handle requests at", listenAddr)
  if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    logger.Fatalf("Could not listen on %s: %v\n", listenAddr, err)
  }

  <-done
  logger.Println("Server stopped")
}

func gracefullShutdown(server http.Server, quit <-chan os.Signal, done chan bool<-) {
  <-quit
  logger.Println("Server is shutting down...")

  ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  defer cancel()

  server.SetKeepAlivesEnabled(false)
  if err := server.Shutdown(ctx); err != nil {
    logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
  }
  close(done)
}

func newWebserver(logger *log.Logger) *http.Server {
  router := http.NewServeMux()
  router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
  })

  return &http.Server{
    Addr:         listenAddr,
    Handler:      router,
    ErrorLog:     logger,
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout:  15 * time.Second,
  }
}

From: https://marcofranssen.nl/go-webserver-with-gracefull-shutdown/

reflect and handling multiple data types

About

🐹 A Golang concentrate for refreshing your memory on the go programming language

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages