{"id":19134663,"url":"https://github.com/marktsuchida/richerrors","last_synced_at":"2026-06-25T10:31:07.840Z","repository":{"id":46894310,"uuid":"327999168","full_name":"marktsuchida/RichErrors","owner":"marktsuchida","description":"Rich error handling for legacy C and C++ projects (beta)","archived":false,"fork":false,"pushed_at":"2023-10-17T21:39:24.000Z","size":708,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-13T10:03:23.802Z","etag":null,"topics":["c","error-handling"],"latest_commit_sha":null,"homepage":"https://marktsuchida.github.io/RichErrors/","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marktsuchida.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-08T19:55:37.000Z","updated_at":"2022-07-11T17:46:55.000Z","dependencies_parsed_at":"2024-11-09T06:28:32.722Z","dependency_job_id":"15f0b307-e2e3-40d2-a044-82fdfab3ac1a","html_url":"https://github.com/marktsuchida/RichErrors","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/marktsuchida/RichErrors","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2FRichErrors","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2FRichErrors/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2FRichErrors/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2FRichErrors/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marktsuchida","download_url":"https://codeload.github.com/marktsuchida/RichErrors/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marktsuchida%2FRichErrors/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284194067,"owners_count":26963045,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-13T02:00:06.582Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","error-handling"],"created_at":"2024-11-09T06:27:41.041Z","updated_at":"2025-11-13T10:03:24.996Z","avatar_url":"https://github.com/marktsuchida.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nThis file is part of RichErrors.\nCopyright 2019-2022 Board of Regents of the University of Wisconsin System\nSPDX-License-Identifier: BSD-2-Clause\n--\u003e\n\n# RichErrors - Error handling for C\n\nRichErrors is a small C library for doing a little better than returning\ninteger codes for errors. It does not do anything akin to exception handling\n(such as in C++), but makes it easy to pass detailed error information up the\ncall chain.\n\n## RichErrors Core (`RichErrors.h`)\n\nAn error in RichErrors is represented with the opaque type `RERR_ErrorPtr`.\nThis type behaves similarly to a pointer, but is not always a pointer, so only\nRichErrors API functions should be used to access its values.\n\nAn error consists of the following:\n\n- A string message\n- An optional 32-bit integer code, together with an error \"domain\" indicating\n  different subsystems to which the code may belong. The domain is required if\n  a code is included.\n- An optional \"info map\", which is an arbitrary string-to-value map. Values can\n  be string, boolean, 64-bit signed integer, 64-bit unsigned integer, or 64-bit\n  floating point. RichErrors does not provide any rules for info map contents.\n- An optional \"cause\", which is a nested error. This is used to wrap low-level\n  errors inside higher-level errors, to provide a causal chain.\n\nError objects are immutable: once created, none of their contents may change.\n\n## Basic Usage\n\nTo return a basic error:\n\n```C\nRERR_ErrorPtr foo(...)\n{\n    // ...\n    if (something_went_wrong()) {\n        return RERR_Error_Create(\"Something went wrong\");\n    }\n}\n```\n\nThis is the minimal usage, and calling code will not be able to\nprogrammatically determine the nature of the error. The advantage is that it is\nvery easy to write (potentially dynamically generated) error messages, so that\nprogrammers will (hopefully) be motivated to do proper error handling.\n\nTo return an error with an error code from some subsystem \"Bar\":\n\n```C\n#define BAR_ERROR_DOMAIN \"BarSystem\"\n\n// Call e.g. RERR_Domain_Register(BAR_ERROR_DOMAIN, RERR_CodeFormat_I32)\n// during initialization.\n\nRERR_ErrorPtr foo2(...)\n{\n    // ...\n    BarErrorCode err = BarSystemFunction(...);\n    if (err != BarError_OK) {\n        return RERR_Error_CreateWithCode(BAR_ERROR_DOMAIN, err,\n                \"Something went wrong in Bar\");\n    }\n}\n```\n\nIf, as is often the case, the subsystem Bar offers a function to fetch a string\nmessage for each error code (similar to `strerror()`), then one mighe write a\nsmall wrapper function that takes a Bar error code and returns an\n`RERR_ErrorPtr` with the corresponding message.\n\nTo return an out-of-memory error:\n\n```C\n// ...\nvoid *buffer = malloc(1000000000);\nif (buffer == NULL) {\n    return RERR_Error_CreateOutOfMemory();\n}\n```\n\nThe out-of-memory error behaves just like any other error, except that it is\nimplemented as a special pointer value with no associated dynamically allocated\nmemory, so that it is guaranteed to work even when heap memory is unavailable.\nAll RichErrors functions return an out-of-memory error when internal memory\nallocation fails.\n\nTo check an error return value:\n\n```C\n// ...\nRERR_ErrorPtr err = foo(...);\nif (err != RERR_NO_ERROR) {\n    MyErrorHandler(RERR_Error_GetMessage(err));\n    RERR_Error_Destroy(err);\n}\n// ...\n```\n\nNote that all errors (including out-of-memory errors) must be destroyed. The\nonly exception is `RERR_NO_ERROR`.\n\nTo ignore an error return value:\n\n```C\n// ...\nRERR_Error_Destroy(foo(...));\n// ...\n```\n\nIt is safe to destroy an `RERR_NO_ERROR`. This makes it explicit that errors\nare being ignored. If you simply ignore the returned error, you will leak\nmemory every time an error occurs. (And a leak checker could be used to detect\nunhandled errors--perhaps a future version of RichErrors itself will provide\nsuch instrumentation.)\n\n## Error Code Domains\n\nMany complex systems consist of multiple subsystems, each of which may define\ntheir own error codes. For this reason, RichErrors requires all errors with\ncodes to indicate which \"domain\" they belong to. Domains are essentially\nnamespaces for error codes.\n\nDomains are C strings, and it is assumed that static string constants will be\nused.\n\nDomains need to be registered before use, to prevent clashing domains (this may\nbecome configurable in future versions).\n\n## RichErrors Design Notes\n\nIf the system or application consists of multiple DLLs, a single, central DLL\nshould link to RichErrors. All other DLLs can then call API methods of the\ncentral DLL that wrap RichErrors functions. In this way, all resource\nallocation and deallocation for RichErrors occurs in the central DLL, meaning\nthat it is safe to pass errors (`RERR_Error_Ptr`) across DLL boundaries, even\nif the DLLs each use a different C runtime. (This matters mostly on Windows\nwhere different versions and configurations of MSVC use different runtimes.)\n\n## Err2Code (`Err2Code.h`)\n\nErr2Code is an add-on feature based on RichErrors, which helps with\nretrofitting rich error handling on top of a legacy API (ABI) that uses integer\nerror codes (so long as the error code type is at least 32 bits wide).\n\nImagine that you have a service-provider interface in some system. A large\nnumber of modules (possibly developed by different parties) implements this\ninterface. The interface consists of functions that return an integer error\ncode. Now you want to let the implementations return more detailed information\nwith errors, but you cannot convert all implementations at the same time. You\nalso don't want to maintain multiple versions of the interface.\n\nThis is where Err2Code comes in. You provide a way for individual modules to\ndeclare that they return rich error information (this needs to happen before\nany other module functions are called, for obvious reasons). You also expose to\nthe modules an API function that converts an `RERR_Error_Ptr` into an `int32_t`\n(or whatever integer type the interface uses for error codes). This function\nwould internally call `RERR_ErrorMap_RegisterThreadLocal()`.\n\nNew or updated modules can now use RichErrors for internal error handling. At\nthe interface boundary, they can call the API method to obtain an integer error\ncode, which they return to the system. Older modules continue to use plain\ninteger error codes, through the same ABI.\n\n```C\n// In module implementing service provider API:\nint Foo(...)\n{\n    RERR_ErrorPtr err = Bar();\n    if (err != RERR_NO_ERROR) {\n        // ErrorAsReturnCode() is a system-provided API function that\n        // internally calls RERR_ErrorMap_RegisterThreadLocal().\n        return ErrorAsReturnCode(err);\n    }\n    // ...\n}\n```\n\n```C\n// In system code calling the modules:\n// ...\nint err = Foo(...); // In module DLL\nif (err != 0) {\n    RERR_ErrorPtr richErr;\n    if (the_module_uses_rich_errors()) {\n        richErr = RERR_ErrorMap_RetrieveThreadLocal(codeMap, err);\n    }\n    else {\n        richErr = RERR_Error_CreateWithCode(LEGACY_CODE_DOMAIN, err,\n                \"An error occurred\");\n    }\n    return richErr;\n}\n// ...\n```\n\n(In real code, the call to `Foo()` might occur through a function pointer.)\n\n## Err2Code Design Notes\n\nErr2Code uses a map from the integer error codes to registered error objects.\nThe map is (at least conceptually) per-thread.\n\nWhy not change the error code type to `intptr_t` and return the pointer\ndirectly?\n\n- Because that would break the API on 64-bit systems (assuming the original\n  error code type is 32-bit). Note that more than one module may participate in\n  a call chain (through callbacks), and these may be a mixture of updated and\n  legacy modules. In such cases, error codes originating from an updated module\n  (with rich error information) may need to pass through legacy modules that\n  still consider errors to be plain integer codes. If we changed the size of\n  the error code type, all modules would need to be modified to use the new\n  type.\n\n- Using a 64-bit type can also cause ambiguity if negative error codes are in\n  use. Sign-extending the code would probably work most of the time, but some\n  error codes out there are not supposed to be interpreted as signed integers\n  (e.g. Windows `HRESULT` codes).\n\nWhy use a per-thread map for assigned error codes?\n\n- Primarily for resource-management reasons. Again note that the call chain may\n  bounce between multiple modules, some of which may be legacy modules that\n  treat error codes as plain integers. Because we cannot count on such modules\n  notifying the system of \"discarded\" codes, the only way we can determine when\n  it is safe to discard the error information mapped to a code is when the call\n  stack pops to the top-level system. At that time, we know that all error\n  codes assigned on the particular thread can be released. Further assumptions\n  may be possible depending on system behavior.\n\n- Because of this design, all modules need to observe the rule that error codes\n  mapped on one thread must not be shared with other threads. This can be\n  enforced in new code that uses rich error information. In theory, legacy\n  modules may be sharing error codes between threads, and this would break our\n  design. We assume that such cases are rare enough to be dealt with\n  individually as long as we can detect them. By using globally sequential\n  error codes, codes returned (or pushed) on the wrong thread can be flagged\n  with very high probability.\n\nHow do we deal with a call stack mixing modules with modernized and legacy\nerror handling?\n\n- For example, assume a Core DLL uses Err2Code to assign codes to errors.\n  Modules A and B implement an interface provided by Core, but only Module B\n  uses rich error information; module A treats error codes as plain integers.\n\n- Things get complicated when Core calls Module A, which calls a callback into\n  Core, which in turn calls into Module B. Supppose an error occurs in Module\n  B. Module B produces rich error information and calls the Core API to assign\n  an integer code to it. It then returns the code to Core. The callback returns\n  the code to Module A, which passes it straight through back to Core as the\n  return value of the first call out of Core. Now Core has the problem that it\n  cannot determine for sure whether the returned error code is a plain integer\n  code from Module A, or one that is mapped to rich error information from\n  Module B.\n\n- However, it should be noted that Core has the knowledge that Module A uses\n  legacy error handling and Module B uses rich error handling. Therefore, upon\n  Module B returning a code mapped to a rich error, it can substitute a fixed\n  code that does not clash with any of the plain error codes used or handled by\n  Module A. When Module A returns, Core can now unambiguously determine whether\n  any returned code came from Module A or was the substitute code for the\n  pending rich error from Module B.\n\n- A few assumptions must be met for all of this to work perfectly, and the\n  details will likely vary from system to system. Most importantly, there needs\n  to be at least one error code that is not used by legacy modules. If modules\n  communicate with each other directly, there had better be a reasonably large\n  range of common unused error codes that can be used as the range for Err2Code\n  mapping.\n\nIn general the fidelity of error handling via Err2Code mirrors the quality of\nthe original code. If error codes are already clearly defined and used\nconsistently throughout the system, then Err2Code can map a range of codes that\nwere never in use, so that no clashes occur. If no policy on error codes was\nenforced to begin with in a system with many modules, then in all likelyhood\nproblems with clashing error codes are already present, and Err2Code will\nprobably not make the situation much worse if used carefully (and in the long\nrun will help eliminate any clashes).\n\n## Performance\n\nRichErrors will not add much overhead when no errors occur, because functions\nwill simply be returning null. In the case that errors do occur, we assume that\nefficiency is not a primary concern, and that errors do not contain large\namounts of data.\n\nDon't use errors as a substitute for control flow.\n\n## C++ API\n\nIt makes sense to have a C API (and ABI) between DLLs, yet use C++ for internal\nimplementation of each DLL. To support error handling in such cases, RichErrors\nhas a C++ API that is a wrapper around the C objects. The C++ API is a\nheader-only library that depens on the C API.\n\n## Building RichErrors\n\nMeson is used for building.\n\n```sh\nmeson build/\ncd build\nninja\nninja test\nninja install\n```\n\nTo build with Visual C++ on Windows, `meson` and `ninja` need to be run from\nthe Visual Studio Tools Command Prompt.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarktsuchida%2Fricherrors","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarktsuchida%2Fricherrors","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarktsuchida%2Fricherrors/lists"}