Safe Divide
Some failures in Go are not error values. Dividing by zero, indexing past the end of a slice, calling a method on a nil pointer: those panic. A panic unwinds the stack, running every deferred call on the way, and if nothing stops it the program dies with a stack trace.
recover stops it:
func safe() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
risky()
return nil
}
Three things make that work, and all three are easy to get wrong.
recover only does anything inside a deferred function of the call that is
panicking. Called anywhere else it returns nil and the panic carries on.
The value it returns is an any - whatever was handed to panic. A runtime
failure hands you a runtime.Error; your
own code can hand over anything, and an error is usually the right choice
because the recoverer can pass it straight on.
And the recovery is only useful if the function can still report something,
which is why these functions use named results: the deferred closure sets
err after the return has already chosen its values.
Panicking is not Go's error handling. Return an error for anything a caller
could reasonably expect. Recover at a boundary, where letting one bad call kill
everything would be worse than reporting it.
Task
Fill in the four functions in main.go.
SafeDivide(a, b int) (int, error)divides, and turns the divide-by-zero panic into"safe divide: <what happened>"with a result of0.MustPositive(n int) intreturnsn, or panics withErrNotPositivewhennis zero or negative.SafeIndex(nums []int, i int) (int, error)is the same shape asSafeDivide, for"safe index: ...".Guard(body func()) errorrunsbodyand converts a panic into an error: anerrorpanic value comes back unchanged, anything else is formatted as"panic: <value>".
Hints
- The tests check that the message carries the recovered text, so an
if b == 0guard that never divides will not pass. Let it panic and catch it. if r := recover(); r != nil { ... }is the whole idiom. Keep it in the deferred literal.- Reset the value result as well as setting
err. Thereturn a / bnever completed, but a partially-set result is exactly the plausible-looking answer a careless caller would use. Guardneeds a type assertion,r.(error), in its comma-ok form:err, ok := r.(error). Type assertions get a lesson of their own later; the comma-ok shape is the one you already know.errors.Isin the tests compares identity, so panic withErrNotPositiveitself rather than a new error with the same text.