Notes on the unique stuff about go, in the context of other languages that I already know.
- Root dir
go
src
andbin
inside the project- unique project/package name
The first statement in a go source file must be
package packagenamehere
Most stuff feels overall quite C-like.
Slices are a bit different. Constrained by type
of their content, but not by length as a standard array
.
Create an empty slice with non-zero length, use make
.
Make a slice of length 3 (zero-valued).
s := make([]string, 3)
append
returns a new slice with 1 or more values. Immutable, which is nice compared to Javascript's array.append.
s = append(s, "d")
s = append(s, "e")
slice
operator similar to Python.
s[1:3]
Initialize values for a slice
t = []string{"go", "is", "cool"}
Similar to a dict or hash table.
String to int map.
m := map(map[string]int)
delete values with:
delete(m, "mykey")
optional second return value indicates whether the key was present in the object
value, didKeyExist = m["mykey]
Sort of a mapper function for various data structure. Or a for in
.
nums := []int{2 ,3 4}
sum := 0
for _, num := range nums {
sum += nums
}
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
// byte index, char (rune)
for i, c := range "go" {
fmt.Println(i, c)
}
A bit like python tuples.
func inty(a, b int) (int, int) {
return a, b
}
Just like the ...rest operator in JS.
func spready(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
Go supports anonymous functions, just like Javascript.
func factory() func() int {
i := 0
return func() int {
i++
return i
}
}
Pointers like C.
func passedByValue(value int) {
# value is passed as a copy of the underlying value and here we operate on that copy only
value = 0
}
func passedByReference(reference *int) {
# here we mutate the underlying value by assigning a new int at the referenced address
*reference = 0
}
Underlying memory addresses:
func main() {
i := 0
fmt.Println(i)
passedByValue(i)
fmt.Println(i)
passedByReference(&i)
fmt.Println(i)
fmt.Println(&i)
}
Like C# structs.
type person {
name string
age int
}
func NewPerson(name string) *person {
p := person{name: name}
p.age = 42
return &p
}
func main() {
This syntax creates a new struct.
fmt.Println(person{"Bob", 20})
fmt.Println(person{name: "Alice", age: 30})
fmt.Println(person{name: "Fred"})
fmt.Println(&person{name: "Ann", age: 40})
fmt.Println(NewPerson("Jon"))
s := person{name: "Sean", age: 50}
fmt.Println(s.name)
sp := &s
fmt.Println(sp.age)
sp.age = 51
fmt.Println(sp.age)
}
It's idiomatic to initiate a new struct with a factory function.
You can also add methods on structs.
type rect struct {
width, height int
}
// Pointer receiver type
func (r *rect) area() int {
return r.width * r.height
}
// Value receiver type
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
Go seems to know that the method is on the struct since the struct is a parameter. The method is named in the function call defined at the top i.e. area()
.
Go also magically converts between values and pointers for method calls. You can control the behavior by specifying a pointer receiver type to avoid copying the struct on method calls or to allow the method to mutate the underlying values.
A "named collection of method signatures".
type geometry interface {
area() float64
perim() float64
}
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
func (r rect) area() float64 {
return r.width * r.height
}
// Value receiver type
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
func measure(g geometry) {
fmt.Println(g.area())
fmt.Println(g.perim())
}
Go communicates errors with explicit return values. Different than exceptions in other languages.
import (
"errors"
)
func errorfunc(arg int) (int, error) {
if arg == 42 {
return -1, errors.new("An unholy number")
}
return arg + 3, nil
}
func main() {
if r, e := errorFunc(42); e != nil {
// FAIL
} else {
// OK
}
}
Goroutine is "a lightweight thread of execution".
Sort of like a Javascript promise except actually concurrent (since JS is forever single-threaded).
func f(arg int) int {
return arg
}
f(4) // called synchronously
go f(5) // called asynchronously
// with an anonymous function
go func(msg string) {
fmt.Println(msg)
}("GOING ASYNC ANON")
timer
Basically setTimeout from JS.
import "time"
function timeMe() {
// returns a channel that will be notified at that time (wait two seconds)
timer1 := time.NewTimer(2 * time.Second)
timer1.Stop() // cancel timer
// sleep
time.Sleep()
}
ticker
Basically setInterval from JS
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(500 * time.Millisecond)
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
fmt.Println("Tick at", t)
}
}
}()
time.Sleep(1600 * time.Millisecond)
ticker.Stop()
done <- true
fmt.Println("Ticker stopped")
}
Sort of like throw
from JS but it will throw a non-zero exit code and provide a stack trace to stderr.
package main
import "os"
func main() {
panic("a problem")
_, err := os.Create("tmp/file")
if err != nil {
panic(err)
}
}
Like a finally
in JS. Except you defer a function call.
You have to check for errors even in a deferred function.
For example, you defer
the cleanup of a file.
func main() {
f := createFile("/tmp/defer.txt")
defer closeFile(f)
writeFile(f)
}
os.Exit(3)
to exit with an explicit exit code. Return values from main don't count a la C.