diff --git a/go/02-next-level/03-errors.go b/go/02-next-level/03-errors.go index 983549b..6e68e3b 100644 --- a/go/02-next-level/03-errors.go +++ b/go/02-next-level/03-errors.go @@ -5,23 +5,28 @@ import ( "fmt" ) +// a function that can fail, so it returns an error as the second return value func f(arg int) (int, error) { if arg == 42 { - - return -1, errors.New("can't work with 42") + // in case of an error, return the zero value for the result and a non-nil error + return 0, errors.New("can't work with 42") } + // simply return the result and a nil error in the success case return arg + 3, nil } +// define some errors to use in makeTea var ErrOutOfTea = fmt.Errorf("no more tea available") var ErrPower = fmt.Errorf("can't boil water") +// a function that can fail in multiple ways, so it returns an error as the second return value func makeTea(arg int) error { if arg == 2 { + // return the error directly, so we can check for it later with errors.Is return ErrOutOfTea } else if arg == 4 { - + // use %w to wrap the error, so we can check for it later with errors.Is return fmt.Errorf("making tea: %w", ErrPower) } return nil @@ -30,6 +35,7 @@ func makeTea(arg int) error { func main() { for _, i := range []int{7, 42} { + //typical pattern for checking errors in Go: if the error is not nil, handle it and return or continue; otherwise, use the result if r, e := f(i); e != nil { fmt.Println("f failed:", e) } else { @@ -40,9 +46,10 @@ func main() { for i := range 5 { if err := makeTea(i); err != nil { + // use errors.Is to check for specific errors, even if they are wrapped if errors.Is(err, ErrOutOfTea) { fmt.Println("We should buy new tea!") - } else if errors.Is(err, ErrPower) { + } else if errors.Is(err, ErrPower) { // this error is wrapped, but errors.Is can still check for it fmt.Println("Now it is dark.") } else { fmt.Printf("unknown error: %s\n", err) diff --git a/go/02-next-level/04-closure.go b/go/02-next-level/04-closure.go index f057a03..a7d0781 100644 --- a/go/02-next-level/04-closure.go +++ b/go/02-next-level/04-closure.go @@ -2,9 +2,11 @@ package main import "fmt" -func intSeq() func() int { - i := 0 - return func() int { +// a closure is a function value that references variables from outside its body. +// The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables. +func intSeq() func() int { //returns a function that returns an int + i := 0 // i is a variable that intSeq's function value will reference. It continues to exist even after intSeq returns. + return func() int { // the anonomous function is returned here. It is not executed yet. i++ return i } @@ -12,12 +14,15 @@ func intSeq() func() int { func main() { + //functions are first class citizens in Go, so we can assign them to variables, + // pass them as arguments to other functions, and return them from functions. nextInt := intSeq() fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) + //a second function value from intSeq, with its own i variable. newInts := intSeq() fmt.Println(newInts()) } diff --git a/go/02-next-level/05-generics.go b/go/02-next-level/05-generics.go index 09fe044..ac9e9fe 100644 --- a/go/02-next-level/05-generics.go +++ b/go/02-next-level/05-generics.go @@ -2,6 +2,9 @@ package main import "fmt" +// a generic function that takes a slice of any type and a value of that type, and returns the index of the value in the slice, or -1 if it is not found. +// The type parameters S and E are declared in square brackets before the function name, and they can be used in the function signature and body. +// The constraint comparable means that the type E must support the == operator, which is necessary for comparing the value with the elements of the slice. func SlicesIndex[S []E, E comparable](s S, v E) int { for i := range s { if v == s[i] { @@ -11,15 +14,21 @@ func SlicesIndex[S []E, E comparable](s S, v E) int { return -1 } +// represents a linked list of any type T. The type parameter T is used in the definition of List and its methods, +// and it can be instantiated with any type when we create a List value. The type any is in fact defined as +// an empty interface. Thanks to duck-typing it can be used to represent any type, but it does not provide any +// operations on the values of that type. type List[T any] struct { head, tail *element[T] } +// the generic element of the linked list type element[T any] struct { next *element[T] val T } +// adds an instance of the generic type to the list func (lst *List[T]) Push(v T) { if lst.tail == nil { lst.head = &element[T]{val: v} @@ -30,7 +39,8 @@ func (lst *List[T]) Push(v T) { } } -func (lst *List[T]) AllElements() []T { +// returns all elements of the list as a slice of the generic type. +func (lst List[T]) AllElements() []T { var elems []T for e := lst.head; e != nil; e = e.next { elems = append(elems, e.val) @@ -39,14 +49,17 @@ func (lst *List[T]) AllElements() []T { } func main() { - var s = []string{"foo", "bar", "zoo"} - fmt.Println("index of zoo:", SlicesIndex(s, "zoo")) - _ = SlicesIndex[[]string, string](s, "zoo") + var s = []string{"foo", "bar", "zoo"} + // The types S and E are explicitly specified as []string and string respectively. + fmt.Println("index of zoo:", SlicesIndex[[]string, string](s, "zoo")) var s2 = []int{2, 4, 5, 6} + // The type E is inferred from the type of the value 4, which is int, + // and the type S is inferred from the type of the slice s2, which is []int. fmt.Println("index of 4: ", SlicesIndex(s2, 4)) + // instantiate a list of float64 elements lst := List[float64]{} lst.Push(10) lst.Push(13) diff --git a/go/02-next-level/06-defer.go b/go/02-next-level/06-defer.go index fde5ef5..e0af38a 100644 --- a/go/02-next-level/06-defer.go +++ b/go/02-next-level/06-defer.go @@ -7,18 +7,21 @@ import ( func main() { - f := createFile("/tmp/defer.txt") - defer closeFile(f) - writeFile(f) -} - -func createFile(p string) *os.File { fmt.Println("creating") - f, err := os.Create(p) + f, err := os.Create("/tmp/defer.txt") + + // defer the closing of the file until the surrounding function returns. + // This ensures that the file will be closed even if there is a panic or an early return in the function. + defer closeFile(f) + if err != nil { panic(err) } - return f + + //will not be called in case of a panic before + writeFile(f) + + // closeFile will be called here due to the defer-statement, even in case of a panic before. } func writeFile(f *os.File) { @@ -28,8 +31,10 @@ func writeFile(f *os.File) { func closeFile(f *os.File) { fmt.Println("closing") - err := f.Close() - + var err error + if f != nil { + err = f.Close() + } if err != nil { panic(err) }