No, not really, but it make me question why you want to print every error…
Is it because all you want is just a single place to do error handling, and otherwise just bubble things up?
If so, that’s what you should unlearn…
— Handle errors where they happen, when they happen unless there’s a strong reason not to. —
If you handle errors locally, you have the context to log the necessary information, or wrap one error type into another to add the extra context for whomever consumes it in the end.
But generally, the moment you logged the error the error indication just becomes a bool…
In case you are writing a library, you could have a -> (result: T, ok: bool)
and -> (result: T, err: Error)
variant of fallible procs, where you put logging in the bool version (use core:log
not core:fmt
) otherwise leave it up to the user.
However, if you are looking for a way to define printers for error types to be used in logging, you are probably looking for fmt.register_user_formatter
.
Example:
package err
import "base:runtime"
import "core:fmt"
import "core:io"
import "core:os"
Reason :: enum {
Super_Green = 0,
Big_Bada_Boom,
}
@(rodata)
REASON_STR := [Reason]string {
.Super_Green = "false alarm",
.Big_Bada_Boom = "call Korben Dallas, he knows how to solve this",
}
Error_Context :: struct {
loc: runtime.Source_Code_Location,
reason: Reason,
as_exit_code: int,
}
fails :: proc(x: int, loc := #caller_location) -> (y: int, err: Maybe(Error_Context)) {
if x > 128 {
err = Error_Context {
loc = loc,
reason = .Big_Bada_Boom,
as_exit_code = 127,
}
} else {
y = x + 1
}
return
}
main :: proc() {
if _, maybe_err := fails(129); maybe_err != nil {
err := maybe_err.?
fmt.eprintfln("%s", err)
os.exit(err.as_exit_code)
}
unreachable()
}
@(init)
init_formatters :: proc() {
@(static) FMTS: map[typeid]fmt.User_Formatter
FMTS = make(type_of(FMTS))
fmt.set_user_formatters(&FMTS)
if fmt.register_user_formatter(Error_Context, fmt_error_context) != nil {
panic("didn't even had a chance")
}
}
@(private)
fmt_error_context :: proc(info: ^fmt.Info, arg: any, verb: rune) -> bool {
if err, ok := arg.(Error_Context); ok {
switch verb {
case 's':
fmt.fmt_value(info, cast(any)err.loc, 's')
io.write_string(info.writer, " error: ", &info.n)
io.write_string(info.writer, REASON_STR[err.reason], &info.n)
io.write_string(info.writer, " (code: ", &info.n)
io.write_int(info.writer, cast(int)err.as_exit_code, 10, &info.n)
io.write_byte(info.writer, ')', &info.n)
return true
case:
fmt.fmt_bad_verb(info, verb)
}
}
return false
}