{"slug": "generic-containers-in-c-result", "title": "Generic Containers in C: Result", "summary": "Martin Uecker published a design for a generic `result` type in C on 2026-10-24, built as a macro wrapping an algebraic `sum` type that pairs a value with an `error_t` string and carries the `[[nodiscard]]` attribute so compilers warn when a returned result is ignored. The design, part of Uecker's noplate library, provides `result_fail` and `result_success` constructors plus `result_ok`, `result_value` and `result_error` accessors, and Uecker notes the macro-based generics require type arguments to be typedef names. Uecker also observed that Anthropic uses sanitizers to verify generated RISC-V C code, an approach similar to his own work on proving safety properties at compile time with GCC's optimizer and sanitizers.", "body_md": "Martin Uecker, 2026-10-24\n\t\n\n\tPreviously, I discussed a [span type](./2025-07-02.html), \n\t[a vector type](./2025-07-20.html), an optional type\n\tcalled [maybe](./2025-08-10.html), and a variant type\n\t[variadic](./2025-09-21.html) (now renamed to\n\t`variant` which makes more sense). I experiment with these types in my\n\t[noplate library](https://codeberg.org/uecker/noplate/). A couple\n\tof months ago, I explored how GCC's optimizer in combination with sanitizer can  be (mis-)used to prove \n\t[safety properties at compile-time](https://uecker.codeberg.page/2026-05-22.html).\n\tInterestingly, the idea of using sanitizers for proving safety properties is\n\talso used by Anthropic to [verify](https://www.anthropic.com/research/formal-verification)\n\tC code, although there it is applied to the generated RISC-V code.\n\t\n\n\tToday, I want to discuss the design of a generic `result` type. \n\tWe want a type that behaves similar to `maybe`, but which can\n\tnot be ignored without a warning and which also encodes an error message.\n\t\n\n```\nstatic result(int) divide(int a, int b)\n{\n\tif (b == -1 && a == INT_MIN)\n    \t\treturn result_fail(int, \"overflow in division\");\n\n\tif (b == 0)\n\t\treturn result_fail(int, \"division by zero\");\n\n\treturn result_success(int, a / b);\n}\n```\n\n\tWe start by defining the result type as a generic macro that simply wraps\n\tan algebraic `sum` type that contains the type of the result\n\tand an error type that simply wraps a string. \n\t\n\n``` js\ntypedef struct { const char *error; } error_t;\n#define result(T) struct [[nodiscard]] result_##T { sum(T, error_t) sum; }\n```\n\n\tHere, we used the `[[nodiscard]]` attribute that tells the\n\tcompiler to emit a warning when the result is ignored when returned\n\tfrom a function.\n\t\n\n\tSide note: A known limitation of our macro-based generics is that the\n\ttype arguments must be typedef names, but this is only a minor\n\tinconvenience which hopefully can be addressed in the future. I still\n\tlike to make the following observation at this point. An alternative design that\n\tuses tagless structures can use arbitrary type names, because it does not need\n\tto construct a new tag using token concatenation (`##`). Such types\n\tthen always require a single unique declaration before use. But this has a major problem:\n\tWhere do you place the declaration for `result(T)` and\n\t`sum(T, error_T)` for various types `T`?  These generic\n\ttypes should be vocabulary types that can freely be used anywhere.\n\t\n\n\tLet's continue constructing `result` by adding two constructors.\n\t\n\n```\n#define result_fail(T, e) \t(result(T)){ sum_init(T, error_t, (error_t){ (e) }) }\n#define result_success(T, v) \t(result(T)){ sum_init(T, error_t, (v)) }\n```\n\nAnd finally also some accessor functions.\n\n```\n#define result_ok(T, r)\t\t(!(r).sum.sel)\n#define result_value(T, r)\tsum_car(r.sum)\n#define result_error(T, r)\tsum_cdr(r.sum)\n```\n\n\tThe `sum` type is simply a tagged union which works similar as\n\t`maybe` as it traps when the inactive member is accessed.  \n\tI will write about it another time, as the design of the algebraic\n\ttypes is not yet completed.\n\t\n\n \n\n\tNow let's look how it works in an example ([godbolt](https://godbolt.org/z/7Y8q8szhP)).\n\t\n\n```\nint main()\n{\n    int a = 2;\n    int b = 1;\n\n    divide(a, b);\n}\n\n: In function 'main':\n:34:5: warning: ignoring return value of type 'struct result_int', declared with attribute 'nodiscard' [-Wunused-result]\n   34 |     divide(a, b);\n      |     ^~~~~~~~~~~~~~~~~\n:17:20: note: in call to 'divide', declared here\n   17 | static result(int) divide(int a, int b)\n```\n\n\tIf we ignore the return value, we get the expected warning. Of course,\n\t`divide` is a poor example for this, because ignoring the result\n\tis not a problem. For certain other functions this is very useful.\n\t\n\n \n\n\tNow let's consider a complete example ([godbolt](https://godbolt.org/z/491ec3n4T)).\n\n```\nint main()\n{\n\tint a = 2;\n\tint b = 1;\n\n\tresult(int) r = divide(a, b);\n\n\tif (result_ok(int, r)) {\n\n\t\tprintf(\"The result is %d.\\n\", result_value(int, r));\n\n\t} else {\n\n\t\tputs(result_error(int, r).str);\n\t}\n}\n```\n\nBut is this now safe? Let's try to use the result value in the false branch.\n\n```\n\t} else {\n\n\t\tprintf(\"The result is %d.\\n\", result_value(int, r));\n\t\tputs(result_error(int, r).str);\n\t}\n```\n\n\tIf you also change `b` to zero, this will trigger a run-time error.\n\t\n\n\tBut what if I want to avoid run-time errors?\n\tLet's try our [previously described trick](https://uecker.codeberg.page/2026-05-22.html)\n\tof re-declaring `__builtin_trap` with the `[[warning(\"UNSAFE\")]]`\n\tattribute. It works! Try it yourself on [godbolt](https://godbolt.org/z/rzYGWMoWK)\n\tby changing the `#if 0` to `#if 1`. This works because because noplate\n\tinternally also uses `__builtin_trap>`.\n\t\n\n\tThere is still a minor potential issue with the design of the type.\n\tIf we add `[[gnu::noinline,gnu::noipa]]`\n\tto the `divide` function, so that the compiler will not look into\n\tit, it will then complain that the argument to `puts` maybe null. \n\tThis is actually nice, as it tells us that there might be a run-time issue!\n\tNote, that in this example, without the attributes, it can see that the pointers\n\tto the error strings are not null, but when the `divide` function were\n\tmoved into a library, the information about the pointers would be inaccessible to the optimizer.\n\t\n\n\tTo avoid this, we could modify the `error_t` type to ensure that the error\n\tstring is always initialized in a way the optimizer can see this.  For example, a version which uses\n\tthe `strv` (string view) type from the noplate library instead of a traditional\n\tC string does not have this problem (the noplate's README contains a link to such an example).\n\tFor now, we can just add a check for this. The complete example, that the compiler now\n\thas proven statically safe with regard to signed integer operations, null pointers,\n\tand error handling can be found [here (godbolt)](https://godbolt.org/z/YKfnYdqb7).\n\t\n\n\tOf course, the compile-time check using GCC's optimizer is hack, but it shows what can be\n\tdone relatively easily with existing technology already shipped with compilers.\n\tWe only need a better interface to it. There is a proposal to add a `compiler_assert`\n\tfeature to C ([PDF](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3950.pdf)),\n\tbut WG14 was not yet convinced.", "url": "https://wpnews.pro/news/generic-containers-in-c-result", "canonical_source": "https://uecker.codeberg.page/2026-10-24.html", "published_at": "2026-10-25 08:53:28+00:00", "updated_at": "2026-10-25 09:12:29.285601+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Martin Uecker", "noplate", "Anthropic", "GCC", "C"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/generic-containers-in-c-result", "markdown": "https://wpnews.pro/news/generic-containers-in-c-result.md", "text": "https://wpnews.pro/news/generic-containers-in-c-result.txt", "jsonld": "https://wpnews.pro/news/generic-containers-in-c-result.jsonld"}}