https://rm4n0s.github.io/posts/3-error-handling-challenge/
The challenge is simple to understand. Print a message to the user based on the path of function calls that produced the error, and not based on the error itself.
print three different messages based on the execution path of the functions and error:
- for f4()->
f2()->
f1()->
ErrBankAccountEmpty
print "Aand it's gone"
- for f4()->
f3()->
f1()->
ErrInvestmentLost
print "The money in your account didn't do well"
- for the rest of the cases
print "This line is for bank members only"
also print any type of stack trace for err
Reference Golang
package main
import (
"errors"
"fmt"
"math/rand/v2"
)
var ErrBankAccountEmpty = errors.New("account-is-empty")
var ErrInvestmentLost = errors.New("investment-lost")
func f1() error {
n := rand.IntN(9) + 1
if n%2 == 0 {
return ErrBankAccountEmpty
}
return ErrInvestmentLost
}
func f2() error {
return f1()
}
func f3() error {
return f1()
}
func f4() error {
n := rand.IntN(9) + 1
if n%2 == 0 {
return f2()
}
return f3()
}
func main() {
err := f4()
fmt.Println("Print stack trace", err)
}
Odin Solution
package main
import "core:fmt"
import "core:math/rand"
// my library for type traces
// https://github.com/rm4n0s/trace
import "trace"
F1_Error :: enum {
None,
Account_Is_Empty,
Investment_Lost,
}
F2_Error :: union #shared_nil {
F1_Error,
}
F3_Error :: union #shared_nil {
F1_Error,
}
F4_Error :: union #shared_nil {
F2_Error,
F3_Error,
}
f1 :: proc() -> F1_Error {
n := rand.int_max(9) + 1
if n % 2 == 0 {
return .Account_Is_Empty
}
return .Investment_Lost
}
f2 :: proc() -> F2_Error {
return f1()
}
f3 :: proc() -> F3_Error {
return f1()
}
f4 :: proc() -> F4_Error {
n := rand.int_max(9) + 1
if n % 2 == 0 {
return f2()
}
return f3()
}
main :: proc() {
err := f4()
switch err4 in err {
case F2_Error:
switch err2 in err4 {
case F1_Error:
#partial switch err2 {
case .Account_Is_Empty:
fmt.println("Aand it's gone")
case:
fmt.println("This line is for bank members only")
}
}
case F3_Error:
switch err3 in err4 {
case F1_Error:
#partial switch err3 {
case .Investment_Lost:
fmt.println("The money in your account didn't do well")
case:
fmt.println("This line is for bank members only")
}
}
}
tr := trace.trace(err)
fmt.println("Trace:", tr)
/* Prints randomly:
Aand it's gone
Trace: F4_Error -> F2_Error -> F1_Error.Account_Is_Empty
The money in your account didn't do well
Trace: F4_Error -> F3_Error -> F1_Error.Investment_Lost
This line is for bank members only
Trace: F4_Error -> F3_Error -> F1_Error.Account_Is_Empty
*/
}
ERLANG RESTRICTIONS
As seen in Elixir's Discord: https://discord.com/channels/269508806759809042/269508806759809042/1304465335079931935
Erlang has the following warning box on its documentation page: Developers should rely on stacktrace entries only for debugging purposes. https://www.erlang.org/doc/system/errors.htmlthe-call-stack-back-trace-stacktrace
The VM performs tail call optimization, which does not add new entries to the stacktrace, and also limits stacktraces to a certain depth. Furthermore, compiler options, optimizations, and future changes may add or remove stacktrace entries, causing any code that expects the stacktrace to be in a certain order or contain specific items to fail. The only exception to this rule is the class error with the reason undef which is guaranteed to include the Module, Function and Arity of the attempted function as the first stacktrace entry.
In particular if f1 or any other function is a private function, the erlang compiler may inline the function and thus you will never see a stacktrace entry, because simply the function does not exist