Golang errors: Difference between revisions

From wikinotes
Line 4: Line 4:
= Errors =
= Errors =
<blockquote>
<blockquote>
== Example ==
== Basics ==
<blockquote>
<blockquote>
Go prefers passing error objects as return values to panicking (go's exception-like behaviour).
Errors can be wrapped (often to add info about where found)<br>
Even when wrapped, errors retain their type.
<syntaxhighlight lang="go">
// define an error
var ErrDivisionByZero = errors.New("Cannot divide by 0")
// test for specific error
errors.Is(err, ErrDivisionByZero)
// wrap an error in an ad-hoc error
// ('%w' refers to the original error message)
if err := doThing(); err != nil {
    fmt.Errorf("MyFunction: %w", err)
}
</syntaxhighlight>
{{ expand
{{ expand
| Define, Wrap, Return, and handle Errors
| Define, Wrap, Return, and handle Errors
Line 54: Line 73:
</syntaxhighlight>
</syntaxhighlight>
}}
}}
</blockquote><!-- Example -->
</blockquote><!-- Basics -->


== Returning Errors ==
== Struct Errors ==
<blockquote>
<blockquote>
Go prefers passing error objects as return values to panicking (go's exception-like behaviour).<br>
Errors can be any object that expose the <code>Error() string</code> method.<br>
Errors can be any object that expose the <code>Error() string</code> method.
You can make a struct into an error, and pass it's context on to methods where it is called.


<syntaxhighlight lang="go">
<syntaxhighlight lang="go">
require "errors"
// Define an error type
require "fmt"
type ErrDivisionByZero struct {
FirstNum int
SecondNum int
}


// build ad-hoc error
func (e *ErrDivisionByZero) Error() string {
fmt.Errorf("User does not exist")
return "Cannot divide by zero!"
errors.New("User does not exist")
}
</syntaxhighlight>
</syntaxhighlight>


Functions generally return an error as the last value on fail,<br>
When handling these errors, identify/cast them to a variable to retrieve their methods/fields.<br>
or nil on success
Note that you cannot use <code>errors.Is()</code> to check these types of errors.
<syntaxhighlight lang="go">
<syntaxhighlight lang="go">
func doThing (value int, error) {
if err := doThing(); err != nil {
     if success {
    var divByZero *ErrDivisionByZero
         return 123, nil                          // success
     if errors.As(err, &divByZero) {   // attempt to cast err to ErrDivisionByZero into var 'divByZero'
         fmt.Printf("%d/%d is a division by zero!\n", divByZero.FirstNum, divByZero.SecondNum)
        fmt.Println(err.Error())
     }
     }
     return 0, fmt.Errorf("User does not exist") // fail
     os.Exit(1)
}
}
</syntaxhighlight>
</syntaxhighlight>
</blockquote><!-- Returning Errors -->


== Wrapping Errors ==
{{ expand
<blockquote>
| Here is a more complete example
|
<syntaxhighlight lang="go">
package main


</blockquote><!-- Wrapping Errors -->
import "errors"
import "fmt"
import "os"


== Handling Errors ==
<blockquote>
<syntaxhighlight lang="go">
func doThing {
    var err error


    if err = doThingOne; err != nil {
type ErrDivisionByZero struct {
        if errors.Is(err, SomePreDefinedError) {...}
FirstNum int
        if errors.As(err, SomeWrappedError) {...}
SecondNum int
    }
}
}
</syntaxhighlight>
</blockquote><!-- Handling Errors -->


== Error Types ==
func (e *ErrDivisionByZero) Error() string {
<blockquote>
return "Cannot divide by zero!"
Errors are often pre-defined as package constants
}


<syntaxhighlight lang="go">
func Divide(a int, b int) (result int, err error) {
require "errors"
if b == 0 {
return 0, &ErrDivisionByZero{a, b}
}
return a / b, nil
}


var UserNotExistError = errors.New("User does not exist")
func main() {
var err error


func DoAs(user string) error {
res, err := Divide(10, 0)
    return UserNotExistError
if err != nil {
var divByZero *ErrDivisionByZero
if errors.As(err, &divByZero) {
fmt.Printf("%d/%d is a division by zero!\n", divByZero.FirstNum, divByZero.SecondNum)
fmt.Println(err.Error())
}
os.Exit(1)
}
fmt.Println(res)
}
}
</syntaxhighlight>
</syntaxhighlight>


You can then check for a specific error
}}
<syntaxhighlight lang="go">
</blockquote><!-- Struct Errors -->
require "errors"
 
if err := DoAs("someuser"); err != nil {
    if errors.Is(err) {...}
}
</syntaxhighlight>
</blockquote><!-- Error Verification -->
</blockquote><!-- Errors -->
</blockquote><!-- Errors -->



Revision as of 15:25, 18 June 2022

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

Errors

Basics

Go prefers passing error objects as return values to panicking (go's exception-like behaviour).

Errors can be wrapped (often to add info about where found)
Even when wrapped, errors retain their type.

// define an error
var ErrDivisionByZero = errors.New("Cannot divide by 0")

// test for specific error
errors.Is(err, ErrDivisionByZero)

// wrap an error in an ad-hoc error
// ('%w' refers to the original error message)
if err := doThing(); err != nil {
    fmt.Errorf("MyFunction: %w", err)
}

Define, Wrap, 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)
}

Struct Errors

Errors can be any object that expose the Error() string method.
You can make a struct into an error, and pass it's context on to methods where it is called.

// Define an error type
type ErrDivisionByZero struct {
	FirstNum int
	SecondNum int
}

func (e *ErrDivisionByZero) Error() string {
	return "Cannot divide by zero!"
}

When handling these errors, identify/cast them to a variable to retrieve their methods/fields.
Note that you cannot use errors.Is() to check these types of errors.

if err := doThing(); err != nil {
    var divByZero *ErrDivisionByZero
    if errors.As(err, &divByZero) {   // attempt to cast err to ErrDivisionByZero into var 'divByZero'
        fmt.Printf("%d/%d is a division by zero!\n", divByZero.FirstNum, divByZero.SecondNum)
        fmt.Println(err.Error())
    }
    os.Exit(1)
}

Here is a more complete example

package main

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


type ErrDivisionByZero struct {
	FirstNum int
	SecondNum int
}

func (e *ErrDivisionByZero) Error() string {
	return "Cannot divide by zero!"
}

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

func main() {
	var err error

	res, err := Divide(10, 0)
	if err != nil {
		var divByZero *ErrDivisionByZero
		if errors.As(err, &divByZero) {
			fmt.Printf("%d/%d is a division by zero!\n", divByZero.FirstNum, divByZero.SecondNum)
			fmt.Println(err.Error())
		}
		os.Exit(1)
	}
	fmt.Println(res)
}


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
}