Notes on the unique stuff about go, in the context of other languages that I already know.
- Root dir
go srcandbininside 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.