Learning goLang

Documenting the initial learning of the go programming language
Getting started
go needs to be installed. Uses the go
command in terminal. Install vscode go extension.
Hello world
// file named main.go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
Run using: go run main.go
Commands
run -> compiles files and runs result
build -> just compiles, creates executable in root, run directly e.g. ./main
fmt -> formats code in files
install / get -> access to other packages
test -> runs test files
Two types of package
Executable -> Generates executable file (only main)
Reusable -> Helpers, libraries for reusable logic (any other name than main)
package main
What is package main?
Assigns all files, with the declaration at the top, to that package, it’s a way of grouping files.
package main is the only package that can be used to create executable
E.g. package me
when built would not create executable
package main must always have a func called main
importing
import
keyword is used to access code within another package (fmt
is a default go package)
go package lib: https://pkg.go.dev/std
// single import
import "fmt"
// importing multiple packages
import (
"fmt"
"strings"
)
func main
func keyword has function name, args and body
File structure
Always; package > import -> func/variables etc
Variable declarations
E.g. var helloWorld sting = "HelloWorld"
Statically typed, (not dynamic like vanilla JS)
(Has type inference so type declaration here not needed var helloWorld = "HelloWorld"
)
Has all the basic types you’d expect
Has a shorthand helloWorld := "HelloWorld"
can only be used for new variable declarations
:=
Seems like variables are mutable
Note: Shorthand syntax seems to only work within a func where as full declaration will work outside
Variables can be declared and then assigned a value later
var helloWorld string
helloWorld = "HelloWorld"
Functions
func getHelloWorld() string {
return "hello world"
}
//To call and assign it to a variable
func main() {
helloWorld := getHelloWorld()
}
funcs must specify a return type, can not be inferred
Variables can be passed into the func; func getHelloWorld(me string)
They can return more than one value:
func getHelloWorld() (string, string) {
return "hello world 1", "hello world 2"
}
//To call and assign it to a variable
func main() {
helloWorld1, helloWorld2 := getHelloWorld()
}
Lists / For loops
Two structures:
- Array -> Fixed length
- Slice -> Variable length
Both must contain the same types
// declares a new array/slice of string and assigned two values to it
myArr := []string {"a", "b"}
// adds value to the slice (NB. append does not mutate myArr)
myArr = append(myArr, "c")
// loops over and prints, range keyword used to iterate over slice
for index, item := range myArr {
fmt.Println(index, item)
}
// why := here? because on every iteration we need to redeclare
for {
// way to do an infinate loop
}
Note, if the index is not needed you can replace with underscore to avoid syntax error.
They are zero indexed; myArr[0]
returns "a"
Built in to accept a range; myArr[0:2]
returns "a", "b"
Values can be omitted to include all; myArr[:2]
returns "a", "b"
myArr[2:]
returns "c"
Language type
Not an OOP or pure functional language. Very similar to javascript using typescript.
Using types
Declaring a type and using it
// list
type deck [] string // Note seems convention for type starts lowercase
myDeck := deck {"a", "b"}
// single type
type colour string
myColour := colour("red")
/*
Not initialising with values is different
between single types and list types
*/
Receiver function
func (t type) print() {}
Any variables of that type
will get access to the function.
Example:
// deck.go
package main
import "fmt"
type deck [] string
func (d deck) print() {
for i, card := range d {
fmt.Println(i, card)
}
}
When initialising and calling the function
// main.go
myArr := deck {"a", "b"}
myArr.print()
Note how we’re calling myArr.print()
The print
func automatically becomes a property of the deck type.
Similar to this
in javascript.
Convention is that the type referenced in the receiver is a single letter; (d deck).
Opinion; not sure why this extra complication is needed over just passing the variable as an argument in the function.
Type conversion
Convert one type into another type
[]byte("HelloWorld") // converts string to byte slice
Strings util package
// useful functions such as join
import "strings"
strings.Join([]string(deck), ", ")
// converts deck type -> string array -> string
OS util package
// writting a file to disk
os.WriteFile("myFile", []byte("my string"), 0666)
// reading a file
bs, err := os.ReadFile("myFile")
if err != nil { // common practise for checking for errors
fmt.Println("Error:", err)
os.Exit(1) // will exit the promgram with an error
}
string(bs) // type converison byte slice to string
Swap func
Way to swap values in a slice
func swap () []string {
v := []string{"1","2","3","4"}
println("v 1: ", toString(v))
v[0], v[3] = v[3], v[0]
println("v 2: ", toString(v))
return v
}
// output
// v 1: 1, 2, 3, 4
// v 2: 4, 2, 3, 2
Testing
To get setup with testing we first need to run: go mod init {name}
to initialise a go.mod
file. Then we can run go test
Dont use third parties such as mocha, jest, selenium etc
Tests go in files {name}_test.go
Testing our swap func
// main_test.go
func TestSwap(t *testing.T) { // Convention for test naming pascal case
v := swap()
if len(v) != 4 {
t.Errorf("incorrect swap length")
}
if v[0] != "4" {
t.Errorf("incorrect first value")
}
if v[3] != "1" {
t.Errorf("incorrect last value: %v", v[3]) // Errorf - formatted string
}
}
To test a function that saves to disk, we don’t mock the behaviour, we handle the clearing of the file at the start and the end of the test.
Struct data structure
Collection of properties that are related, (like an object in javascript).
// Ceating a struct type
type person struct { // Lower case types seems to be convention
firstName string
lastName string
}
// Initialising
joe := person{"joe", "bloggs"} // Not recommended, relies on order
// or
joe2 := person{
firstName: "joe",
lastName: "bloggs",
}
// Mutating
joe.firstName = "jane"
Declaring a type with no values e.g. var joe person
, go will assign whats called zero values to the properties, (not undefined):
- int/float -> 0
- string -> “”
- boolean -> false
To print out the struct in a more readable way use fmt.printf("%+v", joe)
Structs can be embedded in structs, they can have properties that are other custom structs, not just core types like string etc..
type contactInfo struct {
email string
}
type person struct {
...,
contact: contactInfo
// or just contactInfo, if we're happy with the property being called that e.g
contactInfo
}
joe := person{
firstName: "joe",
contact: contactInfo{
email: "joe@bloggs.com",
},
}
Receiver functions work on structs the same way as when using them on types which are base types.
Pass by value
golang is referred to as a pass by value language, when a variable is passed to a function or a receiver function is called on a variable, golang creates a copy and passes that, so any mutations that happen in that function happen on that copy and not the actual value passed in.
func (p person) updateName(name string) {
p.firstName = name
}
p := person{}
p.updateName("joe")
fmt.println(p) // -> empty struct | update in fn will get updated to a copy
Pointers
To allow updating of values we use pointers. We get the pointer, (address where the value is pointing to in memory), and pass that to update function.
func (pPointer *person) updateName(name string) { // * is a type description, type pointer to a deck
(*pPointer).firstName = name // * operator gets the value for that pointer
}
p := person{}
pPointer := &p // & operator gets the pointer for that value
pPointer.updateName("joe")
fmt.println(p) // -> todo
Pointer operators:
- &
-> gets the pointer for a value
- *
-> gets the value for a pointer
Can be confusing that *
means two different things; when used on a type it is to say the type is a pointer to that type, when used on a value it’s a pointer to that value.
Pointers would be needed for; int, float, string and struct types.
Pointer shorthand
Because the above updateName function has a receiver of type *person
golang allows for a person type OR a pointer person type to be passed.
// longhand
pPointer := &p
pPointer.updateName("joe")
// shorthand
p.updateName("joe")
Reference vs Value types

When a slice is passed to a function it will mutate the passed in value, (no need to get a pointer to the slice). This is because when a slice is created it creates the slice and an array behind the scenes. This array has its own address in memory. So when the slice is passed to the function it is copied as you would expect, but the copy contains a pointer to the same underlying array as the original.
Map
Collection of key value pairs, (like object in javascript). Keys and values must all be the exact same type.
colours := map[string]string{ // keys and values both type string
"red": "#ff0800",
"green": "#00ff08",
}
// to create without values
var colours map[string]string
// or
colours := make(map[string]string) // make built in golang function
// adding
colours["white"] = "#fff"
// removing
delete(colours, "white") // delete built in golang function
// iterate
for key, value := range colour {
...
}
Maps vrs structs

Structs would seem to be used over maps in most cases imo.
Interfaces
Allow functions with common code to be called on similar types.
type bot interface {
getGreeting() sting
}
type eBot struct{}
type sBot struct{}
func (e eBot) getGreeting() string{...}
func (s sBot) getGreeting() string{...}
func print (b bot){...}
eb := eBot{}
sb := sBot{}
print(eBot)
print(sBot)
// Becuase both bots call getGreeting and return a string they are classified
// as being a member of the bot interface and the same print function can
// be called on both eBot and sBot types
There are two types of type; concrete (string, struct, eBot etc..) and interface (bot). You can not create a value out of a interface type like you can a concrete type.
Interfaces are implicit, (you dont have to say eBot implements the bot interface, its inferred).
Interfaces can be nested, so one interface can have properties which are other interfaces.
Making a http request
import (
"fmt"
"net/http"
"os"
)
resp, err := http.Get("https://google.com")
if err != nil {
fmt.Println("Error", err)
os.Exit(1)
}
fmt.Println(resp) // outputs the raw response
// to outpout body
bs := make([]byte, 99999) // initialise byte slice for body to go into
bc, err := resp.Body.Read(bs) // read the body and add to byte slice
fmt.Println(string(bs))
fmt.Println("byte count", bc)
fmt.Println("err", err)
// or
io.Copy(os.Stdout, resp.Body) // documented below
Reader and Writer (with io.Copy example)
func Copy(dst Writer, src Reader) (written int64, err error)
This function will take a type that implements the Writer
interface and a type that implements the Reader
interface. It then copies the one to the other.
// a custom log writer to the console
type logWriter struct{}
func (logWriter) Write(b []byte) (int, error) {
fmt.Println(string(b))
fmt.Println("Using my custom writer")
return len(b), nil // int output is byte length
}
lw := logWriter{}
// as logWriter type implements a `Write` function it can be passed to Copy
io.Copy(lw, resp.Body)
Channels and Routines
Running requests in parallel rather than serial, (like Promise.all()
in javascript).
A running process is one golang routine. Additional go routines can be created when dealing with concurrent requests which you want to hand off from the main thread.
Channels, are typed (must be the same type), and allow management of these routines, act like a two way messaging device.
Write to channel — c <- "value"
Read from channel (into variable) — i <- c
links := []string{}
c := make(chan string) // create channel of string
for _, link := range links {
go linkCheck(link, c)
// placing the keyword `go` infront of a func creates new go routine.
// this means that the http request will not wait for a response before
// continuing to the next check
}
func linkCheck(link string, c chan string) {
_, err := http.Get(link)
c <- "resposne" // add a value to the channel
...
}
// when run without a channel the program will just exit as the main
// thread will finish before the passed off routines have come back
// with channel
for i := 0, i <= len(links), i++ {
fmt.println(<-c)
// read from the channel n times until all respsonse recieved
// A VALUE COMING THROUGH c IS A BLOCKING CALL
}
By default, golang will use one cpu core and will use whats called the go Scheduler
to orchestrate the routines, (still technically running one routines at a time). When multi cores are available the scheduler will assign routines across the cores and will be running multiple routines at the same time.
Concurrency is not parallelism
Concurrency — behaviour demonstrated given one core
Parallelism — behaviour demonstrated given multi core
Function literal
Same as anonymous function in javascript.
for l := range c {
go func(link) { // func within body of code defines function literal
time.sleep(5 * time.Second)
linkCheck(link, c)
}(l) // parentheses at the end executes the function literal
// we need to pass the l value so its scoped to the function literal
}
References
https://www.udemy.com/course/go-the-complete-developers-guide