go by example part 3

This commit is contained in:
WieErWill 2022-01-05 22:56:08 +01:00
parent c1b7c64da2
commit c5983176c5
10 changed files with 337 additions and 0 deletions

View File

@ -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 bases 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())
}

View File

@ -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
}
// its 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)
}
}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 functions 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
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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 thats 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")
}
}

View File

@ -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")
}
}