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 | x int, y int |
Function Multiple Results
A function can return any number of results.
1 | // two string type returns |
Naked Return
A return statement without arguments returns the named return values. This is known as a "naked" return.
1 | package main |
Declare Variables
1 |
|
Basic Types
1 | bool |
Not Explicit Initial Value
1 | package main |
Type conversions
The expression 1
2
3i := 42
f := float64(i)
u := uint(f)
Flow Control
For loop
Go has only one looping construct, the for loop.
1 | for i := 0; i < 10; i++ { |
The init and post statements are optional. 1
2
3
4
5
6
7
8
9
10
11sum := 1
// Like while loop in other programming language
for ; sum < 1000; {
sum += sum
}
// or
sum := 1
for sum < 1000 {
sum += sum
}
1 | for { |
If
Declare a local variable in if statement (also available in elseif else block)
1 | if v := math.Pow(x, n); v < lim { |
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 | switch os := runtime.GOOS; 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 | switch { |
Defer
A defer statement defers the execution of a function until the surrounding function returns.
1 | package main |
Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.
1 | package main |
Structs, slices and maps
Pointers
1 |
|
Struct
Accessing using a dot.
1 | package main |
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
17ackage 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 | package main |
Array
1 | package main |
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 |
|
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 | package main |
When slicing, you may omit the high or low bounds to use their defaults instead. 1
2
3
4
5
6
7
8var a [10]int
// These slice expressions are equivalent:
a[0:10]
a[:10]
a[0:]
a[:]
1 | package main |
Length & Capacity in a slice:
1 | package main |
Nil slices: A nil slice has a length and capacity of 0 and has no underlying array.
1 | package main |
Creating a slice with make and this is the way to create a dynamically-sized array
1 | a := make([]int, 5) // a len=5 cap=5 [0 0 0 0 0] |
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 | package main |
Range
1 | var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} |
Map
A map maps keys to values. The zero value of a map is nil.
1 | package main |
Map literals
1 | var m = map[string]Vertex{ |
Map operations
1 | package main |
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 | package main |
Function closures
1 | package main |
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 | type Vertex struct { |
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
32package 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 | package main |
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 | package main |
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 | package main |
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
If not, ok will be false and
Type switches
A type switch is a construct that permits several type assertions in series.
1 | //Usage |
1 | //Example |
Stringers
1 | //Defined by fmt package |
Define a string() function to get the desired output when using the print function.
1 | package main |
Errors
The error type is a built-in interface similar to fmt.Stringer:
1 | type error interface { |
Usage
1 | i, err := strconv.Atoi("42") |
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 | package main |
Generics
Type parameters
Usage
1 | /* 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. |
Example
1 | package main |
Generic types
Example
1 | package main |
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 | //Usage |
1 | //Example |
Range and close
1 | // Check whether the channel was closed or not. |
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
35package 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 | //example |
Default selection
The default case in a select is run if no other case is ready.
1 | //Usage |
1 | // Example |
snyc.Mutex
Go's standard library provides mutual exclusion with sync.Mutex and its two methods:
- Lock
- Unlock
1 | package main |
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 |
|
Var vs. Make vs. New
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 | package main |
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 | package main |
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