// go supports both inline and
/* multiline comments */
/*
the `go fmt` command reads go code and uses it to build documentation
for a go project. Some file in each package should have a doc comment
at the start of it to describe the package. Similarly each public struct
or function should also have a documentation comment.
*/
// package declarations come before any imports.
package main
/*
Each package is a collection of files in the same directory.
A module is defined as a collection of packages. By design a
package has access to any identifiers (structs, variables etc.)
defined in any of the files within it.
The namespace for a module is determined by the `go.mod` file in
the root of the project. This file is generated by the `go mod init`
command.
For example, a directory layout like:
| projectFoo
| go.mod
| main.go
| foos/
| foo_a.go
| foo_b.go
that has a module, defined in `go.mod`, of "github.com/mohkale/projectFoo"
uses `package projectFoo` in the root of the project (such as in `main.go`).
Similairly the `foos` subpackage has a package of `foos` and has a import
namespace of "github.com/mohkale/projectFoo/foos"
NOTE that the namespace here not only relates to the import path for a package
but also how that package is to be downloaded if it isn't installed.
NOTE the special package `main` is used to tell the go compiler to create an
executable instead of a module.
*/
// imports for code go at the top of the file
import "fmt"
// you can wrap the imports in parentheses to specify multiple imports
import (
"io"
"sys"
)
// you can also specify a prefix for the imported namespace
import shortAndSweet "github.com/mohkale/someReallyDumbLongProjectName"
// or remove the need for a prefix altogether
import . "net"
// go has a rich set of builtin types, including:
// byte, rune (unicode code point), string
// int, uint, floats, complex
// types are specified after their identifiers
var foo string
foo = "hello world"
var bar int
bar = 1234
// go can detect the type using the assigned value.
var fooBar = 5 // type(fooBar) is int
// if you omit a default value, each variable will be assigned its
// 'zero' value. For numbers this is 0, for strings the empty string.
var baz int // #=> 0
// you can defer the type definition to the last identifier, in
// a list of them, the type will then apply to all of them.
var bag, bam, boom float64
// go also supports pointers as a way to implement pass-by-reference.
var bagPointer *float64 = &bag
/*
for the most part you can use pointers as the underlying value, there's
no special syntax (like c) to access the fields of a struct pointer.
You can also dereference a pointer using the * operator.
*/
var bagValue = *bagPointer // copy of bag
// functions specify return values after parameter lists.
func fooBar(foo string, bar int) bool {
fmt.Println("f:", foo, "b:", bar)
return true
}
// you can create a varidic argument for an func by adding ellipses.
func fooBarBaz(foo string, bars ...int) {
fmt.Println("f:", foo, "b:", bars)
}
fooBarBaz("hello world", 1, 2, 3, 4, 5, 6)
// #=> f: hello world b: [1 2 3 4 5 6]
// you can define annonymous functions and pass them as first class objects.
var myFunc func(int) int = func(x int) int {
return x + 10
}
fmt.Println(myFunc(5)) // #=> 15
/*
go has extremely simple scoping rules, functions and structs that
begin with a capital letter are accessible outside of the current
package, otherwise they're private (only other package members can)
access them.
NOTE as a consequence this means any functions or structs you import
from another package are always called with capital letters.
*/
fmt.Println("foo")
fmt.Sprintf("foo: %s", "hello")
// you declare an array by prefixing the type with the capacity.
var shoppingList [3]string
shoppingList[0] = "apples"
shoppingList[1] = "oranges"
shoppingList[2] = "pears"
// you can also use a composite literal to specify the fields in 1 expression.
var inlineShoppingList = [3]string{"apples","oranges","pears"}
/*
However you'll rarely be using arrays by themselves. Go is implemented
as a pass by value language, therefore arrays are copied in their entirety
when passed to functions.
To get around this, you'll more often than not be using slices. A slice is
a subsection of an array that's defined with both a length and a capacity.
The length is how many items the slice has, the capacity is how many the
underlying array can hold if need be.
*/
var shoppingSlice []string = shoppingList[:]
// NOTE the type is the same except there's no range specified in [].
fmt.Println(len(shoppingSlice)) // #=> 3
fmt.Println(cap(shoppingSlice)) // #=> 3
// You can specify an upper and lower bound for a slice just like python.
var shoppingSlice2 []string = shoppingList[0:1]
fmt.Println(len(shoppingSlice2)) // #=> 1
fmt.Println(cap(shoppingSlice2)) // #=> 3
// You can reslice a slice and delete elements from a slice by just
// defining a newer slice without some leading elements.
shoppingSlice = shoppingSlice[1:] // #=> ["oranges", "pears"]
fmt.Println(len(shoppingSlice2)) // #=> 2
fmt.Println(cap(shoppingSlice2)) // #=> 2
/*
The array declarations we've encountered so far are defined at compile
time. You can't declare an array like this:
var capacity = 10
var myArray [capacity]string
Because there won't be any compile time bound checking. Instead you can
allocate an array on the heap like so.
*/
var capacity = 10
var length = 5
// this create a slice of type string that can take upto 10 strings and is
// initialised with 5 empty strings. If capacity isn't provided, it equals
// length.
var myArray = make([]string, length, capacity)
// You can declare a hash type, mapping keys to values like this.
shopPrices := map[string]float32 {
"apple": 1.234,
"oranges": 2.567,
"pears": 3.890,
}
// NOTE the := syntax is an alternative to the var= form, for variable
// initialisation.
fmt.Println(shopePrices["apple"])
// To check for the presence of an element in a map, you can accept another
// value to guarantee it's ok.
var applePrice, ok = shopPrices["apple"];
fmt.Println(applePrice, ok) // #=> 1.234 true
// NOTE use the _ identifier for the value when the value doesn't matter.
var tangerinePrice, ok = shopPrices["tangerine"]
fmt.Println(tangerinePrice, ok) // #=> 0 false
// the builtin `delete` function removes an element from a map.
delete(shopPrices, "apple")
var applePrice, ok = shopPrices["apple"];
fmt.Println(applePrice, ok) // #=> 0 false
/*
WARN shopPrices was declared at compile time, you can't add new items to
it at runtime, but you can redefine values of defined types. To add new
keys you have to define the map at runtime.
*/
var dynamicShopPrices = make(map[string]float32)
dynamicShopPrices["apple"] = 1.234
dynamicShopPrices["oranges"] = 2.567
dynamicShopPrices["pears"] = 3.890
// NOTE `dynamicShopPrices` will grow to accomodate as many keys as needed.
/*
if statements in go work like c, except parentheses are optional and you can
assign an initial value before the condition check. So statements like the
following are ok 😄.
*/
if pearPrices, ok := shopPrices["pears"]; ok {
fmt.Println("pears cost:", pearPrices)
}
/*
go lacks a while loop or do while loop. it only has for loops. However it lets
you use for loops like while loops.
*/
// a for loop without any parameters loops forever
for {
fmt.Println("hello world")
break
}
// a for loop with a single condition is like a while loop
var ok = false
for !ok {
fmt.Println("not ok yet")
ok = true
}
fmt.Println("ok")
// the classical c style for loop
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// the range keyword let's you enumerate over slices and maps.
var myNums = []string{"hello", "world", "friend", "!"}
for i, char := range myNums {
fmt.Println(i, char)
}
scores := map[string]int {
"me": 1_000_000,
"someone": 5_000,
"no-one": 0,
}
for key, val := range scores {
fmt.Println(key, ":", val)
}
// structs are how you define complex types.
type Foo struct {
string string
value int
}
var myFoo = Foo{"hello",1}
// The builtin `new` function allocates the memory for a struct
// and then returns a pointer to it. Our previous definition could
// also have been written as:
var myFooNew = *new(Foo)
myFooNew.string = "hello"
myFooNew.value = 1
// you can also define a function on a struct using the reciever syntax.
// a functions reciever is placed before the function name.
func (f Foo) printSelf() {
fmt.Printf("%s@%p\n", f, &f)
}
// you can call this function through an instance of the struct.
myFoo.printSelf() // #=> {hello %!s(int=1)}@0xc00000c080
myFoo.printSelf() // #=> {hello %!s(int=1)}@0xc00000c0c0
// NOTE the adress of the reciever changes across different calls, this is
// because our struct is being copied on each invocation of `printSelf`.
// a pointer reciever won't have this issue.
func (f *Foo) printSelfNoCopy() {
fmt.Printf("%s@%p\n", *f, f)
}
myFoo.printSelfNoCopy() // #=> {hello %!s(int=1)}@0xc00000c080
myFoo.printSelfNoCopy() // #=> {hello %!s(int=1)}@0xc00000c080
// an interface is like a struct except it specifies the recievers
// a type must have to be supplied in place of the interface.
type Fooer interface {
printSelf()
}
// WARN interface recievers cannot be pointer recievers. A function
// with a reciever of *MyType is distinct from one of MyType.
// See https://stackoverflow.com/questions/40823315/x-does-not-implement-y-method-has-a-pointer-receiver
func runPrintSelf(fooer Fooer) {
fooer.printSelf()
}
runPrintSelf(myFoo)
// The empty interface is often used as a catch all for any type.
func printArg(arg interface{}) {
fmt.Println(arg)
}
printArg(1) // # => 1
printArg("hello") // # => hello
printArg(1.2345) // # => 1.2345
// you can check the type an interface at runtime using a type-switch.
// NOTE this only works for interfaces... because otherwise you know
// the type.
func printArgWithType(arg interface{}) {
switch arg.(type) {
case string:
fmt.Println("string", arg)
case int:
fmt.Println("number", arg)
default:
fmt.Println("unknown", arg)
}
}
printArgWithType(1) // #=> number 1
printArgWithType("hello") // #=> string hello
printArgWithType(1.2345) // #=> unknown 1.2345
// you can alias a type using the type keyword, this can extend to
// structs, slices and maps as well.
type Fooer2 Fooer
func runPrintSelf2(fooer Fooer2) {
fmt.Println("begin-foo2")
fooer.printSelf()
fmt.Println("end-foo2")
}
runPrintSelf(myFoo) // #=> {hello %!s(int=1)}@0xc0000ac040
// x.(T) where T is an interface is a type cast, in this case it's
// not necessary because Foo can autocast to Fooer2, but its worth
// demonstrating.
runPrintSelf2(myFoo.(Fooer2))
// #=> "begin-foo2\n{hello %!s(int=1)}@0xc0000ac080\nend-foo"
// WARN a typecast between interfaces can fail, you should check
// when you cast.
// the defer keyword is used to postpone the execution of an expression
// until the end of the current function. Each call to defer evaluates
// the arguments of the function and pushes it onto a stack to be evaluated
// later. The stack is unravelled at the end of the function, with each
// defered call being run.
for i := 0; i < 10; i++ {
defer fmt.Printf("%d ", i)
}
// #=> 9 8 7 6 5 4 3 2 1 0
// go uses goroutines as the main concurrency medium, instead of threads.
// think of a goroutine as a light thread, any function can be invoked
// as a goroutine by prefixing it with the go keyword.
func printArr(arr []int) {
for _, v := range arr {
fmt.Printf("%d ", v)
time.Sleep(100 * time.Millisecond)
}
}
go printArr([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
go printArr([]int{100, 99, 98, 97, 96, 95, 94, 93, 92, 91})
// #=> 1 100 2 99 98 3 4 97 96 5 6 95 94 7 8 93 92 9 10 91
for {} // needed or else go wont wait for goroutines to finish
/*
go has mutexes, but offers another means of synchronising goroutines.
goroutines are passed a channel and which they write to or read from.
when a goroutine attempts to read from an empty channel, it'll block
until there's something to be read. When a goroutine attempts to write
to a full channel, it'll block until there's space to write.
*/
// makes a channel of capacity 1 which reads/writes ints, you can supply
// a larger capacity to buffer the channel.
var fibCh = make(chan int)
func fib(ch chan int) {
var i, j = 0, 1
for {
ch <- i // write to channel
i, j = j, i+j
}
}
go fib(fibCh) // start goroutine
for i := 0; i < 10; i++ {
fmt.Printf("%d, ", <-fibCh)
}
// #=> 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
// the select clause is a special expression that let's you wait
// on multiple channels at once.
func tickTickBoom() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
// WARN you can't break the outer for, from the inner select
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
// NOTE you can use the := assignment to assign a value to the output
// of the channel in the select clause.