Golang errors: Difference between revisions

From wikinotes
Line 6: Line 6:
== Example ==
== Example ==
<blockquote>
<blockquote>
{{ expand
| Define, Return, and handle Errors
|
<syntaxhighlight lang="go">
<syntaxhighlight lang="go">
package main
package main
Line 50: Line 53:
}
}
</syntaxhighlight>
</syntaxhighlight>
}}
</blockquote><!-- Example -->
</blockquote><!-- Example -->



Revision as of 14:18, 18 June 2022

Go seems to discourage the use of exception-style control-flows,
encouraging the use of errors in return-values instead.

Errors

Example

Define, Return, and handle Errors

package main

import "errors"
import "fmt"
import "os"

var ErrDivisionByZero = errors.New("Cannot divide by 0")
var ErrOverNineThousand = errors.New("Cannot sum to over 9000")

func Divide(a int, b int) (result int, err error) {
	if b == 0 {
		return 0, ErrDivisionByZero
	}
	return a / b, nil
}

func DivideThenAdd(a int, b int, c int) (result int, err error) {
	res, err := Divide(a, b)
	if err != nil {
		return 0, fmt.Errorf("DivideThenAdd: %w", err)
	}
    res += c
    if res > 9000 {
        return 0, ErrOverNineThousand
    }
	return res, nil
}

func main() {
	var err error

	res, err := DivideThenAdd(10, 0, 1)
	if err != nil {
		if errors.Is(err, ErrDivisionByZero) {
			fmt.Println("Cannot divide by zero!!")
		} else if errors.Is(err, ErrOverNineThousand) {
			fmt.Println("I'd prefer we kept numbers below 9000")
		}
		os.Exit(1)
	}
	fmt.Println(res)
}

Returning Errors

Go prefers passing error objects as return values to panicking (go's exception-like behaviour).
Errors can be any object that expose the Error() string method.

require "errors"
require "fmt"

// build ad-hoc error
fmt.Errorf("User does not exist")
errors.New("User does not exist")

Functions generally return an error as the last value on fail,
or nil on success

func doThing (value int, error) {
    if success {
        return 123, nil                          // success
    }
    return 0, fmt.Errorf("User does not exist")  // fail
}

Wrapping Errors

Handling Errors

func doThing {
    var err error

    if err = doThingOne; err != nil {
        if errors.Is(err, SomePreDefinedError) {...}
        if errors.As(err, SomeWrappedError) {...}
    }
}

Error Types

Errors are often pre-defined as package constants

require "errors"

var UserNotExistError = errors.New("User does not exist")

func DoAs(user string) error {
    return UserNotExistError
}

You can then check for a specific error

require "errors"

if err := DoAs("someuser"); err != nil {
    if errors.Is(err) {...}
}

Panic

panic

A panic is go's repacement for exceptions.
If a panic is not caught, it bubbles to the top of the application, and it exits with an error.

panic("I encountered an error")

recover

You can check the value of a panic (if one has been raised) using recover().

panic("I encountered an error")  // raise a panic
err := recover()                 // returns nil/error-msg-if-present

It is common to handle panics in deferred functions

func main() {
    fmt.Println("hi")
    panic("I just encountered an error")
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Error: ", err)
            panic(err)                         // <<-- re-raise panic
        }
    }

    fmt.Println("bye")  // <-- never runs
}