These are my notes about the Go: The Complete Developers Guide (Golang) course on Udemy:
Using vim to set up:
- install vim-go: https://github.com/fatih/vim-go
- install the plugin (Vim8):
git clone https://github.com/fatih/vim-go.git ~/.vim/pack/plugins/start/vim-go
- run the install:
:GoIinstallBinaries
- install the plugin (Vim8):
vim-go
will autocomplete by using
<C-x><C-o>
Go code style is strictly enforced and can be automatically cleaned up with:
go fmt
go run main.go deck.go
runs both modules as they are both part of the main
package
the main
package will create an executeable. All other packages will not
create an executeable
so
package apple
import (
"fmt"
)
func main() {
fmt.Println("Hello World")
}
will not create an executeable when compiled with go build main.go
for index, item := range myitemsslice {}
loop through a range of items (like a slice)for i := 0; i < maximumVal; i++ {}
bog standard for loopfor {}
loop indefinitely
type deck []string
creates a type called deck
which will behave like []string
, much like a
subclass so we can use it like
deck{"One", "Two", "Three"}
create a function with a receiver (in this case (d deck)
func (d deck) print() {
for i, card := range d {
fmt.Println(i, card)
}
}
d
is from typedeck
which is set up as[]string
. By convention, this is a one or two letter variable from the type- it loops through a range, exposing
i
andcard
range d
allows iterating over a range (like[]string
Links
we can use the following to set a range from within a slice
slicename[upToAndIncluding:FromNotIncluding]
so the following are true
mySlice := []string {"Apple", "Banana", "Orange", "Grape"}
mySlice[0:2]
// returns {"Apple", "Banana"}
mySlice[:2]
// returns {"Apple", "Banana"}
mySlice[2:4]
// returns {"Orange", "Grape"}
mySlice[2:]
// returns {"Orange", "Grape"}
Notes:
- the first number in the range is "from and including"
- the first number can be omitted to infer from the start (0)
- the second number in the range is "up to and not including"
- the second number can be omitted to infer to the end (4 in this case)
so a single number can be used on either side of the colon to select 2 subsets whose total is the whole set
mySlice := []string {"Apple", "Banana", "Orange", "Grape"}
fmt.Println(mySlice[2:])
// returns {"Orange", "Grape"}
fmt.Println(mySlice[:2])
// returns {"Apple", "Banana"}
the following shows how we can return multiple values from a single function
func deal(d deck, handSize int) (deck, deck) {
return d[:handSize], d[handSize:]
}
func main() {
cards := newDeck()
hand, remainingCards := deal(cards, 5)
hand.print()
remainingCards.print()
}
this function will take a deck and a handsize and split that deck into 2
separate decks, one with the handsize and one with the remaining cards in the
original deck. In the main func we create and assign the two return values to
hand
and remainingCards
. Phew!
Using random numbers from the math/rand
package directly will result in the
same result every time the program is run. To make the random numbers more
random, create an instance of rand
with a seed
func (d deck) shuffle() {
// use number of nanoseconds since the epoch
// as a seed for a new rand instance
seed := time.Now().UnixNano()
source := rand.NewSource(seed)
r := rand.New(source)
for i := range d {
newPosition := r.Intn(len(d) - 1)
d[i], d[newPosition] = d[newPosition], d[i]
}
}
using the fmt
package we can use
Println
output list of arguments - very likeconsole.log()
in jsPrintf
output with formatting tokens%v
value%+v
print out all keys and values from a struct
set up with a type with (or writhout) some fields
type person struct {
firstName string
lastName string
}
can use nested structs
type contactInfo struct {
email string
zip int
}
type person struct {
firstName string
lastName string
contactInfo
}
notes:
contactInfo
is destructured here, so it meanscontactInfo contactInfo
methods can also be added to a struct by using the type in the receiver, making it as close to a class as Golang gets
func (p person) getName() string {
return p.lastName + ", " + p.firstName
}
in the following function:
func main() {
jim := person{
firstName: "Jim",
lastName: "Cricket",
contactInfo: contactInfo{
email: "jimminycrickey@gmail.com",
zipCode: 12345,
},
}
jimPointer := &jim
jimPointer.updateName("Jimminy")
jim.print()
}
func (pointerToPerson *person) updateFirstName(newFirstName string) {
(*pointerToPerson).firstNameName = newFirstName
}
*person
is a type of "pointer to a person type"(*pointerToPerson)
is a dereference of a pointer to an actual value&jim
is a reference to the jim person
Type | Description |
---|---|
&variable |
the memory address of the value this variable is pointing at |
*pointer |
the value of the memory address |
so in the following example, the output will be Bill
because we are converting
name
into a memory address and immediately dereferencing that back into a
string value
package main
import "fmt"
func main() {
name := "Bill"
fmt.Println(*&name)
}
Golang will allow the developer to not worry about manually converting the value into a memory address before using in a reference so the following works:
func main() {
jim := person{
firstName: "Jim",
lastName: "Cricket",
contactInfo: contactInfo{
email: "jimminycrickey@gmail.com",
zipCode: 12345,
},
}
jim.updateName("Jimminy")
jim.print()
}
func (pointerToPerson *person) updateFirstName(newFirstName string) {
(*pointerToPerson).firstNameName = newFirstName
}
This is taking jim
of type person
and intrinsically converting the person
to a *person
(pointer to person), making the code look less noddy.
Importantly we must still use the type *person
in our function declaration to
dereference it otherwise we will have a type mismatch person
to *person
so even if we do the following, the slice will be updated >,<
func main() {
mySlice := []int{0, 2, 3, 4, 5}
updateSlice(mySlice)
fmt.Println(mySlice) // outputs [1 2 3 4 5]
}
func updateSlice(s []int) {
s[0] = 1
}
This is because, although the function does actually copy the slice, the slice itself is a reference type (it points to an underlying array). The following table shows which types are values and which are reference types
Value Types | Reference Types |
---|---|
int | slice |
float | map |
bool | channel |
string | pointer |
struct | function |
- https://www.udemy.com/course/go-the-complete-developers-guide/learn/lecture/7797348
- https://www.udemy.com/course/go-the-complete-developers-guide/learn/lecture/7797350
is a set of key/value pairs where the types of the keys and the values are always the same
types of declaration
// these create empty maps
colors := make(map[string]string)
var colors = map[string]string
// this includes values
colors := map[string]string{
"red": "#ff0000",
"green": "#00ff00",
}
to access or mutate values within the map, we must always use the square brackets
myColour := myMap["white"] // good
myColour := myMap.white // nope
This is because we need to match the type of the key
to remove k/v from a map use `delete(myMap, "thekey")
Interfaces are simple contracts that mean that a type will implement all of the methods defined on the interface. It can be used in place of the type, much like an interface / superclass in OOP land.
note: there is no implements
keyword in Golang; it is implied
type Writer interface {
Write(p []byte) (n int, err error)
}
type logWriter struct{}
func (lw logWriter) Write(p []byte) (n int, err error) {
// do something with p
fmt.Println(string(p))
// return correct value types
return len(p), nil
}
func main() {
res, err := http.Get("https://google.com")
lw := logWriter
io.Copy(lw, res.Body)
}
res.Body
implements theReader
interfacelw
implements theWriter
interface- therefore we can use these in
io.Copy()
as theWriter
andReader
go routines allow us to create coroutines for a function call. They always occur on a function call (rather than a block).
on a single core machine, the go routine will execute until it hits a blocking call (like an http.Get() that waits for a response before continuing). When this happens it will continue the execution of the program until it hits another blocking call / go routine declaration.
on a multi core machine, each go routine will be assigned to a core to run in actual parallel.
links:
in the following example
func main() {
links := []string{
"https://overbyte.co.uk",
"https://google.com",
"https://facebook.com",
"https://twitter.com",
"https://stackoverflow.com",
}
for _, link := range links {
go checkLink(link)
}
}
func checkLink(link string) {
fmt.Println("checking", link)
_, err := http.Get(link)
if err != nil {
fmt.Println("Error opening link", link, ":", err)
return
} else {
fmt.Println(">", link, "is all good")
}
}
we would receive no output because the goroutines execute outside of the the normal operation so once the loop is complete and the goroutines are set up, the program exits, before the go routines have a chance to complete
main routine -------------------|exit(0) (all is well)
└── go checkLink() 1 --------------^
└── go checkLink() 2 --------------^
└── go checkLink() 3 --------------^
to deal with this, we can put the goroutines into a channel which holds the process open and allows communication between the main routine
func main() {
links := []string{
"https://overbyte.co.uk",
"https://google.com",
"https://facebook.com",
"https://twitter.com",
"https://stackoverflow.com",
}
// set up a new channel using string type to communicate between
// the channel and the main routine
c := make(chan string)
// set up a goroutine for each link
for _, link := range links {
go checkLink(link, c)
}
// wait for a response *from each goroutine* before exiting
for i := 0; i < len(links); i++ {
fmt.Println("received", <-c)
}
}
// GET a link and report the result back up to the main routine
func checkLink(link string, c chan string) {
// GET a link
_, err := http.Get(link)
// log if the link is an error (site is probably not up)
if err != nil {
fmt.Println("Error opening link", link, ":", err)
// report the link back to the main routine
c <- link
return
} else {
fmt.Println(">", link, "is all good")
c <- link
}
}
notes:
myvar <-channelname
receive a single message from the channel to a variable / argument. This blocks the main routine from exitingchannelname <- data
senddata
to channel
main routine ------------------------------------------------------------|exit(0) (all is well)
└── setup channel -------------------------|--------|------^ messages received
└── go checkLink() 1 --------------^ message
└── go checkLink() 2 --------------^ message
└── go checkLink() 3 ----------^ message
although we can use a perpetual for loop to take the output of a go routine and pass it back to a new one:
for {
go checkLink(<-c, c)
}
it is a better convention to use the following syntax as it exposes the response value in a way that is easy to follow:
for l := range c {
go checkLink(l, c)
}
Important. If the main routine is Sleep
ed, nothing else (including other go
routines) will complete - they will be queued up to wait for the main routine to
begin again. time.Sleep
should really only be used on child go routines unless
the desired outcome is for the whole application to cease activity.
the following will sleep the go routine for 5 seconds
time.Sleep(5 * time.Second)
we can use a function literal (think anonymous function) to kick off a go routine
for link := range myChannel {
go func (l string) {
time.Sleep(5 * time.Second)
checkLink(l, myChannel)
}(link)
}
notes:
- the function literal has to self-execute by using parentheses after the declaration
- because
link
in the outerfor
loop scope may be changing every iteration, we need to copy the value into the function literal- the string
link
is passed to the function literal via the parentheses as it would be with a function declaration
- the string
checkLink()
is no longer kicking off the go routine - the function literal is
lectures:
- https://www.udemy.com/course/go-the-complete-developers-guide/learn/lecture/7809266
- https://www.udemy.com/course/go-the-complete-developers-guide/learn/lecture/7809272
- https://www.udemy.com/course/go-the-complete-developers-guide/learn/lecture/7824514
repos: