(DISCLAIMER: the article may topple a benevolent dictator)
Introduction
In the previous article, I compared Golang’s and Odin’s error handling, and I said why Go’s error types suck.
However, some Golang developers told me that it is possible to do the same things with errors.Is(), errors.As(), fmt.Errorf() or other library.
At that point, I realized that these developers didn’t understand the problem and for that reason, I made it as an exercise for them to solve.
Nevertheless, this exercise it is not only for Golang developers, but for all the developers. Everybody has the chance to prove that their favorite programming language is better than Odin’s superior error handling.
I challenge you ALL to the “Error Handling Challenge!”
The 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.
The exercise is in Golang, and it has in comments the requirements to complete the task.
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()
// 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
fmt.Println("Print stack trace", err)
}
Please don’t change the logic, the number of functions and the messages.
However, you can change the types of errors, add more return values, global values and use any library you want.
Furthermore, you can rewrite this exercise in any programming language you want, as long as you keep the logic the same. If your programming language is OOP, then use static methods in the same object.
Yet, before you start solving it, I want you to know that I believe no language can solve this challenge except Odin.
Because Odin it is not just a programming language, it is a holy teacher.
Odin’s holy teachings
To solve this task in Odin, you have to follow the unwritten verses from Odin’s source code:
- Use Enum, Union and Struct for errors, no strings or other types of pointers
- Define a type as an error by adding “_Error” at the end of the name.
- Give one or more error types for each procedure no matter the race, sex, religion and political beliefs
- If a procedure does not produce its own errors, then unionize the errors from the functions it calls.
- To create messages for users, parse the errors with switch/case, if conditions or “core:reflect” library.
- Replace stack traces with type traces.
Odin’s teachings in action
Here I solve the above exercise in Odin, using the holy verses.
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
*/
}
As you can see, Odin’s holy teachings not only solve difficult challenges, but also it supports developers’ rights.
But let me guess, you don’t even know your rights and for that reason, you let your favorite programming language treat you horrible.
I will educate you, my brothers and sisters, about your rights.
Developers’ rights
- To read the errors of a function without reading the function’s code.
- Spent your time in thinking, not searching.
- To print a stack trace for each error.
- The way is more important than the destination.
- To create error messages for users from parsing the trace of functions that produced the error.
- DON’T use the same language for users, administrators, and developers.
No programming language can give us all the rights, except Odin.
Odin is the evolution
Start the revolution
If you feel woke now and ready to protest for your rights, here is a list of chants:
- In Union there is strength, for errors to transcend
- We want non-binary errors, no more exception terrors
- Errors unite! in a union type!
- Two, Four, Six, Eight! How Do You Know Your Errors Are Traced?
- Who’s got the power? We’ve got the power! What kind of power? Union power!
- Get up, get down, Odin is a union town!
- We don’t want exceptions, we want union inceptions!
- Developers rights are human rights!
Languages that passed the challenge
Here is a list of implementations per programming language that solved the exercise.
I will not judge how each implementation is solved. That is for you to decide. But I will put them in order based on personal preference.