Unified error handling with stack traces for PIC. Use error_t as an intent(out) or intent(inout) argument to propagate errors with context through call chains.
Basic usage: type(error_t) :: err call err%set(ERROR_IO, “failed to open file”) if (err%has_error()) call err%fatal() ! Or using operator: if (.haserror. err) call err%fatal()
Error wrapping (Rust-style “caused by”, outermost first): call low_level_routine(err) if (err%has_error()) then call err%wrap(ERROR_PARSE, “failed to parse input”) return end if
Every mutator (set, clear, wrap, add_context) and the
error_raise helper are pure, so they can be used from the pure
and pure recursive procedures that make up most of PIC’s sorting and
array code. The reporting procedures (get_full_trace, print_trace,
fatal) perform I/O and are therefore necessarily impure: calling
err%fatal() from a pure procedure is a compile error by design.
A pure procedure must return the error to its caller and let an
impure caller report it.
error_t carries no locking of any kind. PIC has threaded code paths
(pic_array exposes set_threading_mode, and sorting can be threaded),
so the rule is:
error_t (a thread-private local, or one
element of a per-thread array), then reduce/inspect them after the
parallel region, orerror_t only from inside a critical
region (or an equivalent lock).Two threads calling set, wrap, add_context or clear on the same
error_t is a data race: message is an allocatable component, so
concurrent writes can corrupt the heap, not merely lose a message.
Reading a shared error_t (has_error, is, get_code) while another
thread writes it is equally a race.
PIC reserves codes 0 .. PIC_ERROR_CODE_MAX (0-99) for itself and will
only ever add new library codes inside that range. Downstream code that
wants its own codes must take them at or above PIC_ERROR_CODE_MAX + 1
(i.e. 100 and up); is_pic_error_code checks a candidate value.
Because code_to_string cannot know a downstream name, pass it:
integer(default_int), parameter :: MYLIB_ERROR_SOCKET = 100 print *, code_to_string(err%get_code(), “MYLIB_ERROR_SOCKET”)
The user_name argument is only consulted for codes PIC does not know,
so it is safe to pass unconditionally.
Error codes
Stack trace configuration
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer(kind=default_int), | public, | parameter | :: | ERROR_ALLOC | = | 5 | |
| integer(kind=default_int), | public, | parameter | :: | ERROR_BOUNDS | = | 7 |
an index or extent was out of range |
| integer(kind=default_int), | public, | parameter | :: | ERROR_GENERIC | = | 1 | |
| integer(kind=default_int), | public, | parameter | :: | ERROR_INTERNAL | = | 6 |
a PIC invariant was violated: this is a bug in the library itself, not bad input from the caller. Report it upstream. |
| integer(kind=default_int), | public, | parameter | :: | ERROR_IO | = | 2 | |
| integer(kind=default_int), | public, | parameter | :: | ERROR_PARSE | = | 3 | |
| integer(kind=default_int), | public, | parameter | :: | ERROR_VALIDATION | = | 4 | |
| integer(kind=default_int), | public, | parameter | :: | PIC_ERROR_CODE_MAX | = | 99 |
Highest error code PIC reserves for itself. Downstream libraries must number their own codes from PIC_ERROR_CODE_MAX + 1 upwards. |
| integer(kind=default_int), | public, | parameter | :: | SUCCESS | = | 0 | |
| integer(kind=default_int), | private, | parameter | :: | MAX_CAUSE_DEPTH | = | 8 | |
| integer(kind=default_int), | private, | parameter | :: | MAX_CAUSE_MSG_LEN | = | 256 | |
| integer(kind=default_int), | private, | parameter | :: | MAX_LOCATION_LEN | = | 128 |
Cause chain configuration |
| integer(kind=default_int), | private, | parameter | :: | MAX_STACK_DEPTH | = | 20 | |
| character(len=*), | private, | parameter | :: | TRUNCATION_MARKER | = | "..." |
Marker used whenever a location or cause message does not fit in its
fixed-length slot. Locations keep their tail (the Unified error type with stack trace support |
Operator for checking error state: if (.haserror. err) then
Check if an error is set
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(in) | :: | self |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| character(len=MAX_LOCATION_LEN), | public | :: | call_stack(MAX_STACK_DEPTH) |
Call locations |
|||
| integer(kind=default_int), | public | :: | cause_codes(MAX_CAUSE_DEPTH) |
Error codes of wrapped causes |
|||
| integer(kind=default_int), | public | :: | cause_depth | = | 0 |
Number of wrapped causes |
|
| character(len=MAX_CAUSE_MSG_LEN), | public | :: | cause_messages(MAX_CAUSE_DEPTH) |
Messages of wrapped causes |
|||
| integer(kind=default_int), | public | :: | code | = | SUCCESS |
Error code (0 = no error) |
|
| character(len=:), | public, | allocatable | :: | message |
Error message |
||
| integer(kind=default_int), | public | :: | omitted_causes | = | 0 |
Number of intermediate causes dropped because the cause chain was already MAX_CAUSE_DEPTH deep. The root cause is always kept; it is the middle layers that are lost, and the traces report how many. |
|
| integer(kind=default_int), | public | :: | stack_depth | = | 0 |
Current stack depth |
| procedure, public :: add_context => error_add_context | |
| procedure, public :: clear => error_clear | |
| procedure, public :: fatal => error_fatal | |
| procedure, public :: get_code => error_get_code | |
| procedure, public :: get_full_trace => error_get_full_trace | |
| procedure, public :: get_message => error_get_message | |
| procedure, public :: has_error => error_has_error | |
| procedure, public :: is => error_is | |
| procedure, public :: print_trace => error_print_trace | |
| procedure, public :: set => error_set | |
| procedure, public :: wrap => error_wrap |
Map an error code to a human-readable name
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer(kind=default_int), | intent(in) | :: | code | |||
| character(len=*), | intent(in), | optional | :: | user_name |
True when code falls inside the range PIC reserves for its own
error codes (0 .. PIC_ERROR_CODE_MAX). Downstream libraries can
assert their own codes are outside it:
if (is_pic_error_code(MYLIB_ERROR_SOCKET)) error stop “code clash”
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer(kind=default_int), | intent(in) | :: | code |
Fit a location into MAX_LOCATION_LEN keeping its TAIL, marking any truncation with a leading TRUNCATION_MARKER.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| character(len=*), | intent(in) | :: | location |
Fit a cause message into MAX_CAUSE_MSG_LEN keeping its HEAD, marking any truncation with a trailing TRUNCATION_MARKER.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| character(len=*), | intent(in) | :: | message |
Get the error code
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(in) | :: | self |
Get complete error message with cause chain and stack trace Returns a dynamically-sized multi-line string
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(in) | :: | self |
Get the error message (without stack trace)
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(in) | :: | self |
Check if an error is set
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(in) | :: | self |
Check if the error matches a specific error code
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(in) | :: | self | |||
| integer(kind=default_int), | intent(in) | :: | code |
One-line notice that count intermediate causes were dropped
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer(kind=default_int), | intent(in) | :: | count |
Set an optional error argument, if the caller supplied one.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(error_t), | intent(inout), | optional | :: | err | ||
| integer(kind=default_int), | intent(in) | :: | code | |||
| character(len=*), | intent(in) | :: | message |
Add a call location to the stack trace Typically called when propagating errors upward
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(inout) | :: | self | |||
| character(len=*), | intent(in) | :: | location |
Clear the error state, stack trace, and cause chain
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(inout) | :: | self |
Print the error trace and stop the program Use for unrecoverable errors
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(in) | :: | self | |||
| integer(kind=default_int), | intent(in), | optional | :: | unit | ||
| integer(kind=default_int), | intent(in), | optional | :: | exit_code |
Print error with cause chain and stack trace to specified unit If unit not specified, prints to stdout
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(in) | :: | self | |||
| integer(kind=default_int), | intent(in), | optional | :: | unit |
Set an error with code and message Resets the stack trace and cause chain
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(inout) | :: | self | |||
| integer(kind=default_int), | intent(in) | :: | code | |||
| character(len=*), | intent(in) | :: | message |
Wrap the current error with a higher-level context Pushes the current error into the cause chain and sets a new top-level code and message (Rust-style “caused by”)
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(error_t), | intent(inout) | :: | self | |||
| integer(kind=default_int), | intent(in) | :: | code | |||
| character(len=*), | intent(in) | :: | message |