diff --git a/5-go-by-example/21-embedding.go b/5-go-by-example/21-embedding.go new file mode 100644 index 0000000..52209c3 --- /dev/null +++ b/5-go-by-example/21-embedding.go @@ -0,0 +1,48 @@ +// embedding of structs and interfaces to express a more seamless composition of types + +package main + +import "fmt" + +type base struct { + num int +} + +func (b base) describe() string { + return fmt.Sprintf("base with num=%v", b.num) +} + +// container embeds a base +// embedding looks like a field without a name +type container struct { + base + str string +} + +func main() { + // when creating structs with literals, initialize the embedding explicitly + co := container{ + base: base{ + num: 1, + }, + str: "some name", + } + + // access the base’s fields directly + fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str) + + // full path using the embedded type name + fmt.Println("also num:", co.base.num) + + // container embeds base + // methods of base also become methods of a container + fmt.Println("describe:", co.describe()) + + type describer interface { + describe() string + } + + // container implements describer interface because it embeds base + var d describer = co + fmt.Println("describer:", d.describe()) +} diff --git a/5-go-by-example/22-errors.go b/5-go-by-example/22-errors.go new file mode 100644 index 0000000..508e84b --- /dev/null +++ b/5-go-by-example/22-errors.go @@ -0,0 +1,59 @@ +package main + +import ( + "errors" + "fmt" +) + +// by convention, errors are the last return value and have type error, a built-in interface +func f1(arg int) (int, error) { + if arg == 42 { + // errors.New constructs a basic error value with the given error message + return -1, errors.New("can't work with 42") + } + // a nil value in the error position indicates that there was no error + return arg + 3, nil +} + +// it’s possible to use custom types as errors by implementing the Error() method on them +type argError struct { + arg int + prob string +} + +func (e *argError) Error() string { + return fmt.Sprintf("%d - %s", e.arg, e.prob) +} + +func f2(arg int) (int, error) { + if arg == 42 { + // use &argError syntax to build a new struct + return -1, &argError{arg, "can't work with it"} + } + return arg + 3, nil +} + +func main() { + // loops below test out each of our error-returning functions + for _, i := range []int{7, 42} { + if r, e := f1(i); e != nil { + fmt.Println("f1 failed:", e) + } else { + fmt.Println("f1 worked:", r) + } + } + for _, i := range []int{7, 42} { + if r, e := f2(i); e != nil { + fmt.Println("f2 failed:", e) + } else { + fmt.Println("f2 worked:", r) + } + } + + // programmatically use the data in a custom error + _, e := f2(42) + if ae, ok := e.(*argError); ok { + fmt.Println(ae.arg) + fmt.Println(ae.prob) + } +} diff --git a/5-go-by-example/23-goroutines.go b/5-go-by-example/23-goroutines.go new file mode 100644 index 0000000..125bb9c --- /dev/null +++ b/5-go-by-example/23-goroutines.go @@ -0,0 +1,32 @@ +// A goroutine is a lightweight thread of execution +package main + +import ( + "fmt" + "time" +) + +func f(from string) { + for i := 0; i < 3; i++ { + fmt.Println(from, ":", i) + } +} + +func main() { + // normal function call f(s), running synchronously + f("direct") + + // invoke functions in a goroutine, use go f(s) + // this new goroutine will execute concurrently with the calling one + go f("goroutine") + + // start a goroutine for an anonymous function call + go func(msg string) { + fmt.Println(msg) + }("going") + + // the two function calls are running asynchronously in separate goroutines now + // Wait for them to finish (alternate use WaitGroup) + time.Sleep(time.Second) + fmt.Println("done") +} diff --git a/5-go-by-example/24-channels.go b/5-go-by-example/24-channels.go new file mode 100644 index 0000000..3dfece3 --- /dev/null +++ b/5-go-by-example/24-channels.go @@ -0,0 +1,16 @@ +// Channels are the pipes that connect concurrent goroutines +package main + +import "fmt" + +func main() { + // create new channel with make(chan val-type) + messages := make(chan string) + + // send a value into a channel, here "ping" + go func() { messages <- "ping" }() + + // receives a value from the channel + msg := <-messages + fmt.Println(msg) +} diff --git a/5-go-by-example/25-channel-buffering.go b/5-go-by-example/25-channel-buffering.go new file mode 100644 index 0000000..4312be9 --- /dev/null +++ b/5-go-by-example/25-channel-buffering.go @@ -0,0 +1,18 @@ +// By default channels are unbuffered, meaning that they will only accept sends (chan <-) if there is a corresponding receive (<- chan) ready to receive the sent value +// Buffered channels accept a limited number of values without a corresponding receiver for those values +package main + +import "fmt" + +func main() { + // create channel of strings buffering up to 2 values + messages := make(chan string, 2) + + // send values into the channel without corresponding concurrent receive + messages <- "buffered" + messages <- "channel" + + // receive values as usual + fmt.Println(<-messages) + fmt.Println(<-messages) +} diff --git a/5-go-by-example/26-channel-synchronization.go b/5-go-by-example/26-channel-synchronization.go new file mode 100644 index 0000000..ac732c7 --- /dev/null +++ b/5-go-by-example/26-channel-synchronization.go @@ -0,0 +1,24 @@ +// synchronize execution across goroutines +package main + +import ( + "fmt" + "time" +) + +// the done channel will be used to notify another goroutine that this function’s work is done +func worker(done chan bool) { + fmt.Print("working...") + time.Sleep(time.Second) + fmt.Println("done") + // Send a value to notify "done" + done <- true +} + +func main() { + // start a worker goroutine, giving it the channel to notify on + done := make(chan bool, 1) + go worker(done) + // block until receive notification from the worker on the channel + <-done +} diff --git a/5-go-by-example/27-channel-directions.go b/5-go-by-example/27-channel-directions.go new file mode 100644 index 0000000..415dc66 --- /dev/null +++ b/5-go-by-example/27-channel-directions.go @@ -0,0 +1,23 @@ +// specify if a channel is meant to only send or receive values +package main + +import "fmt" + +// ping function only accepts a channel for sending values +func ping(pings chan<- string, msg string) { + pings <- msg +} + +// pong function accepts one channel for receives (pings) and a second for sends (pongs) +func pong(pings <-chan string, pongs chan<- string) { + msg := <-pings + pongs <- msg +} + +func main() { + pings := make(chan string, 1) + pongs := make(chan string, 1) + ping(pings, "passed message") + pong(pings, pongs) + fmt.Println(<-pongs) +} diff --git a/5-go-by-example/28-select.go b/5-go-by-example/28-select.go new file mode 100644 index 0000000..0993b78 --- /dev/null +++ b/5-go-by-example/28-select.go @@ -0,0 +1,34 @@ +// select lets you wait on multiple channel operations +package main + +import ( + "fmt" + "time" +) + +func main() { + // select across two channels + + c1 := make(chan string) + c2 := make(chan string) + + // each channel will receive a value after some amount of time, to simulate + go func() { + time.Sleep(1 * time.Second) + c1 <- "one" + }() + go func() { + time.Sleep(2 * time.Second) + c2 <- "two" + }() + + // use select to await both of these values simultaneously + for i := 0; i < 2; i++ { + select { + case msg1 := <-c1: + fmt.Println("received", msg1) + case msg2 := <-c2: + fmt.Println("received", msg2) + } + } +} diff --git a/5-go-by-example/29-timeouts.go b/5-go-by-example/29-timeouts.go new file mode 100644 index 0000000..f3f92c1 --- /dev/null +++ b/5-go-by-example/29-timeouts.go @@ -0,0 +1,41 @@ +// bound execution time +package main + +import ( + "fmt" + "time" +) + +func main() { + // returns its result on a channel c1 after 2s + // channel is buffered, so the send in the goroutine is nonblocking + c1 := make(chan string, 1) + go func() { + time.Sleep(2 * time.Second) + c1 <- "result 1" + }() + + // select implementing a timeout + // res awaits the result and <-time + // After awaits a value to be sent after the timeout + // select proceeds with the first receive that’s ready + select { + case res := <-c1: + fmt.Println(res) + case <-time.After(1 * time.Second): + fmt.Println("timeout 1") + } + + // allow a longer timeout of 3s, then the receive from c2 will succeed and print the result + c2 := make(chan string, 1) + go func() { + time.Sleep(2 * time.Second) + c2 <- "result 2" + }() + select { + case res := <-c2: + fmt.Println(res) + case <-time.After(3 * time.Second): + fmt.Println("timeout 2") + } +} diff --git a/5-go-by-example/30-non-blocking-channel-.go b/5-go-by-example/30-non-blocking-channel-.go new file mode 100644 index 0000000..53d1260 --- /dev/null +++ b/5-go-by-example/30-non-blocking-channel-.go @@ -0,0 +1,42 @@ +// Basic sends and receives on channels are blocking +// use select with a default clause to implement non-blocking +package main + +import "fmt" + +func main() { + messages := make(chan string) + signals := make(chan bool) + + // non-blocking receive + // if a value is available on messages then select will take the <-messages case with that value + // if not it will immediately take the default case + select { + case msg := <-messages: + fmt.Println("received message", msg) + default: + fmt.Println("no message received") + } + + // non-blocking send + // msg cannot be sent to the messages channel, because the channel has no buffer and there is no receiver + // Therefore the default case is selected. + msg := "hi" + select { + case messages <- msg: + fmt.Println("sent message", msg) + default: + fmt.Println("no message sent") + } + + // use multiple cases above the default clause to implement a multi-way non-blocking select + // here non-blocking receives on both messages and signals + select { + case msg := <-messages: + fmt.Println("received message", msg) + case sig := <-signals: + fmt.Println("received signal", sig) + default: + fmt.Println("no activity") + } +}