Playground for my first steps in Go.
- Go programs are made out of packages
- the
main
method must be in the main package - inside of the import statement are the packages specified that should be imported
- the last element of the import path is the package name, by convention.
math/rand
imports the files frommath
that begin withpackage rand
- the last element of the import path is the package name, by convention.
- in Go, a name is exported if it begins with a capital letter, e.g.
math.Pi
- function definitions start with
func
followed by the function name, the parameter list and the return value- as opposed to C, the parameter name comes before the type, e.g.
x int
- here is why they choosed this syntax
- if two or more consecutive parameters share the same type, you can omit it from all but the last
- a function can return any number of values (like tuples in python)
- as opposed to C, the parameter name comes before the type, e.g.
- strings are enquoted by doublequotes
"
func add(a, b int) int {
return a+b
}
- the var statement declares a list of variables with the type last
- it is allowed on function and package level (global)
- examples:
var a, b bool
- initalizers can be used like this:
var a, b, c = true, false, "hej!"
- note that the type can be omitted if the initializer is present
- each variable from the initializer list can have a different type
- var statements can be factored into blocks, similar to the import statement, see basictypes.go for an example
- variables declared without an explicit initial value will be instantiated with their specific zero value
- inside a function the short assignment statement can be used:
a := 100
- type conversions can be done with
T(..)
, whereT
is the type and inside of the parantheses is the value to convert, e.g.float64(128)
- functions can also be assigned as variable values:
square := func(x int) int {
return x*x
}
- a closure is a function value that references variables from outside its body
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
- the inner function can access the
sum
variable from the enclosing function, even after the outer function has returned
- declared using the
const
keyword - can't be declared using the short assigment statement
:=
- constants can be character, string boolean or numeric values
- numeric-constants are high-precision values
- go has only one looping construct, the
for
loop - to emulate a
while
loop leave the pre and post statements empty:for ; x < y; {}
, you can even omit the semicolon:for x < y {}
- omit the loop conditions and you get an infinite loop:
for {}
for i := 0; i < 10; i++ {
sum += i
}
- C-like but without the parentheses:
if x < y {
x++
} else {
y++
}
- you can write a pre-statement before the if-statement
- variables declared in this pre-statement are only visible inside the scope of the if statement
if x := 10; x == 10 {
fmt.Println("It's only an example.")
}
- switch-case statements break automatically, unless you specfiy a falltrough statement (
default
case) - the evaluation order is from top to bottom
- a
switch
without condition is the same asswitch true
and can be used for long if-else chains:
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
- pointer declaration is C-like:
*T
, whereT
is the type of a value the pointer refers to - dereferencing the
&
generates an pointer of the value it refers to - there is no pointer arithmetic in Go
var p *int
i := 42
p = &i
fmt.Println(*p) // prints 42
- example use cases:
- avoid copying large structs to a function by passing a pointer to the struct to the function
- as the Go FAQ says, it's not call-by-reference because the pointer is copied, as well as every other argument which is passed to the function
- in-place modification, say you want to modify elements of a struct inside your function without returning it. I'm sure there is a valid use case for this, but I would consider it bad practice in most cases.
- avoid copying large structs to a function by passing a pointer to the struct to the function
struct literals
denotes a newly allocated struct- you can list a subset using the
Name:
syntax:Vertex{X: 3}
- the indirection through struct pointers is transparent
type Vertex struct {
X int
Y int
}
// instantiation
v := Vertex{1, 2}
v.X = 4
- an array of
n
elements with typeT
is declared like this[n]T
, e.g.[100]rune
- arrays can't be resized
- Go has an array slice syntax similar to pythons list slices:
p := []int{2, 3, 5, 7, 11, 13}
fmt.Println(p[1:5])
make([]T, l, c)
creates a slice with initial lengthl
and (optional) capicityc
len(s)
gives the length andcap(s)
the capacity of slices
- a
nil
slice (FP) has length and capacity0
- a slice can be appended with
append(s []T, vs ...T) []T
, where the first argument is a slice of typeT
and the following parameters areT
values - looping over a slice:
x = []int {2, 4, 8}
for i, v := range x {
// i = index
// v = value of x[i]
}
- you can skip a loop variable when you assign
_
to it, like in Python:for _, v := range x {}
- map declaration looks like this:
map[T_key]T_value
, e.g.map[string]uint64
- maps have to be created with
make(map_declaration)
before using them - you can use map literals to initalize a map like this:
var m2 = map[string]uint64{
"foo": 42,
"bar": 314,
}
- there must be a trailing comma behind the last value!
- insert
m[key] = elem
- get
elem = m[key]
delete(m, key)
- check if a key is present:
elem, ok = m[key]
, whereok
istrue
ifkey
is present in mapm
, otherwiseok
is false and theelem
is the zero value of its type
- there is no class construct in Go
- but, you can define methods on [struct] types, which is pratically the same (see OOP with Ansi-C (pdf)) apart from the access modifiers
- the declaration looks like that from a function with an additional Method Receiver between the
func
keyword and the function name - you can call the method like you can access struct elements:
foo.F()
type Vertex struct {
X, Y float64
}
// func MethodReceiver MethodName(Params) ReturnValue
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
- you can declare method on any type from your package, but not on others
Methods with Pointer Receiver
- two main reasons for using pointer receivers:
- call-by-reference, as default the method gets a copy of the struct (call-by-value)
- modifying the method receiver in-place. You should now why you want to do this, because it's the explicit usage of side effects
- you can't define the same method name for pointer and value type, see the example below
type Decimal struct {
X float64
}
func (v Decimal) Double() float64 {
return 2 * v.X
}
func (v *Decimal) DoublePR() {
v.X = 2 * v.X
}
...
v := Decimal{3.14}
// call-by-value
fmt.Println(v, v.Double())
// use the pointer Receiver
v.DoublePR()
// DoublePR() has mutated v in-place
fmt.Println(v)
prints out:
{3.14} 6.28
{6.28}
- an interface type is defined by a set of methods
- a type implements an interface by implementing its methods
- interfaces are satisfied implicitly. There is no explicit implements keyword (like in Java), therefore an interface is satisfied if the type implements its methods.
- the equivalent of Java's
toString()
method is theString()
method from theStringer
interface:
type Stringer interface {
String() string
}
Errors (Exceptions in Go)
errors
is a built-in interface (similar toStringer
)- error checking is done by validating if an error value is
nil
(Go's null type):
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
}
- the http package serves HTTP requests using any value that implements
http.Handler
- those values have to implement
ServeHTTP(w http.ResponseWriter, r *http.Request)
- http Handler example
func (s Struct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, fmt.Sprintf("%s%s %s\n", s.Greeting, s.Punct, s.Who))
}
- a goroutine is a lightweight thread
- the name is wordplay of coroutines
- goroutines run in the same address space, so they have access to the shared memory → need of synchronization/locks
- a goroutine is started with
go f()
, wheref
is an arbitrary function- f's arguments will be evaluated in the current goroutine
- f will be executed in the new goroutine
- a channel is a types pipe (like pipes from the shell)
- a channel must be created before use:
ch := make(chan type, bufferlen)
. Thebufferlen
parameter is optional. - you can send and receive values from a channel using the
<-
operator:- send
ch <- v
- receive
v := <-ch
- send
- send and receive on channels is blocking (until the other side is ready) by default
- a buffered channel blocks only when the buffer is full
- channels can be closed to indicate that no more values will be send
- only senders should close channels!
- you can check if the second return value of a receive is
false
, then the channel was closed:v, ok := <-ch
Loops until the channel was closed
c := make(chan type)
//...
for v := range c {
// ...
}
- the
select
statement is likeswitch-case
for channels - if mutliple channels are ready at once, a random channel is chosen
- the
default
case is run if no other channel is ready (can be used for non-blocking send/receive)
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
- the
defer
statement defers the execution of a function until the surrounding function returns - deferred function calls are pushed on a stack and are executed in LIFO order
- more on defer
- to build & run a Go file in one step use
go run file.go
- Go files can be formatted automatically using the
gofmt
tool. On default the formatted code is written tostdout
, to overwrite the source file usegofmt -w file.go
. - the execution environment of a compiled program is deterministic, thus a random generator for example has to be seeded, otherwise it will deliver the same number on every run of the program
- go-koans lets you learn Go by fixing test cases. Sounds boring but instead it's quite fun to fix it!