diff --git a/examples.txt b/examples.txt index b637704b8..1607fcdec 100644 --- a/examples.txt +++ b/examples.txt @@ -22,6 +22,8 @@ Interfaces Struct Embedding Generics Errors +Wrapping and Sentinel Errors +Custom Errors Goroutines Channels Channel Buffering diff --git a/examples/custom-errors/custom-errors.go b/examples/custom-errors/custom-errors.go new file mode 100644 index 000000000..89dcbc041 --- /dev/null +++ b/examples/custom-errors/custom-errors.go @@ -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) + } +} diff --git a/examples/custom-errors/custom-errors.hash b/examples/custom-errors/custom-errors.hash new file mode 100644 index 000000000..d7c9dd54e --- /dev/null +++ b/examples/custom-errors/custom-errors.hash @@ -0,0 +1,2 @@ +bb8fb48c2d05f29aa87ea21616ff042b52d497c3 +sD36hYfgf__q diff --git a/examples/custom-errors/custom-errors.sh b/examples/custom-errors/custom-errors.sh new file mode 100644 index 000000000..47772ac2b --- /dev/null +++ b/examples/custom-errors/custom-errors.sh @@ -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 diff --git a/examples/errors/errors.go b/examples/errors/errors.go index b8501392d..9d3b17ebf 100644 --- a/examples/errors/errors.go +++ b/examples/errors/errors.go @@ -16,13 +16,11 @@ 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 @@ -30,58 +28,16 @@ func f1(arg int) (int, 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) - } } diff --git a/examples/errors/errors.hash b/examples/errors/errors.hash index a1f74a51c..e368e59fc 100644 --- a/examples/errors/errors.hash +++ b/examples/errors/errors.hash @@ -1,2 +1,2 @@ -00affa44cc98f14c2b10934a4187c9445fac0fe6 -NiJOpCPO3L0 +bd2a75026d7959adf8e633c6084343740dfe842e +o2xrWWk_1MU diff --git a/examples/errors/errors.sh b/examples/errors/errors.sh index 54315ae7d..959a9c120 100644 --- a/examples/errors/errors.sh +++ b/examples/errors/errors.sh @@ -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. diff --git a/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.go b/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.go new file mode 100644 index 000000000..6315534c8 --- /dev/null +++ b/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.go @@ -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.") + } +} diff --git a/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.hash b/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.hash new file mode 100644 index 000000000..230cbf85d --- /dev/null +++ b/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.hash @@ -0,0 +1,2 @@ +936b77def6dea78dd79c8eeb59c74c8cd1caf642 +W9gasLW-MJp diff --git a/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.sh b/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.sh new file mode 100644 index 000000000..19c75a8a6 --- /dev/null +++ b/examples/wrapping-and-sentinel-errors/wrapping-and-sentinel-errors.sh @@ -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. diff --git a/public/custom-errors b/public/custom-errors new file mode 100644 index 000000000..da217e8c4 --- /dev/null +++ b/public/custom-errors @@ -0,0 +1,206 @@ + + + + + Go by Example: Custom Errors + + + + +
+

Go by Example: Custom Errors

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

It’s possible to use custom types as errors 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 (
+    "fmt"
+)
+
+

It’s possible to use custom types as errors 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)
+}
+
+

In this case we use &argError syntax to build +a new struct, supplying values for the two +fields arg and prob.

+ +
+ +
func f(arg int) (int, error) {
+    if arg == 42 {
+
+ + + +
        return -1, &argError{arg, "can't work with it"}
+    }
+    return arg + 3, nil
+}
+
+

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.

+ +
+ +
func main() {
+
+ + + +
    for _, i := range []int{7, 42} {
+        if r, e := f(i); e != nil {
+            fmt.Println("f failed:", e)
+        } else {
+            fmt.Println("f 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 := f(42)
+    if ae, ok := e.(*argError); ok {
+        fmt.Println(ae.arg)
+        fmt.Println(ae.prob)
+    }
+}
+
+ + + + + + + + +
+ + + +
$ go run errors.go
+f worked: 10
+f failed: 42 - can't work with it
+42
+can't work with it
+
+ + +

+ Next example: Goroutines. +

+ + + + +
+ + + + diff --git a/public/errors b/public/errors index ab084adae..f2c73c07f 100644 --- a/public/errors +++ b/public/errors @@ -14,7 +14,7 @@ if (e.key == "ArrowRight") { - window.location.href = 'goroutines'; + window.location.href = 'wrapping-and-sentinel-errors'; } } @@ -48,7 +48,7 @@ non-error tasks.

- +
package main
@@ -74,7 +74,7 @@ have type error, a built-in interface.

-
func f1(arg int) (int, error) {
+          
func f(arg int) (int, error) {
     if arg == 42 {
@@ -87,17 +87,8 @@ with the given error message.

-
        return -1, errors.New("can't work with 42")
- - - - - - - - - -
    }
+
        return -1, errors.New("can't work with 42")
+    }
@@ -114,72 +105,6 @@ there was no error.

- - -

It’s possible to use custom types as errors 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 @@ -190,37 +115,22 @@ idiom in Go code.

-
    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)
-        }
-    }
+
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 type via type -assertion.

- + -
    _, e := f2(42)
-    if ae, ok := e.(*argError); ok {
-        fmt.Println(ae.arg)
-        fmt.Println(ae.prob)
+          
    for _, i := range []int{7, 42} {
+        if r, e := f(i); e != nil {
+            fmt.Println("f failed:", e)
+        } else {
+            fmt.Println("f worked:", r)
+        }
     }
 }
@@ -237,12 +147,8 @@ assertion.

$ 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
@@ -262,7 +168,7 @@ on the Go blog for more on error handling.

- Next example: Goroutines. + Next example: Wrapping and Sentinel Errors.

@@ -273,7 +179,7 @@ on the Go blog for more on error handling.

diff --git a/public/goroutines b/public/goroutines index 6b16ea9d9..b9784c738 100644 --- a/public/goroutines +++ b/public/goroutines @@ -9,7 +9,7 @@ onkeydown = (e) => { if (e.key == "ArrowLeft") { - window.location.href = 'errors'; + window.location.href = 'custom-errors'; } diff --git a/public/index.html b/public/index.html index 54853fec8..2daeb4ec3 100644 --- a/public/index.html +++ b/public/index.html @@ -75,6 +75,10 @@

Go by Example

  • Errors
  • +
  • Wrapping and Sentinel Errors
  • + +
  • Custom Errors
  • +
  • Goroutines
  • Channels
  • diff --git a/public/wrapping-and-sentinel-errors b/public/wrapping-and-sentinel-errors new file mode 100644 index 000000000..4851e6132 --- /dev/null +++ b/public/wrapping-and-sentinel-errors @@ -0,0 +1,204 @@ + + + + + Go by Example: Wrapping and Sentinel Errors + + + + +
    +

    Go by Example: Wrapping and Sentinel Errors

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    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” is usualy exported (starting with +a capital letter) so that user the package can check +for particular errors.

    + +
    + +
    var OutOfTeaErr = errors.New("no more tea available.")
    +
    + + + +
    var PowerErr = errors.New("can't boil water.")
    +
    +

    You can wrap a sentinel error with %w

    + +
    + +
    func MakeTea() error {
    +    if rand.Int32N(4) == 0 {
    +        return OutOfTeaErr
    +    }
    +    if rand.Int32N(7) == 0 {
    +
    + + + +
            return fmt.Errorf("Add custom text: %w", PowerErr)
    +    }
    +    return nil
    +}
    +
    +

    By using several if-statements we can handle +different sentinel errors. +A switch statement is not applicable here.

    + +
    + +
    func main() {
    +    for range 14 {
    +        err := MakeTea()
    +        if err != nil {
    +
    + + + +
                if errors.Is(err, OutOfTeaErr) {
    +                fmt.Println("We should buy new tea!")
    +                continue
    +            }
    +            if errors.Is(err, PowerErr) {
    +                fmt.Println("Now it is dark.")
    +                continue
    +            }
    +            fmt.Printf("Some unknown error: %s", err)
    +            continue
    +        }
    +        fmt.Println("The tea is warm and inviting.")
    +    }
    +}
    +
    + + + + + + + + +
    + + + +
    $ 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.
    +
    + + +

    + Next example: Custom Errors. +

    + + + + +
    + + + +