Intro

The content of this article is mainly compiled from Tour of Go, with some personal insights added.


What is Go

Go is a statically typed, compiled high-level programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. It is syntactically similar to C, but also has memory safety, garbage collection, structural typing, and CSP-style concurrency. [1]


Packages, Variables and functions

Functions Continued

When two or more consecutive named function parameters share a type, you can omit the type from all but the last.

1
2
3
x int, y int
// is equal to
x,y int

Function Multiple Results

A function can return any number of results.

1
2
3
4
// two string type returns
func swap(x, y string) (string, string) {
return y, x
}

Naked Return

A return statement without arguments returns the named return values. This is known as a "naked" return.

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

import "fmt"

func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x

// Naked return x,y here
return
}

func main() {
fmt.Println(split(17))
}

// Output is 7 10

Declare Variables

1
2
3
4
5
6
7
8
9
10

// For Package-Level Variable Declaration.
var c, python, java bool

// Short declaration for local variables
c := true

// constant

const pi = 3.14

Basic Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
// represents a Unicode code point

float32 float64

complex64 complex128

Not Explicit Initial Value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}

// Output is:
// 0 0 false ""

Type conversions

The expression converts the value to the type .

1
2
3
i := 42
f := float64(i)
u := uint(f)


Flow Control

For loop

Go has only one looping construct, the for loop.

1
2
3
for i := 0; i < 10; i++ {
// code
}

The init and post statements are optional.

1
2
3
4
5
6
7
8
9
10
11
sum := 1
// Like while loop in other programming language
for ; sum < 1000; {
sum += sum
}

// or
sum := 1
for sum < 1000 {
sum += sum
}
Infinite loop

1
2
for {
}

If

Declare a local variable in if statement (also available in elseif else block)

1
2
3
if v := math.Pow(x, n); v < lim {
return v
}

Switch

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.

1
2
3
4
5
6
7
8
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
//none of the other case conditions are met.
fmt.Printf("%s.\n", os)

Switch without a condition is the same as switch true. This construct can be a clean way to write long if-then-else chains.

1
2
3
4
5
6
7
8
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}

Defer

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
defer fmt.Println("world")

fmt.Println("hello")
}

/*Output is:

hello
world
*/

Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.

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

import "fmt"

func main() {
fmt.Println("counting")

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

fmt.Println("done")
}

/*Output is
2
1
0*/

Structs, slices and maps

Pointers

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

package main

import "fmt"

func main() {


i, j := 42, 2701

//Declare a pointer p
var p *int

p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i

p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j

/*
Output is:
42
21
73 (2701/37)
*/
}

Struct

Accessing using a dot.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

type Vertex struct {
X int
Y int
}

func main() {
v := Vertex{1, 2} //construct
v.X = 4 //assess
fmt.Println(v.X)
}

Pointers to structs

(*p).X can be written as p.X

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ackage main

import "fmt"

type Vertex struct {
X int
Y int
}

func main() {
v := Vertex{1, 2}
p := &v
(*p).X = 1e9 // p.X = 1e9 is also OK
fmt.Println(v)
}


Struct Literals

The default value is 0 if you don't assign a initial value in construction.

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
package main

import "fmt"

type Vertex struct {
X, Y int
}

var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X:1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)

func main() {
fmt.Println(v1, p, v2, v3)
}

/*
Output is:
{1 2} &{1 2} {1 0} {0 0}

*/

Array

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

import "fmt"

func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)

primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}

/*
Output is:
Hello World
[Hello World]
[2 3 5 7 11 13]
*/

Slices

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

package main

import "fmt"

func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}

var s []int = primes[1:4]
fmt.Println(s)
}

/*
Output is:
[3 5 7]
*/

A slice does not store any data, it just describes a section of an underlying array. Changing the elements of a slice modifies the corresponding elements of its underlying array.

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
package main

import "fmt"

func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)

a := names[0:2]
b := names[1:3]
fmt.Println(a, b)

b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}

/* Output is
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]

*/

When slicing, you may omit the high or low bounds to use their defaults instead.

1
2
3
4
5
6
7
8
var a [10]int

// These slice expressions are equivalent:
a[0:10]
a[:10]
a[0:]
a[:]

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

import "fmt"

func main() {
s := []int{2, 3, 5, 7, 11, 13}

s = s[1:4]
fmt.Println(s)

s = s[:2]
fmt.Println(s)

s = s[1:]
fmt.Println(s)
}

/*
Output is
[3 5 7]
[3 5]
[5]
*/

Length & Capacity in a slice:

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
package main

import "fmt"

func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)

// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)

// Extend its length.
s = s[:4]
printSlice(s)

// Drop its first two values.
s = s[2:]
printSlice(s)
}

func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}


/*
Output is
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7] //capacity equal to the number of elements in the underlying array from the index of the first element of the slice to the end of the array.
*/

Nil slices: A nil slice has a length and capacity of 0 and has no underlying array.

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

import "fmt"

func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}

/*
Output is
[] 0 0
nil!
*/

Creating a slice with make and this is the way to create a dynamically-sized array

1
2
a := make([]int, 5)  // a len=5 cap=5 [0 0 0 0 0]
b := make([]int, 0, 5) // b len=0 cap=5 []

Appending to a slice. If the backing array 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.

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
package main

import "fmt"

func main() {
var s []int
printSlice(s)

// append works on nil slices.
s = append(s, 0)
printSlice(s)

// The slice grows as needed.
s = append(s, 1)
printSlice(s)

// We can add more than one element at a time.
s = append(s, 2, 3, 4)
printSlice(s)
}

func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

/* Output is
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4] // cap=6, because more memory space has been allocated.
*/

Range

1
2
3
4
5
6
7
8
9
10
11
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v) // i for index, v for value
}

//

for _, value := range pow { //only for value, _ for skipping
fmt.Printf("%d\n", value)
}

Map

A map maps keys to values. The zero value of a map is nil.

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"

type Vertex struct {
Lat, Long float64
}

var m map[string]Vertex

func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}

/*Output is
{40.68433 -74.39967}
*/

Map literals

1
2
3
4
5
6
7
8
9
10
11
12
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
}
}

//or

var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967}
}

Map operations

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
package main

import "fmt"

func main() {
m := make(map[string]int)

m["Answer"] = 42
fmt.Println("The value:", m["Answer"])

m["Answer"] = 48
fmt.Println("The value:", m["Answer"])

delete(m, "Answer") //delete the k,v
fmt.Println("The value:", m["Answer"])

v, ok := m["Answer"] //check the key "Answer" is present or not
fmt.Println("The value:", v, "Present?", ok)
}

/*
Output is
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
*/

Function values

Functions are values too. They can be passed around just like other values.

Function values may be used as function arguments and return values.

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
package main

import (
"fmt"
"math"
)

/*
func compute(fn func(float64, float64) float64) float64:
This function compute takes a function fn as an argument.
This function fn is expected to take two float64 arguments and return a float64.
The compute function itself returns a float64.
*/
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}

func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))

fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}


/* Output is
13
5
81
*/

Function closures

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
package main

import "fmt"

/*
func adder() func(int) int: This function adder returns another function that takes an integer as input and returns an integer.
*/
func adder() func(int) int {
sum := 0
return func(x int) int { //anonymous function, no function name
sum += x
return sum
}
}

func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}

/*Output is
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
*/

Methods and interfaces

Methods

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

A method is a function with a special receiver argument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Vertex struct {
X, Y float64
}

// the Abs() method has a receiver of type Vertex named v.
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}

/*Output is
25
*/

Pointer receivers

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
package main

import (
"fmt"
"math"
)

type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
//Using a pointer receiver is necessary if you want to modify the original content of a struct instance in Go.
//Otherwise, with a value receiver, you're merely operating on a copy.
v.X = v.X * f
v.Y = v.Y * f
}

func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}

/*Output is
50
*/

Value receiver VS. Pointer receiver Pros of Pointer receiver: * Methods will directly modify the original value that its receiver points to * Avoiding copying value on each method call (efficiency)

All methods in a given type should use same receiver

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
package main

import (
"fmt"
"math"
)

type Abser interface {
Abs() float64
}

func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}

a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser

// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = &v //Revised from a = v, otherwise it will abort

fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

type Vertex struct {
X, Y float64
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

/*
Output is:
5
*/

A type implements an interface by implementing its methods. There is no explicit declaration of intent, no "implements" keyword.

Under the hood, interface values can be thought of as a tuple of a value and a concrete type:

(value, type)

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
package main

import (
"fmt"
"math"
)

type I interface {
M()
}

type T struct {
S string
}

func (t *T) M() {
fmt.Println(t.S)
}

type F float64

func (f F) M() {
fmt.Println(f)
}

func main() {
var i I

i = &T{"Hello"}
describe(i)
i.M()

i = F(math.Pi)
describe(i)
i.M()
}

func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}

/*
Output is:
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793
*/

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.

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
package main

import "fmt"

func main() {
var i interface{}
describe(i)

i = 42
describe(i)

i = "hello"
describe(i)
}

func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}

/* Output is:
(<nil>, <nil>)
(42, int)
(hello, string)

*/

Type assertions

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.

1
t, ok := i.(T)

If holds a , then will be the underlying value and will be true.

If not, ok will be false and will be the zero value of type , and no panic occurs.

Type switches

A type switch is a construct that permits several type assertions in series.

1
2
3
4
5
6
7
8
9
//Usage
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
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
//Example

package main

import "fmt"

func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}

func main() {
do(21)
do("hello")
do(true)
}

/* Output is
Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!
*/

Stringers

1
2
3
4
//Defined by fmt package
type Stringer interface {
String() string
}

Define a string() function to get the desired output when using the print function.

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

import "fmt"

type Person struct {
Name string
Age int
}

//string() was defined here
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}

/* Output is
Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
*/

Errors

The error type is a built-in interface similar to fmt.Stringer:

1
2
3
type error interface {
Error() string
}

Usage

1
2
3
4
5
6
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)

Readers

Usage

1
2
// given byte slice with data and returns the number of bytes populated and an error value
func (T) Read(b []byte) (n int, err error)

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
package main

import (
"fmt"
"io"
"strings"
)

func main() {
r := strings.NewReader("Hello, Reader!")

b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}

/*
Output is
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""
*/

Generics

Type parameters

Usage

1
2
3
/* This declaration means that s is a slice of any type T that fulfills the built-in constraint comparable. x is also a value of the same type.
comparable is a useful constraint that makes it possible to use the == and != operators on values of the type.*/
func Index[T comparable](s []T, x T) int

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
package main

import "fmt"

// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
// v and x are type T, which has the comparable
// constraint, so we can use == here.
if v == x {
return i
}
}
return -1
}

func main() {
// Index works on a slice of ints
si := []int{10, 20, 15, -10}
fmt.Println(Index(si, 15))

// Index also works on a slice of strings
ss := []string{"foo", "bar", "baz"}
fmt.Println(Index(ss, "hello"))
}

/*
Output is
2
-1
*/

Generic types

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
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
package main

import "fmt"

type Number interface {
int64 | float64
}

func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}

// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}

fmt.Printf("Non-Generic Sums: %v and %v\n",
SumInts(ints),
SumFloats(floats))

fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))

fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))

fmt.Printf("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
}

// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}

// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}

// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}

// SumNumbers sums the values of map m. Its supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}

/*Output is
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
Generic Sums with Constraint: 46 and 62.97
*/

Concurrency

Goroutine

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

1
go f(x, y, z)

Channel

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

1
2
3
4
5
6
//Usage
ch := make(chan int)
// ch := make(chan int, 100) //Buffered channel
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and
// assign value to v.
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
//Example

package main

import "fmt"

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

func main() {
s := []int{7, 2, 8, -9, 4, 0}

c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c

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

/* Output is
-5 17 12
*/

Range and close

1
2
3
// Check whether the channel was closed or not.
v, ok := <-ch
//ok is false if there are no more values to receive and the channel is closed.

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
package main

import (
"fmt"
)

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

func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c { //The loop for i := range c receives values from the channel repeatedly until it is closed.
fmt.Println(i)
}
}

/* Output is
0
1
1
2
3
5
8
13
21
34
*/

Select

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
36
37
38
39
40
41
42
43
44
45
//example
package main

import "fmt"

func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}

func main() {
c := make(chan int)
quit := make(chan int)

go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}

/*
Output is
0
1
1
2
3
5
8
13
21
34
quit
*/

Default selection

The default case in a select is run if no other case is ready.

1
2
3
4
5
6
7
//Usage
select {
case i := <-c:
// use i
default:
// receiving from c would block
}
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
// Example

package main

import (
"fmt"
"time"
)

func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}

/*
Output is
.
.
tick.
.
.
.
tick.
.
.
tick.
.
.
tick.
.
tick.
BOOM!*/


snyc.Mutex

Go's standard library provides mutual exclusion with sync.Mutex and its two methods:

  • Lock
  • Unlock
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
package main

import (
"fmt"
"sync"
"time"
)

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

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mu.Unlock()
return c.v[key]
}

func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}

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

/*Output is
1000
*/



Supplementary Content

Array vs. Slice

Fixed Size vs. Dynamic Size:

  • Array: Arrays have a fixed size that cannot be changed after declaration. Once you define the size of an array, it cannot be resized.

  • Slice: Slices, on the other hand, are dynamic and can grow or shrink. They are built on top of arrays and provide a more flexible way of working with sequences of data.

Underlying Data:

  • Array: Each element of an array is a distinct value stored directly in memory.

  • Slice: Slices hold a reference to an underlying array. This means that modifying a slice can affect the original array and any other slices that share the same underlying array.

Passing to Functions:

  • Array: When you pass an array to a function, you are actually passing a copy of the array. Changes made to the array inside the function do not affect the original array.

  • Slice: Slices are references to the underlying array, so changes made to a slice inside a function will affect the original slice.

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

package main

import ("fmt")

func main() {
// Array
arr := [3]int{1, 2, 3}
fmt.Println("Array before:", arr)
modifyArray(arr)
fmt.Println("Array after:", arr)

// Slice
slice := []int{1, 2, 3}
fmt.Println("Slice before:", slice)
modifySlice(slice)
fmt.Println("Slice after:", slice)
}

func modifyArray(arr [3]int) {
arr[0] = 100
}

func modifySlice(slice []int) {
slice[0] = 100
slice = append(slice, 4) // This doesn't affect the original slice
}

/*
Output is
Array before: [1 2 3]
Array after: [1 2 3]
Slice before: [1 2 3]
Slice after: [100 2 3]
*/

Var vs. Make vs. New

Useful material

var:

  • var is used to declare variables.
  • It allocates memory for the variable and allows you to specify its type.
  • It's used for general variable (outside function) declarations.

Make:

  • Initialize slice map channel

New:

  • Create a pointer
  • new is a built-in function in Go that allocates memory for a new value of the specified type and returns a pointer to it.

Value types vs. Reference types

Value Types:

Value types hold their data directly and each variable has its own copy of the data. When you assign a value type to another variable or pass it to a function, a copy of the value is made.

Types like int, float, bool, string, and struct

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

import "fmt"

func main() {
a := 10
b := a // a copy of the value of a is assigned to b
a = 20
fmt.Println(a) // prints 20
fmt.Println(b) // prints 10
}

/*
Output is:
20
10
*/

Reference Types:

Reference types hold a reference (memory address) to the underlying data. When you assign a reference type to another variable or pass it to a function, you're passing a reference to the same underlying data.

Types like slices, maps, and channels

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

import "fmt"

func main() {
slice1 := []int{1, 2, 3}
slice2 := slice1 // both slice1 and slice2 reference the same underlying array
slice1[0] = 10
fmt.Println(slice1) // prints [10 2 3]
fmt.Println(slice2) // prints [10 2 3]
}

output is:
/*
[10 2 3]
[10 2 3]
*/

Reference

  • [1] Wikipedia contributors. (2023, November 23). Go (programming language). In Wikipedia, The Free Encyclopedia. Retrieved 11:32, December 5, 2023, from https://en.wikipedia.org/w/index.php?title=Go_(programming_language)&oldid=1186536358
  • [2] Tour of Go
  • [3] Tutorial: Getting started with generics