Skip to content

Commit

Permalink
Add wrapping errors.
Browse files Browse the repository at this point in the history
Before there was one page about error handling.

Now there are three.

A simple page (the first part of the existing page).

Then a new page about wrapping errors.

And the third page is the second half of the existing page (custom errors).
  • Loading branch information
guettli committed Mar 16, 2024
1 parent 9c71a85 commit 62da17f
Show file tree
Hide file tree
Showing 15 changed files with 564 additions and 168 deletions.
2 changes: 2 additions & 0 deletions examples.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Interfaces
Struct Embedding
Generics
Errors
Wrapping and Sentinel Errors
Custom Errors
Goroutines
Channels
Channel Buffering
Expand Down
47 changes: 47 additions & 0 deletions examples/custom-errors/custom-errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// It's possible to use custom types as `error`s by
// implementing the `Error()` method on them. Here's a
// variant on the example above that uses a custom type
// to explicitly represent an argument error.

package main

import (
"errors"
"fmt"
)

// It's possible to use custom types as `error`s by
// implementing the `Error()` method on them. Here's a
// variant on the example above that uses a custom type
// to explicitly represent an argument error.
// A custom error type has usualy the suffix "Error".
type argError struct {
arg int
message string
}

func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.message)
}

func f(arg int) (int, error) {
if arg == 42 {
// In this case we use `&argError` syntax to build
// a new struct, supplying values for the two
// fields `arg` and `message`.
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}

func main() {
// If you want to programmatically use the data in
// a custom error, you'll need to get the error as an
// instance of the custom error with errors.As()
_, err := f(42)
var ae *argError
if errors.As(err, &ae) {
fmt.Println(ae.arg)
fmt.Println(ae.message)
}
}
2 changes: 2 additions & 0 deletions examples/custom-errors/custom-errors.hash
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bb8fb48c2d05f29aa87ea21616ff042b52d497c3
sD36hYfgf__q
5 changes: 5 additions & 0 deletions examples/custom-errors/custom-errors.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$ go run errors.go
f worked: 10
f failed: 42 - can't work with it
42
can't work with it
52 changes: 4 additions & 48 deletions examples/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,72 +16,28 @@ import (

// By convention, errors are the last return value and
// have type `error`, a built-in interface.
func f1(arg int) (int, error) {
func f(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 `error`s by
// implementing the `Error()` method on them. Here's a
// variant on the example above that uses a custom type
// to explicitly represent an argument error.
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 {

// In this case we use `&argError` syntax to build
// a new struct, supplying values for the two
// fields `arg` and `prob`.
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}

func main() {

// The two loops below test out each of our
// error-returning functions. Note that the use of an
// inline error check on the `if` line is a common
// idiom in Go code.
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 failed:", e)
if r, e := f(i); e != nil {
fmt.Println("f failed:", e)
} else {
fmt.Println("f1 worked:", r)
fmt.Println("f 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)
}
}

// If you want to programmatically use the data in
// a custom error, you'll need to get the error as an
// instance of the custom error type via type
// assertion.
_, e := f2(42)
if ae, ok := e.(*argError); ok {
fmt.Println(ae.arg)
fmt.Println(ae.prob)
}
}
4 changes: 2 additions & 2 deletions examples/errors/errors.hash
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
00affa44cc98f14c2b10934a4187c9445fac0fe6
NiJOpCPO3L0
bd2a75026d7959adf8e633c6084343740dfe842e
o2xrWWk_1MU
8 changes: 2 additions & 6 deletions examples/errors/errors.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
$ go run errors.go
f1 worked: 10
f1 failed: can't work with 42
f2 worked: 10
f2 failed: 42 - can't work with it
42
can't work with it
f worked: 10
f failed: can't work with 42
# See this [great post](https://go.dev/blog/error-handling-and-go)
# on the Go blog for more on error handling.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Wrapping errors is a technique that allows you to add additional context
// to an error while preserving the original error.
// This approach is beneficial for debugging and understanding
// the chain of events that led to an error, especially in
// complex applications with multiple layers of function calls.

package main

import (
"errors"
"fmt"
"math/rand/v2"
)

// A "sentinel error" has usualy the prefix "Err"
var ErrOutOfTea = errors.New("no more tea available.")

var ErrPower = errors.New("can't boil water.")

func MakeTea() error {
if rand.Int32N(4) == 0 {
return ErrOutOfTea
}
if rand.Int32N(7) == 0 {
// You can wrap a sentinel error with %w
return fmt.Errorf("Add custom text: %w", ErrPower)
}
return nil
}

func main() {
for range 14 {
err := MakeTea()
if err != nil {
// By using several if-statements we can handle
// different sentinel errors.
// A switch statement is not applicable here.
if errors.Is(err, ErrOutOfTea) {
fmt.Println("We should buy new tea!")
continue
}
if errors.Is(err, ErrPower) {
fmt.Println("Now it is dark.")
continue
}
fmt.Printf("Some unknown error: %s", err)
continue
}
fmt.Println("The tea is warm and inviting.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
936b77def6dea78dd79c8eeb59c74c8cd1caf642
W9gasLW-MJp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
$ go run wrapping-and-sentinel-errors.go
The tea is warm and inviting.
The tea is warm and inviting.
The tea is warm and inviting.
The tea is warm and inviting.
We should buy new tea!
The tea is warm and inviting.
The tea is warm and inviting.
The tea is warm and inviting.
Now it is dark.
The tea is warm and inviting.
The tea is warm and inviting.
The tea is warm and inviting.
We should buy new tea!
The tea is warm and inviting.
Loading

0 comments on commit 62da17f

Please sign in to comment.