Let's Go

It’s time to be a Gopher, I think.

0

Background

The Go Programming Language was started at Google by Robert Griesemer, Rob Pike and Ken Thompson in 2007, with its first release in 2009. The core reason behind creating a new language then was to help manage large code bases, keep the development productive and focus on simplicity. With its roots across multiple programming languages and especially that of C language, it found its followers slowly and steadily. The Go Project is not just the language specification but also a toolchain and an ecosystem that consists of standard libraries and external 3rd party libraries contributed by organizations and individuals.

Key Features

Binaries

Go generates binaries for your applications with all the dependencies built-in. This removes the need for you to install runtimes that are necessary for running your application. This eases the task of deploying applications and providing necessary updates across thousands of installations. With its support for multiple OS and processor architectures, this is a big win for the language.

Language Design

The designers of the language made a conscious decision to keep the language simple and easy to understand. The entire specification is in a small number of pages and some interesting design decisions were made vis-à-vis Object-Oriented support in the language, that keeps the features limited. Towards this, the language is opinionated and recommends an idiomatic way of achieving things. It prefers Composition over Inheritance and its Type System is elegant and allows for behavior to be added without tight coupling the components too much. In Go Language, “Do More with Less” is the mantra.

But, less is more, do more is less.

vis-à-vis: 面对面的;相对;同 … 矛盾

Powerful standard library

Go comes with a powerful standard library, distributed as packages. This library caters to most components, libraries that developers have come to expect from 3rd party packages when it comes to other languages

Package Management

Go combines modern day developer workflow of working with Open Source projects and includes that in the way it manages external packages. Support is provided directly in the tooling to get external packages and publish your own packages in a set of easy commands.

Static Typing

Go is a statically typed language and the compiler works hard to ensure that the code is not just able to compile correctly but other type conversions and compatibility are taken care of. This can avoid the problems that one faces in dynamically typed languages, where you discover the issues only when the code is executed.

Concurrency Support

One area where the language shines is its first-class support for Concurrency in the language itself. If you have programming concurrency in other languages, you understand that it is quite complex to do so. Go Concurrency primitives via go routines and channels makes concurrent programming easy. Its ability to take advantage of multi-core processor architectures and efficient memory is one of the reasons while Go code is today running some of the most heavily used applications that are able to scale.

First-class support: 一等/头等支持
Concurrency primitives: 并发原语

Testing Support

Go Language brings Unit Testing right into the language itself. It provides a simple mechanism to write your unit tests in parallel with your code. The tooling also provides support to understand code coverage by your tests, benchmarking tests and writing example code that is used in generating your code documentation.

Code Organization

Workspace

Most Go programmers keep all their Go source code and dependencies in a single workspace.

And a workspace is a directory hierarchy with three directories at its root:

  • src contains Go source files
  • pkg contains package objects: built by go tool
  • bin contains executable commands: installed by go tool

For example in my macOS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
➜  go pwd
/Users/ckwongloy/go
➜ go tree -d -L 3 .
.
├── bin
├── pkg
│   └── darwin_amd64
│   └── github.com
└── src
├── github.com
│   ├── BurntSushi
│   ├── PuerkitoBio
│   ├── alessio
│   ├── andybalholm
│   ├── asciimoo
│   ├── fatih
│   ├── golang
│   ├── jroimartin
│   ├── mattn
│   ├── mitchellh
│   ├── mkouhei
│   ├── nsf
│   ├── nwidger
│   ├── tidwall
│   └── x86kernel
└── golang.org
└── x

23 directories

Inclusion

Workspace contains repositories, repository contains packages, package consists of one or more Go source files in a single directory

Import paths

Package is imported by a path: either a location inside a local workspace, or a remote repository.

The packages from the standard library are given short import paths such as “fmt” and “net/http”. For your own packages, you must choose a base path that is unlikely to collide with future additions to the standard library or other external libraries.

If you keep your code in a source repository somewhere, then you should use the root of that source repository as your base path. For instance, if you have a GitHub account at github.com/user, that should be your base path.

It’s important to use base path if the package you want import is not a standard library in golang.org.

Generally, we using github.com/user as base name to import 3rd party package:

1
2
3
import (
"github.com/ckwongloy/some-package"
)

Of course, in the example above, package some-package is unnecessary to be published on github.com until you really need to, it can only be a local path in $workspace/src, like ~/go/src/github.com/ckwongloy/some-package/, so basically this is a convention in go to organize your code.

In a word, The path to package’s directory determines its import path

Export Visiblity

Determined by letter case of name, for example: Foo is exported, but foo is not.

GOPATH

Print current effective GOPATH:

1
go env GOPATH

The GOPATH environment variable specifies the location of your workspace.

By default GOPATH is a directory named go inside your home directory. However it’s also alterable, take zsh for example:

Adding the following line in ~/.zshrc:

1
2
3
4
# export GOPATH=$HOME/go
export GOPATH=$(go env GOPATH) # Ensure the scripts can run if you have no set GOPATH yet

# export PATH=$PATH:$(go env GOPATH)/bin # For convenience to call go executables

Then source ~/.zshrc. More settings in different shell see: https://github.com/golang/go/wiki/SettingGOPATH.

Note that GOPATH must not be the same path as your Go installation.

Install package

1
2
3
4
5
6
7
# 1. Install from a specific package path name
# This command can be ran from anywhere on your system. The go tool finds the source code by looking for the github.com/user/package package inside the workspace specified by GOPATH.
go install github.com/user/package

# 2. Install directly in the dirctory of that package
cd ~/go/src/github.com/user/package
go install

Both example above are build the package command, producing an executable binary, and install this binary to the bin directory of workspace as package (or package.exe on windows).

Generally, there are four ways to get golang dependencies installed:

  • github.com
  • golang.org
  • gopkg.in
  • honnet.co

For example, we will using go get to download packages into $GOPATH/src, and using go install to install them into $GOPATH/bin.

The go tool will only print output when an error occurs, so if these commands produce no output they have executed successfully.

Build library

Same as go install, go build can be executed in two forms.

Things different from package are:

  • package keyword to point out in go source file.

In package.go, it looks must like this:

1
2
3
package main    # !!! must named as `main`

// ...

In library.go, it looks must like this:

1
2
3
4
// some_library is the package's default name for imports
package some_library # !!! must not named as `main`

// ...
  • Whether produce output file

Using go install to install or using go build to build a pacakge will always produce output binary file (former place that binary in $GOPATH/bin/, and later place that binary in current working directory).

However, using go build to build an library will not produce any output files. It is the go install produces and palces an file called package object (library.a) inside the $GOPATH/pkg/$OSA/github.com/user/, which follows same directory hierarchy of that library source, of course.

$OSA: depends on OS and architecture

The reason for existence of package object file: This is so that future invocations of the go tool can find the package object and avoid recompiling the package unnecessarily. The linux_amd64/darwin_amd64 part is there to aid in cross-compilation, and will reflect the operating system and architecture of your system.

Go command executables are statically linked; the package objects need not be present to run Go programs.

NOTE THAT: Whenever the go tool installs a package or binary, it also installs whatever dependencies it has automatically.

Package Names

All files in a package must use the same name.

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.

Executable commands must always use package main.

There is no requirement that package names be unique across all packages linked into a single binary, only that the import paths (their full file names) be unique.

Testing

See: https://golang.org/pkg/testing/

Typical test

Go has a lightweight test framework composed of the go test command and the testing package.

You write a test by creating a file with a name ending in _test.go that contains functions named TestXXX with signature func (t *testing.T).

The test framework runs each such function; if the function calls a failure function such as t.Error or t.Fail, the test is considered to have failed.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// add.go
package demo

func Add(x, y int) int {
return x + y
}

// add_test.go
package demo

import "testing"

// func TestFname(t *testing.T) {
func TestAdd(t *testing.T) {
cases := []struct{
x, y, sum int
} {
{1, 0, 1},
{11, 22, 33},
{0, -1, -1},
}
}

Note that Fname does not start with a lowercase letter. The function name serves to identify the test routine.

Same as go install/go build, go test can be executed in two forms.

1
2
3
4
go test github.com/user/some_package

cd ~/go/src/github.com/user/some_package
go test

Within these functions, use the Error, Fail or related methods to signal failure, or skip if not applicable with a call to the Skip method of T (and B below).

Test Examples: Examples are Tests

See: https://blog.golang.org/examples

Examples are compiled , and optionally executed as part of a package’s test suite.

As with typical tests, examples are functions that reside in a package’s _test.go files. Unlike normal test functions, though, example functions take no arguments and begin with the word Example instead of Test.

Example functions may include a concluding line comment that begins with “Output:” and is compared with the standard output of the function when the tests are run. (The comparison ignores leading and trailing space.)

1
2
3
4
func ExampleAdd() {
fmt.Println(demo.Add(1, 2))
// Output: 3
}

As it executes the example, the testing framework captures data written to standard output and then compares the output against the example’s “Output:” comment.
The test passes if the test’s output matches its output comment.

Examples with Godoc

Godoc will present this example alongside the Reverse function’s documentation

Godoc uses a naming convention to associate an example function with a package-level identifier.

1
2
3
func ExampleFoo()     // documents the Foo function or type
func ExampleBar_Qux() // documents the Qux method of type Bar
func Example() // documents the package as a whole

Multiple examples can be provided for a given identifier by using a suffix beginning with an underscore followed by a lowercase letter. Each of these examples documents the Reverse function:

1
2
3
func ExampleReverse()
func ExampleReverse_second()
func ExampleReverse_third()

Following this convention, godoc displays the ExampleReverse example alongside the documentation for the Reverse function.

godoc.org: Search for Go Packages

GoDoc hosts documentation for Go packages on Bitbucket, GitHub, Google Project Hosting and Launchpad.

godoc.org is functionally similar with packagist.org, where the place to search/publish third party packages for that programming language.

Benchmark

The testing package also support benchmarks.

1
2
3
4
5
6
7
func BenchmarkFname(b *testing.B) {
const s = "some_text"

for i := 0; i < b.N; i++ {
strings.Index(s, "v")
}
}

Note that, the value of b.N is varied when benchmarking, until the benchmark function get enough samples to make the result reliably.

The run:

1
2
3
4
5
6
go test -test.bench=Index

`
PASS
BenchmarkFname 500000000 40.2 ns/op
`

The result means that the loop ran 500000000 times at a speed of 40.2 ns per loop.

Go Tool

1
go help [command]

go get Remote Packages

If you include the remote repository URL in the package’s import path, go get will fetch, build, and install it automatically:

1
2
3
4
$ go get github.com/golang/example/hello

$ $GOPATH/bin/hello
Hello, Go examples!

If the specified package is not present in a workspace, go get will place it inside the first workspace specified by GOPATH. (If the package does already exist, go get skips the remote fetch and behaves the same as go install.)

go get command is able to locate and install the dependent packages of target package, too.

gofmt

Formatting issues are the most contentious but the least consequential.

There’s no need to spend time formatting the go code, let gofmt do that for you.

1
2
3
# write result to (source) file instead of stdout
# see `gofmt -help` for more details
gofmt -w source.go

godoc

  • Run a local golang.org site
1
godoc -http=127.0.0.1:6060

By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set). This behavior can be altered by providing an alternative $GOROOT with the -goroot flag.

1
godoc -http=127.0.0.1:6060 -goroot=/path/to/godoc_webroot
  • Two modes with godoc command: http (with -http=addr option) and cli (without).

  • godoc built in with search function, we can search against an server with option -q:

1
2
godoc -q keywords    # Try localhost:6060 first and then http://golang.org
godoc -server=:6666 -q keywords # search keywords on localhost:6666

Well, if you are running godoc website in your local machine, you need to specific -index option when start the server.

1
godoc -http=127.0.0.1:6060 -index
  • godoc only generates documentation for non-main packages: library packages and command packages.

If both a library package and a command with the same name exists, using the prefix cmd/ will force documentation on the command rather than the library package.

If you want document package main, you need to build a slightly modified version of godoc. (See FAQ blew).

  • godoc only prints the exported interface (Types and Functions) in Go source form, ie, lower case function name fn() will not displayed in documentation.

See More Details: https://godoc.org/golang.org/x/tools/cmd/godoc

  • Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// This is the package description
// You can put License, Author, Copyright here
// This will not displayed in documentation

// This comment next to `package` keyword without new empty line will displayed in the overview
package doc_demo

import (
"A"
"B"
)

// This is the description to constant list
const (
PI = 3.1415926
)

// This is the description to struct `S`
type S struct {
A string
B int

// This will not documented and output as:
// contains filtered or unexported fields
c bool
}

// This is the description to function `Fn`
func Fn() (int, error) {
return 1, nil
}

// This will not been documented
func fn() (int, error) {
return 1, nil
}

See more: https://godoc.org/github.com/fluhus/godoc-tricks#example-Examples

Grammer

Packages, data types, variables, functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Every Go program is made up of packages.
// Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.
package main

// Import package from standard library, 3rd party packages or an accessible URL
import (
"fmt"
// "math"
// "github.com/user/repo"
)

// Define outer variables
// Outside a function, every statement begins with a keyword (var, func, and so on)
// so the declare-and-initialize construct := is not available.
// error := "not working" // error
var go, goo, gooo bool

/ Constants can be character, string, boolean, or numeric values.
// Constants cannot be declared using the := syntax.
const Pi = 3.1415926
const (
Big = 1 << 100
Small = Big >> 99
)
func need_int(i int) int {
return i * 10 + 1
}
func need_float(f float64) float64 {
return f * 0.1
}
func const_demo() {
const InnerConst = "CJLI"
const Truth = true
fmt.Println("Hello", InnerConst)
fmt.Println(Pi)
fmt.Println("Go rules?", Truth)

fmt.Println(Small)

// An untyped constant takes the type needed by its context.
// Here its int as default
// fmt.Println(Big) // ERROR: constant XXXX overflows int
// fmt.Println(uint64(Big))

fmt.Println(complex128(Big))
fmt.Println(need_int(Small))
fmt.Println(need_float(Small))
// fmt.Println(need_int(Big))
fmt.Println(need_float(Big))
}

// Basic data types
var (
boolean bool = false
str string = "go go go"
intMax uint64 = 1 << 64 -1
byteV byte = 42
unicodePoint rune = 1024
unitptrV uintptr = 1024*1024
floatV float64 = 11.22
complexZ complex128 = cmplx.Sqrt(-5 + 12i)

intZero int
boolZero bool
strZero string
)

// Basic functions
func fn_xyz(x, y, z float64) (float64, error) {
// Closure
// Inside a function, the := short assignment statement can be used in place of a var declaration with implicit type.
adder := func() func(float64, float64) float64 {
return func(m, n float64) float64 {
return m + n
}
}

sum := adder()(x, y) + adder()(x, z) + adder()(y, z)

return sum, nil
}

// Programs start running in package main.
// A complete program is created by linking a single, unimported package called the main package with all the packages it imports, transitively.
// The main package must have package name main and declare a function main that takes no arguments and returns no value.
func main() {
fmt.Printf("Type: %T Value: %v\n", boolean, boolean)

fmt.Println(fn_xyz(1, 2, 3))
}

The expression T(v) converts the value v to the type T:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func type_conversion() {
// var x, y int = 1, 2
// var ff float64 = math.Sqrt(float64(x*x + y*y))

var x, y = 1, 2
var ff = math.Sqrt(float64(x*x + y*y))

// Or simplier
f := float64(x)
ui := uint(f)

fmt.Println(x, y, ff, f, ui)
}

func type_reference() {
// When declaring a variable without specifying an explicit type (either by using the := syntax or var = expression syntax), the variable's type is inferred from the value on the right hand side.
v := 42.24

// When the right hand side of the declaration is typed, the new variable is of that same type:
var i int
j := i // j is an int

fmt.Printf("v is of type %T\n", v)
fmt.Printf("i, j is of type %T, %T\n", i, j)
}

Flow control statements

Condition

1
2
3
4
5
6
7
8
9
10
11
12
func pow(x, n, limit float64) float64 {
if v := math.Pow(x, n); v < limit {
return v
} else {
fmt.Printf("%g^%g=%g >= %g \n", x, n, v, limit)
}

// Variables declared by the statement are only in scope until the end of the if.
// return v // undefined: v

return limit
}

Go’s switch is like the one in C, C++, Java, JavaScript, and PHP, except that Go only runs the selected case, not all the cases that follow.

In effect, the break statement that is needed at the end of each case in those languages is provided automatically in Go.

Another important difference is that Go’s switch cases need not be constants, and the values involved need not be integers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
func switch_demo() {
fmt.Println("Go on checking operating system ...")

// os := runtime.GOOS
// switch os {
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("mac OS")
// ERROR: invalid case 1024 in switch on os (mismatched types int and string)
// case 1024:
case "linux":
fmt.Println("Linux")
default:
fmt.Println("%s", os)
}

switch i := 1; i {
case 42:
fmt.Println("Answer to the Ultimate Question of Life, the Universe, and Everything")
case f_int():
fmt.Printf("integer %d", i)
default:
fmt.Println("integer ?")
}

fmt.Print("\nWhen's Saturday ? ")
today := time.Now().Weekday()
switch time.Saturday {
case today:
fmt.Println("Today")
case today + 1:
fmt.Println("Tomorrow")
case today + 2:
fmt.Println("In 2 days")
default:
fmt.Println("We don't know")
}

// Switch with no conditions
time := time.Now()
switch {
case time.Hour() < 12:
fmt.Println("Morning!")
case time.Hour() < 18:
fmt.Println("Afternoon!")
default:
fmt.Println("Evening!")
}
}

Defer

A defer statement defers the execution of a function until the surrounding function returns.

The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

1
2
3
4
5
6
7
8
9
10
11
func defer_demo() {
defer fmt.Println("World !")

fmt.Println("Hello")

// Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.
// Output: 4 3 2 1
for i := 1; i < 5; i++ {
defer fmt.Println(i)
}
}

Loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Go has only one looping construct, the for loop.
func loop() {
sum := 0
// ++i will trigger an error
for i := 0; i < 10; i++ {
sum += i
}

fmt.Println(sum)

// The init and post statement are optional.
for ; sum < 100; {
sum += sum
}

fmt.Println(sum)

// C's while is spelled for in Go.
for sum < 1000 {
sum += 20
}

fmt.Println(sum)

// infinite loop
for {
}
}

Data structures: pointer/struct/array/slice/range/map/function

Pointer

A pointer holds the memory address of a value

Pointer’s zero value is .

Unlike C, Go has no pointer arithmetic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 对已说明的变量来说 变量名就是对变量值的直接引用
// 对指向变量或内存中的任何对象的指针来说 指针就是对对象值的间接引用
// 如果p是一个指针,p的值就是其对象的地址;`*p` 表示“使间接引用运算符作用于p”,*p的值就是p所指向的对象的值。
// *p是一个左值,和变量一样,只要在*p的右边加上赋值运算符,就可改变*p的值。如果p是一个指向常量的指针,*p就是一个不能修改的左值,即它不能被放到赋值运算符的左边
func pointer() {
// The type *T is a pointer to a T value
var p *int // 定义 int 型指针变量 p

fmt.Println(p) // nil

// The & operator generates a pointer to its operand.
i := 42
p = &i // 将变量 i 的内存地址放入变量 p 中
// dereferencing => 直接引用变量 i
fmt.Println(i, p) // 42 0xc42000e298

// The * operator denotes the pointer's underlying value.
// indirecting => 间接引用变量 i
fmt.Println(*p) // read i through the pointer p
*p = 1024 // set i through the pointer p

fmt.Println(*p) // 1024
}

Struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func struct_demo() {
// A struct is a collection of fields.
type Vertex struct {
X int
Y int

M, N float32
}

fmt.Println(Vertex{1, 2, 33.44, 55.66})

v := Vertex{11, 22, 33.444, 55.666}
v.X = 33 // Struct fields are accessed using a dot.
fmt.Println(v.X)

// To access the field X of a struct when we have the struct pointer p we could write (*p).X.
// However, that notation is cumbersome, so the language permits us instead to write just p.X, without the explicit dereference.
p := &v
// (*p).X = 44
p.X = 44
fmt.Println(v)

// A struct literal(结构体文法) denotes a newly allocated struct value by listing the values of its fields.
var (
v1 = Vertex{1, 2, 333.44, 555.66}
v2 = Vertex{X: 1} // Y,M,N are implicit 0
v3 = Vertex{}
p1 = &Vertex{1, 2, 3333.4, 5555.6}
)

fmt.Println(v1, v2, v3, p1)
}

Array

An array’s length is part of its type, so arrays cannot be resized.

This seems limiting, but don’t worry; Go provides a convenient way of working with arrays.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func array_demo() {
var arr [5]int

fmt.Println(arr) // 0 0 0 0 0

var str [2]string
str[0] = "hello"
str[1] = "array"
// str[2] = "error" // invalid array index 2 (out of bounds for 2-element array)

fmt.Println(str, str[0])

foo := [3] int{1, 3, 5}

fmt.Println(foo)
}

Slice

An array has a fixed size. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array.

In practice, slices are much more common than arrays.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
func slice_demo() {
primes := [5]int{2, 3, 5, 7, 11}

// A slice is formed by specifying two indices, a low and high bound, separated by a colon: a[low : high]
// var s []int = primes[0:0] // empty
var s []int = primes[0:2] // [2,3]

// A slice does not store any data, it just describes a section of an underlying array.
fmt.Println(s)
fmt.Println(primes[:3]) // <=> primes[0:3]
fmt.Println(primes[3:]) // <=> primes[3:4]

// Slices are like references to arrays
// Changing the elements of a slice modifies the corresponding elements of its underlying array.
// Other slices that share the same underlying array will see those changes.
a := primes[:2]
b := primes[3:]
fmt.Println(a, b, primes)

a[0] = 1111
b[0] = 2222
fmt.Println("Slice like reference: ", a, b, primes)

// A slice literal is like an array literal without the length.
// m := [3]int{1, 3, 5}
// And this creates the same array as above, then builds a slice that references it:
m := []int{1, 3, 5}
n := []bool{false, true, false}
q := []struct {
i int
j bool
} {
{11, true},
{22, false},
}
fmt.Println("Slice literal: ", m, n, q)

var a1 [10]int
a2 := a1[0:10]
a3 := a1[:10]
a4 := a1[0:]
a5 := a1[:]

fmt.Println("Slice defaults, a1, a2, a3, a4, a5 are equivalent", a1, a2, a3, a4, a5)

// Slice length and capacity
// You can extend a slice's length by re-slicing it, provided it has sufficient capacity.
d := []int{1, 3, 5, 7, 9}
d1 := d[:0] // slice the slice to zere length
d2 := d1[:4] // extend slice
// d2 := d1[:99] // panic: runtime error: slice bounds out of range
d3 := d2[3:] // drop first three

fmt.Printf("len=%d, cap=%d, %v\n", len(d), cap(d), d)
fmt.Printf("len=%d, cap=%d, %v\n", len(d1), cap(d1), d1)
fmt.Printf("len=%d, cap=%d, %v\n", len(d2), cap(d2), d2)
fmt.Printf("len=%d, cap=%d, %v\n", len(d3), cap(d3), d3)

// Nil slice: the zero value of a slice
var sn []int
fmt.Println(sn, len(sn), cap(sn))
if sn == nil {
fmt.Println("Zero slice <=> nil")
}

// Dynamically-sized slice/array
// `func append(s []T, vs ...T) []T`
// - The first parameter s of append is a slice of type T, and the rest are T values to append to the slice.
// - The resulting value of append is a slice containing all the elements of the original slice plus the provided values.
// If the backing array of s is too small to fit all the given values a bigger array will be allocated.
// The returned slice will point to the newly allocated array.
// da2 := [3]int{1,4,7}
// append(da2, 2) // ERROR: first argument to append must be slice

da0 := make([]int, 5) // 5 is the length of slice
da1 := make([]int, 0, 5) // 5 is the capacity of slice
da0 = append(da0, 2)
da1 = da1[:cap(da1)]
fmt.Printf("da0 lenth=%d capacity=%d: %v \n", len(da0), cap(da0), da0)
fmt.Printf("da1 lenth=%d capacity=%d: %v \n", len(da1), cap(da1), da1)

// Slices of slices
// Slices can contain any type, including other slices.
// Create a tic-tac-toe board.
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}

// The players take turns.
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"

for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}
Growing slices/Dynamic array

To increase the capacity of a slice one must create a new, larger slice and copy the contents of the original slice into it. This technique is how dynamic array implementations from other languages work behind the scenes.

https://blog.golang.org/go-slices-usage-and-internals
https://golang.org/pkg/builtin/#append

Range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func range_demo() {
// list := []int{1, 3, 5, 7, 9}
list := [5]int{2, 4, 6, 8}

// Two values are returned for each iteration.
// The first is the index, and the second is a copy of the element at that index.
for i, v := range list {
fmt.Println(i, v)
}

// Skip index or value by assigning to `_`
list2 := make([]int, 8)
for i := range list2 { // Here we need index only and drop value entirely
list2[i] = 1 << uint(i) // 2 ** i
}

for _, value := range list2 {
fmt.Printf("%d\n", value)
}
}

Map

A map maps keys to values.

The zero value of a map is nil. A nil map has no keys, nor can keys be added.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
func map_demo() {
type Coord struct {
lat, lng float64
}
var m map[string]Coord
// m := map[string]Coord // Error: type map[string]Coord is not an expression
m = make(map[string]Coord)
m["test_point"] = Coord{
113.323323, 23.212434, // `,` is requisite here
}

fmt.Println(m["test_point"])

// Map literals are like struct literals, but the keys are required.
var m2 = map[string]Coord{
"test_1": Coord{
113.111111, 23.111111,
},
"test_2": Coord{
113.222222, 23.222222,
},
// If the top-level type is just a type name, you can omit it from the elements of the literal.
"test_3": {113.333333, 23.333333,},
}

fmt.Println(m2)

// Mutating maps
m3 := make(map[string]Coord)
m3["key_1"] = Coord{11,22} // Insert or update
m3["key_2"] = Coord{33,44} // Insert or update
key_1 := m3["key_1"] // Retrieve item by its key
delete(m3, "key_1") // Delete
// Test that a key is present with a two-value assignment:
// f key is in m, ok is true. If not, ok is false.
// If key is not in the map, then elem is the zero value for the map's element type.
key_1_1, exist := m3["key_1"]

fmt.Println(m3, key_1, key_1_1, exist)
}

Functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func func_demo() {
fn1 := func (x, y float64) float64 {
return x + y
}
fn2 := func (fn func (float64, float64) float64) float64 {
return fn(2, 22)
}
// Functions are values too. They can be passed around just like other values.
// Function values may be used as function arguments and return values.
fmt.Println(fn1(1, 11))
fmt.Println(fn2(fn1))
fmt.Println(fn2(math.Pow))

// A closure is a function value that references variables from outside its body.
// The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables.
fnc := func() func(int) int {
sum := 0
return func (i int) int {
sum += i
return sum
}
}

fnct0, fnct1 := fnc(), fnc()

for i := 0; i < 5; i ++ {
fmt.Println(fnct0(i), fnct1(i+42))
}
}

Literal

Programming languages use the word “Literal” when referring to sytactic ways to construct some data structure. It means it’s not constructed by creating an empty one and adding or subtracting as you go

https://stackoverflow.com/questions/29173720/why-are-struct-literals-literal

Method and interface

Method

Go does not have classes. However, you can define methods on types.

A method is a function with a special receiver argument.

The receiver appears in its own argument list between the func keyword and the method name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
type Vertex struct {
X, Y float64
}
type Float float64

// The method `Sqrt` has a receiver of type Vertex named `v`
// A method is just a function with a receiver argument.
// Here's Sqrt written as a regular function with no change in functionality.
// func Sqrt() float64 {
func (v Vertex) Sqrt() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// You can declare a method on non-struct types, too.
// You can only declare a method with (ONLY) a receiver whose type is defined in the same package as the method.
// You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).
func (f Float) abs() float64 {
if f < 0 {
return float64(-f)
}

return float64(f)
}
// With a value receiver, the `ScaleV` method operates on a copy of the original Vertex value. (This is the same behavior as for any other function argument.)
func (v Vertex) ScaleV(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
// You can declare methods with pointer receivers.
// This means the receiver type has the literal syntax *T for some type T. (Also, T cannot itself be a pointer such as *int.)
func (v *Vertex) ScaleP(f float64) {
// Methods with pointer receivers can modify the value to which the receiver points (as `ScaleP` does here).
// Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
v.X = v.X * f
v.Y = v.Y * f
}
// 方法和指针间接寻址
func methods_and_pointer_indirection() {
var v Vertex
fn1 := func (v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

// Functions that take a value/pointer argument must take a value/pointer of that specific type
// fn1(v, 5) // Complie Error: cannot use v (type Vertex) as type *Vertex in argument to fn1
fn1(&v, 5)

// Methods with pointer receivers take either a value or a pointer as the receiver when they are called
// For the statement v.Scale(5), even though `v` is a value and not a pointer, the method with the pointer receiver is called automatically.
// That is, as a convenience, Go interprets the statement v.Scale(5) as (&v).Scale(5) since the Scale method has a pointer receiver.
v.ScaleV(5)
// Conversely, the method call `mp.ScaleV()` is interpreted as `(*mp).Abs()`
mp := &v
mp.ScaleV(4)
}
func abc() {
v := Vertex{1, 2}
fmt.Println(v.Sqrt())
v.ScaleV(2)
// ScaleV(2) // Error: undefined : ScaleV
fmt.Println(v.Sqrt())

v.ScaleP(2)
fmt.Println(v.Sqrt())

f := Float(-math.Sqrt2)
fmt.Println(f.abs())
}
pointer receiver

here are two reasons to use a pointer receiver.

The first is so that the method can modify the value that its receiver points to.

The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, even though the method needn’t modify its receiver.

In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.

Interface

An interface type is defined as a set of method signatures.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Define an interface
type Sqrtable interface {
Sqrt() float64
}

type Float float64
type Vertex struct {
X, Y float64
}

// Interfaces are implemented implicitly
// A type implements an interface by implementing its methods. There is no explicit declaration of intent, no "implements" keyword.
// Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.
func (f Float) Sqrt() float64 {
return math.Sqrt(4)
}

// Type `*Vertex` is implemented interface Sqrtable along with the defination of method Sqrt whose recevier is `*Vertex`
func (v *Vertex) Sqrt() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func abc() {
var sb Sqrtable
f := Float(-math.Sqrt2)
v := Vertex{10, 24}

// Let Type implements interface
// A value of interface type can hold any value that implements those methods. (But only hold one at most)
// An interface value holds a value of a specific underlying concrete type.
// Calling a method on an interface value executes the method of the same name on its underlying type.
sb = f
fmt.Println(sb.Sqrt())
sb = &v
fmt.Println(sb.Sqrt())
// fmt.Println(sb) // <=> fmt.Println(&v)
// Under the covers, interface values can be thought of as a tuple of a value and a concrete type: (value, type)
fmt.Printf("(%v, %T)", sb, sb)

// Vertex (the value type) doesn't implement Sqrtable because the Sqrt method is defined only on *Vertex (the pointer type).
// sb = v // Error: cannot use v (type Vertex) as type Sqrtable in assignment, Vertex does not implement Sqrtable (Sqrt method has pointer receiver)
// fmt.Println(sb.Sqrt())
}

type X interface {
Y()
}
type M struct {
N string
}
func (m *M) Y() {
if m == nil {
fmt.Println("<nil>")
return
}

fmt.Println(m)
}

func nil_empty_zero() {
// Interface values with nil underlying values
// - If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
// - In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver (as with the method M in this example.)
// - Note that an interface value that holds a nil concrete value is itself non-nil.
var x X
var m *M

x = m
desc(x)
x.Y() // <nil>

x = &M{"not_nil"}
desc(x)
x.Y()

// Nil interface values
// - A nil interface value holds neither value nor concrete type.
// - Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.
var xn X
desc(xn) // (<nil>, <nil>)
// xn.Y() // panic: runtime error: invalid memory address or nil pointer dereference

// The empty interface
// - The interface type that specifies zero methods is known as the empty interface: `interface{}`
// - An empty interface may hold values of any type. (Every type implements at least zero methods.)
// - Empty interfaces are used by code that handles values of unknown type. For example, `fmt.Print` takes any number of arguments of type interface{}.
var ei interface{}
fmt.Printf("Empty interface : (%v, %T)", ei, ei)
}
func desc(x X) {
fmt.Printf("(%v, %T)\n", x, x)
}

func interface_type() {
var i interface{} = "string demo"

// A type assertion provides access to an interface value's underlying concrete value.
// This statement asserts that the interface value `s` holds the concrete type `string` and assigns the underlying `string` value to the variable `s`.
s := i.(string)
fmt.Println(s)

// To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.
// And no panic occurs. (similarity with reading from a map)
s, is_str := i.(string)
fmt.Println(s, is_str)

ss, is_int := i.(int)
fmt.Println(ss, is_int)

// If `i` does not hold a `int`, the statement will trigger a panic.
// ss = i.(int) // panic: interface conversion: interface {} is string, not int
// fmt.Println(ss)

// A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.
get_type := func (i interface{}) {
// The declaration in a type switch has the same syntax as a type assertion i.(T), but the specific type T is replaced with the keyword type.
switch t := i.(type) {
case int:
fmt.Printf("%v is an integer\n", t)
case string:
fmt.Printf("%v is a string\n", t)
default:
fmt.Printf("I dont know the type of %v yet: %T", t, t)
}
}

get_type(1)
get_type("cjli")
get_type(&s)
}

Concurrency

Goroutine

A goroutine is a lightweight thread managed by the Go runtime.

Goroutines run in the same address space, so access to shared memory must be synchronized. The sync package provides useful primitives, although you won’t need them much in Go as there are other primitives.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"time"
)

func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}

func main() {
// starts a new goroutine running `say()`
// The evaluation of `say(s)` happens in the current goroutine and the execution of `say()` happens in the new goroutine.
go say("CJLI")

say("Hey")
}

Channels

Channels are a typed conduit through which you can send and receive values with the channel operator, <-.

The example code sums the numbers in a slice, distributing the work between two goroutines. Once both goroutines have completed their computation, it calculates the final result.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import(
"fmt"
"time"
)

func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}

c <- sum // Send sum to channel c
}

func channel() {
s := []int{1, 3, 32, 322, 43, 65}

// Like maps and slices, channels must be created before use
c := make(chan int)

go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)

// Receive from channel and assign value to variables
// By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
x, y := <-c, <-c // Receive result from channel c

fmt.Println(x, y, x+y)
}

Generally, buffering in channels is beneficial for performance reasons.

You should never add buffering merely to fix a deadlock. If your program deadlocks, it’s far easier to fix by starting with zero buffering and think through the dependencies. Then add buffering when you know it won’t deadlock. (https://stackoverflow.com/questions/15113410/when-to-use-a-buffered-channel)

  • Buffer
1
2
3
4
5
6
7
8
9
10
11
func buffer() {
// Channels can be buffered. Provide the buffer length as the second argument to make to initialize a buffered channel:
ch := make(chan int, 20) // When length is less than 2, a deadlock will occur

// Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.
ch <- 1
ch <- 2

fmt.Println(<-ch)
fmt.Println(<-ch)
}

A sender can close a channel to indicate that no more values will be sent.

Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression

Only the sender should close a channel, never the receiver. Sending on a closed channel will cause a panic.

Channels aren’t like files; you don’t usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// v, ok := <- ch
func close_demo() {
c := make(chan int, 5)

fibonacci := func (n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)
}
go fibonacci(cap(c), c)

// The loop for j := range c receives values from the channel repeatedly until it is closed.
for j := range c {
fmt.Println(j)
}
}
  • Goroutine in Select statement

The select statement lets a goroutine wait on multiple communication operations.

A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func select_demo() {
fibonacci := func (c, quit chan int) {
x, y := 0, 1
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick ...")
case <-boom:
fmt.Println("Boom !")
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("QUIT")
return
// The default case in a select is run if no other case is ready.
// Use a default case to try a send or receive without blocking:
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
c := make(chan int)
quit := make(chan int)
go func () {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()

fibonacci(c, quit)
}

Sync Mutex

We’ve seen how channels are great for communication among goroutines.

But what if we don’t need communication? What if we just want to make sure only one goroutine can access a variable at a time to avoid conflicts?

This concept is called mutual exclusion, and the conventional name for the data structure that provides it is mutex.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Go's standard library provides mutual exclusion with sync.Mutex and its two methods: `Lock`/`Unlock`
import "sync"

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
// We can define a block of code to be executed in mutual exclusion by surrounding it with a call to Lock and Unlock
c.mux.Lock()

// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++

c.mux.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()

// Lock so only one goroutine at a time can access the map c.v.
// We can also use defer to ensure the mutex will be unlocked as in the Value method.
defer c.mux.Unlock()

return c.v[key]
}

func main() {
c := SafeCounter{v: make(map[string]int)}

for i := 0; i < 1000; i++ {
go c.Inc("skey")
}

time.Sleep(time.Second)
fmt.Println(c.Value("skey"))
}

Full hello-world source code see: https://github.com/ckwongloy/hola-go

Others

Git ignore go project

1
**/*.go

See: https://stackoverflow.com/questions/36018171/how-to-gitignore-go-binaries

Version

TL;DR:semantic import versioning, and minimal version selection; vgo

  • import compatibility rule

If an old package and a new package have the same import path, the new package must be backwards compatible with the old package.

  • import uniqueness rule

Different packages must have different import paths. The only way to have partial code upgrades, import uniqueness, and semantic versioning is to adopt semantic import versioning as well.

See: A Proposal for Package Versioning in Go

Wannex: where to GO?

Three things that make Go fast, fun, and productive: interfaces, reflection, and concurrency.

a simple “chat roulette” server to demonstrate when the function of the program changes dramatically, Go’s flexibility preserves the original design as it grows.

see how tricky concurrency problems can be solved gracefully with simple Go code.

dive deeper into Go’s concurrency primitives.

FAQ

Infinite Loop in fmt.Sprintf(e)

fmt.Sprint(e) will call e.Error() to convert the value e to a string. If the Error() method calls fmt.Sprint(e), then the program recurses until out of memory.

You can break the recursion by converting the e to a value without a String or Error method.

See:

Is it necessary to use nginx in golang web applications?

Some conclutions:

  • The Go HTTP server is very good, but you will need to reinvent the wheel to do some of these things (which is fine: it’s not meant to be everything to everyone).
  • Use Only Go is better than NGINX+GO. Only NGINX is better than only Go.
  • Another benefit of a frontend is to be able to sustain a Go server panic. The standard server never panics, since it do a recovery. However this is bad: the server should terminate, gracefully closing existing connections. With a frontend, you can keep 2 instances of your application (maybe managed by systemd). With caddy you can probably write a plugin to automate the task.

See Details:

How to test unexported functions?

In the _test.go file which has same package visiblity with target source .go file.

How to document package main with godoc

  • vi $GOPATH/src/golang.org/x/tools/godoc/server.go
1
2
- info.IsMain = pkgname == "main"
+ info.IsMain = false && pkgname == "main"
  • Build and install with go install golang.org/x/tools/cmd/godoc.

See: https://github.com/golang/go/issues/5727.

How to access internal data from another package for integration testing purposes?

There is no way in go.

The *_test.go files are compiled into the package only when running go test for that package.
See: https://github.com/golang/go/issues/10184

What the Appropriate Programming Paradigm for Go?

The point is that, you choose the paradigm that suits your situation. There is no single paradigm that works for all cases.

BTW, go supports OO in the unusual way.

package golang.org/x/tools/cmd/goimports: unrecognized import path “golang.org/x/tools/cmd/goimports”

When we using higher version of go and downloading go packages via go get golang.org/x/tools/xxx, this error may happened. And here’s the solution (take goimports for example):

1
2
3
4
mkdir -p $GOPATH/src/golang.org/x
cd $GOPATH/src/golang.org/x/
git clone https://github.com/golang/tools.git
go install golang.org/x/tools/cmd/goimports

go get: terminal prompts disabled?

1
echo 'export GIT_TERMINAL_PROMPT=1' >> ~/.zshrc

Wannex: OO?

Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

How do I get dynamic dispatch of methods?

The only way to have dynamically dispatched methods is through an interface. Methods on a struct or any other concrete type are always resolved statically.

Why is there no type inheritance?

Object-oriented programming, at least in the best-known languages, involves too much discussion of the relationships between types, relationships that often could be derived automatically. Go takes a different approach.

Rather than requiring the programmer to declare ahead of time that two types are related, in Go a type automatically satisfies any interface that specifies a subset of its methods. Besides reducing the bookkeeping, this approach has real advantages. Types can satisfy many interfaces at once, without the complexities of traditional multiple inheritance. Interfaces can be very lightweight—an interface with one or even zero methods can express a useful concept. Interfaces can be added after the fact if a new idea comes along or for testing—without annotating the original types. Because there are no explicit relationships between types and interfaces, there is no type hierarchy to manage or discuss.

It’s possible to use these ideas to construct something analogous to type-safe Unix pipes. For instance, see how fmt.Fprintf enables formatted printing to any output, not just a file, or how the bufio package can be completely separate from file I/O, or how the image packages generate compressed image files. All these ideas stem from a single interface (io.Writer) representing a single method (Write). And that’s only scratching the surface. Go’s interfaces have a profound influence on how programs are structured.

It takes some getting used to but this implicit style of type dependency is one of the most productive things about Go.

Why does Go not support overloading of methods and operators?

Method dispatch is simplified if it doesn’t need to do type matching as well. Experience with other languages told us that having a variety of methods with the same name but different signatures was occasionally useful but that it could also be confusing and fragile in practice. Matching only by name and requiring consistency in the types was a major simplifying decision in Go’s type system.

Regarding operator overloading, it seems more a convenience than an absolute requirement. Again, things are simpler without it.

Why doesn’t Go have “implements” declarations?

A Go type satisfies an interface by implementing the methods of that interface, nothing more. This property allows interfaces to be defined and used without having to modify existing code. It enables a kind of structural typing that promotes separation of concerns and improves code re-use, and makes it easier to build on patterns that emerge as the code develops. The semantics of interfaces is one of the main reasons for Go’s nimble, lightweight feel.

Go takes an unusual approach to object-oriented programming, allowing methods on any type, not just classes, but without any form of type-based inheritance like subclassing. This means there is no type hierarchy. This was an intentional design choice. Although type hierarchies have been used to build much successful software, it is our opinion that the model has been overused and that it is worth taking a step back.

All data types that implement these methods satisfy this interface implicitly; there is no implements declaration. That said, interface satisfaction is statically checked at compile time so despite this decoupling interfaces are type-safe.

Object-oriented programming provides a powerful insight: that the behavior of data can be generalized independently of the representation of that data. The model works best when the behavior (method set) is fixed, but once you subclass a type and add a method, the behaviors are no longer identical. If instead the set of behaviors is fixed, such as in Go’s statically defined interfaces, the uniformity of behavior enables data and programs to be composed uniformly, orthogonally, and safely.

We argue that this compositional style of system construction has been neglected by the languages that push for design by type hierarchy. Type hierarchies result in brittle code. The hierarchy must be designed early, often as the first step of designing the program, and early decisions can be difficult to change once the program is written. As a consequence, the model encourages early overdesign as the programmer tries to predict every possible use the software might require, adding layers of type and abstraction just in case. This is upside down. The way pieces of a system interact should adapt as it grows, not be fixed at the dawn of time.

Go takes an unusual approach to object-oriented programming, allowing methods on any type, not just classes, but without any form of type-based inheritance like subclassing. This means there is no type hierarchy. This was an intentional design choice. Although type hierarchies have been used to build much successful software, it is our opinion that the model has been overused and that it is worth taking a step back.

Go therefore encourages composition over inheritance, using simple, often one-method interfaces to define trivial behaviors that serve as clean, comprehensible boundaries between components.

Interface composition is a different style of programming, and people accustomed to type hierarchies need to adjust their thinking to do it well, but the result is an adaptability of design that is harder to achieve through type hierarchies.

Note too that the elimination of the type hierarchy also eliminates a form of dependency hierarchy. Interface satisfaction allows the program to grow organically without predetermined contracts. And it is a linear form of growth; a change to an interface affects only the immediate clients of that interface; there is no subtree to update. The lack of implements declarations disturbs some people but it enables programs to grow naturally, gracefully, and safely.

The designs are nothing like hierarchical, subtype-inherited methods. They are looser (even ad hoc), organic, decoupled, independent, and therefore scalable.

There’s this idea about “programming in the large” and somehow C++ and Java own that domain. I believe that’s just a historical accident, or perhaps an industrial accident. But the widely held belief is that it has something to do with object-oriented design.

I don’t buy that at all. Big software needs methodology to be sure, but not nearly as much as it needs strong dependency management and clean interface abstraction and superb documentation tools, none of which is served well by C++ (although Java does noticeably better).

References