Golang errors: Difference between revisions
(→Errors) |
|||
Line 4: | Line 4: | ||
= Errors = | = Errors = | ||
<blockquote> | <blockquote> | ||
== | == 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><!-- | </blockquote><!-- Basics --> | ||
== | == Struct Errors == | ||
<blockquote> | <blockquote> | ||
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"> | ||
// Define an error type | |||
type ErrDivisionByZero struct { | |||
FirstNum int | |||
SecondNum int | |||
} | |||
func (e *ErrDivisionByZero) Error() string { | |||
return "Cannot divide by zero!" | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
When handling these errors, identify/cast them to a variable to retrieve their methods/fields.<br> | |||
Note that you cannot use <code>errors.Is()</code> to check these types of errors. | |||
<syntaxhighlight lang="go"> | <syntaxhighlight lang="go"> | ||
if err := doThing(); err != nil { | |||
if | 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) | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
{{ expand | |||
< | | Here is a more complete example | ||
| | |||
<syntaxhighlight lang="go"> | |||
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) | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
}} | |||
</blockquote><!-- Struct Errors --> | |||
} | |||
</blockquote><!-- | |||
</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 useerrors.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-presentIt 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 }