The README was 794 lines: the summary, the design rationale, the whole macro reference, the threading contract, the build internals and the exit-status specification in one file. It is now 178 lines -- summary, installation, quickstart, and an index -- and the reference material lives in docs/, one file per topic: architecture, usage, status-codes, uncaught-errors, exit-status, thread-safety, building. The prose moved as written. Inbound references followed it: UPGRADING.md, TODO.md, include/akerror.tmpl.h and tests/err_threads_handoff.c now name the docs/ file that owns the text they cite, and AGENTS.md says where new documentation goes so the README does not grow back. Five factual errors fixed in the moved text: - Both NULL-pointer examples inverted their test. FAIL_ZERO_* fails when the expression is zero, so `(somePointer == NULL)` failed on a *valid* pointer. They now read `(somePointer != NULL)`. - AKERROR_NOIGNORE, four times including the #define, is AKERR_NOIGNORE. - FINISH_NORExbTURN is FINISH_NORETURN. - "functiions" is "functions". - The architecture link pointed at include/akerror.h, which is generated and not in the tree; it points at include/akerror.tmpl.h. The quickstart is new text. It compiles under -Wall -Wextra -Werror and was run through all three of its paths: handled usage error exits 0, unhandled IO error prints a trace and exits with the status, success exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
179 lines
7.0 KiB
Markdown
179 lines
7.0 KiB
Markdown
# Summary
|
|
|
|
This library provides a TRY/CATCH style exception handling mechanism for C.
|
|
|
|

|
|
|
|
# Why?
|
|
|
|
There is nothing wrong with C as it is. This library does not claim to fix some problem with C.
|
|
|
|
Instead, this library implements a pragmatic and stylistic choice to assist the programmer in better handling errors in their programs. Vanilla C provides everything you need to do this out of the box, but this library makes it easier to avoid pointing certain guns at your foot, and when you do, it provides better context with those errors to help you more quickly recover.
|
|
|
|
Why? Because some programmers prefer to have the power of C with just a little bit of help in managing their errors.
|
|
|
|
This library has 6 guiding principles:
|
|
|
|
* Manually checking every possible return code for every possible meaning of that return code is tedious and prone to miss unpredicted failure cases
|
|
* Functions should return rich descriptive error contexts, not values
|
|
* Uncaught errors should cause program termination with a stacktrace
|
|
* Dynamic memory allocation is the source of many errors and should be avoided if possible
|
|
* Manipulating the call stack directly is error prone and dangerous
|
|
* Declaring, capturing, and reacting to errors should be intuitive and no more difficult than managing return codes
|
|
|
|
# Documentation
|
|
|
|
| Document | What it answers |
|
|
| -------- | --------------- |
|
|
| [docs/architecture.md](docs/architecture.md) | What an error context is, how one travels up the call stack, and what the macros build |
|
|
| [docs/usage.md](docs/usage.md) | The macro reference: `ATTEMPT`/`CLEANUP`/`PROCESS`/`FINISH`, `CATCH`, `FAIL_*`, `PASS`, `HANDLE`, `SUCCEED_RETURN` |
|
|
| [docs/status-codes.md](docs/status-codes.md) | Defining your own status codes, and reserving a range so two libraries cannot collide |
|
|
| [docs/uncaught-errors.md](docs/uncaught-errors.md) | `AKERR_NOIGNORE`, what a stack trace looks like, and how to read one |
|
|
| [docs/exit-status.md](docs/exit-status.md) | Why you call `akerr_exit()` and never `exit()`, and how to replace the unhandled-error handler |
|
|
| [docs/thread-safety.md](docs/thread-safety.md) | What thread safety here covers, what it does not, and how to hand an error to another thread |
|
|
| [docs/building.md](docs/building.md) | Configure options, the generated header, and building without stdlib |
|
|
| [UPGRADING.md](UPGRADING.md) | What changed in 1.0.0, 2.0.0 and 2.0.1, and how to migrate |
|
|
| [TODO.md](TODO.md) | Known defects, ordered by blast radius |
|
|
|
|
# Installation
|
|
|
|
```bash
|
|
cmake -S . -B build
|
|
cmake --build build
|
|
cmake --install build
|
|
```
|
|
|
|
The library depends on `stdlib` and on POSIX threads. Both are optional at the
|
|
cost of some functionality — see [docs/building.md](docs/building.md) for
|
|
`-DAKERR_USE_STDLIB=OFF` and
|
|
[docs/thread-safety.md](docs/thread-safety.md#building-single-threaded) for
|
|
`-DAKERR_THREADS=none`.
|
|
|
|
## Setting up your project
|
|
|
|
Include it
|
|
|
|
```c
|
|
#include <akerror.h>
|
|
```
|
|
|
|
Link the library directly, or
|
|
|
|
```sh
|
|
cc -lakerror
|
|
```
|
|
|
|
Using pkg-config, or
|
|
|
|
```sh
|
|
pkg-config akerror --cflags
|
|
pkg-config akerror --ldflags
|
|
```
|
|
|
|
Using cmake:
|
|
|
|
```cmake
|
|
find_package(akerror REQUIRED)
|
|
pkg_check_modules(akerror REQUIRED akerror)
|
|
target_link_libraries(YOUR_TARGET PRIVATE akerror::akerror)
|
|
```
|
|
|
|
Using this project as a submodule with cmake:
|
|
|
|
```cmake
|
|
add_subdirectory(deps/libakerror EXCLUDE_FROM_ALL)
|
|
|
|
target_link_libraries(YOUR_PROJECT PRIVATE akerror::akerror)
|
|
```
|
|
|
|
# Quickstart
|
|
|
|
A function that can fail returns an `akerr_ErrorContext *` instead of a value,
|
|
and moves its real output to a pointer parameter. `AKERR_NOIGNORE` makes the
|
|
compiler complain if a caller throws that return away.
|
|
|
|
```c
|
|
#include <akerror.h>
|
|
#include <stdio.h>
|
|
|
|
/* Fails with a message; the caller finds out what and where. */
|
|
static akerr_ErrorContext AKERR_NOIGNORE *open_config(const char *path, FILE **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
|
|
FAIL_ZERO_RETURN(errctx, (path != NULL), AKERR_NULLPOINTER,
|
|
"no config path was given");
|
|
|
|
*dest = fopen(path, "r");
|
|
FAIL_ZERO_RETURN(errctx, (*dest != NULL), AKERR_IO,
|
|
"could not open %s", path);
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
FILE *config = NULL;
|
|
|
|
PREPARE_ERROR(errctx);
|
|
ATTEMPT {
|
|
FAIL_ZERO_BREAK(errctx, (argc == 2), AKERR_VALUE,
|
|
"usage: %s <config>", argv[0]);
|
|
CATCH(errctx, open_config(argv[1], &config));
|
|
/* ... read the config ... */
|
|
} CLEANUP {
|
|
/* Runs whether or not anything failed. */
|
|
if ( config != NULL ) {
|
|
fclose(config);
|
|
}
|
|
} PROCESS(errctx) {
|
|
} HANDLE(errctx, AKERR_VALUE) {
|
|
/* A usage error is ours to handle, so handle it and carry on. */
|
|
} FINISH_NORETURN(errctx);
|
|
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
`ATTEMPT` is where work that can fail goes. `CLEANUP` always runs. `PROCESS`
|
|
opens the handler section, and each `HANDLE` claims one status. Anything no
|
|
`HANDLE` claims is still an error when it reaches `FINISH`: inside a function,
|
|
`FINISH(errctx, true)` returns it to your caller; at the top, as above,
|
|
`FINISH_NORETURN(errctx)` prints the stack trace and ends the process. You do
|
|
not need to call `akerr_init()` — every entry point does it for you.
|
|
|
|
Three things worth knowing before you write much more than that:
|
|
|
|
* [The full macro reference](docs/usage.md), including `PASS` for errors you
|
|
cannot do anything about, and `HANDLE_GROUP` for several statuses that fail
|
|
the same way.
|
|
* [Never use `CATCH` or a `FAIL_*_BREAK` macro inside a loop](docs/usage.md#important-do-not-use-catch-or-fail__break-inside-a-loop).
|
|
They leave the `ATTEMPT` with a C `break`, which only escapes the innermost
|
|
loop. This is the first thing that bites people.
|
|
* [Never call `exit()` with a status. Call `akerr_exit()`](docs/exit-status.md).
|
|
An exit status is a byte and every consumer status starts at 256, so `exit()`
|
|
truncates status 256 to 0 and reports success.
|
|
|
|
# Thread safety
|
|
|
|
The library is thread safe as built by default: every entry point may be called
|
|
from any thread at any time, and an error context may be handed from one thread
|
|
to another and released there. There is one recursive lock covering the pool and
|
|
the registry, so error construction is serialized — a program that raises errors
|
|
on its hot path will feel it. See [docs/thread-safety.md](docs/thread-safety.md)
|
|
for what that covers, what it cannot, and the handoff pattern.
|
|
|
|
# Upgrading
|
|
|
|
2.0.1 fixes an unhandled error killing the process and still reporting success:
|
|
the exit code was the status truncated to a byte, and every consumer status
|
|
starts at 256. Use `akerr_exit()` instead of `exit()` — see
|
|
[docs/exit-status.md](docs/exit-status.md). No ABI break.
|
|
|
|
2.0.0 makes the library thread safe. That is an ABI break — `__akerr_last_ignored`
|
|
became thread-local storage and the pool now takes its own reference — so
|
|
everything built against a 1.x header must be rebuilt. 1.0.0 replaced the
|
|
consumer-sized status-name array with a private, ownership-enforced registry.
|
|
See [UPGRADING.md](UPGRADING.md) for all three, what was removed, how to migrate,
|
|
the capacity limits and how to raise them, and the thread-safety rules.
|