# Generic Containers in C: Result

> Source: <https://uecker.codeberg.page/2026-10-24.html>
> Published: 2026-10-25 08:53:28+00:00

Martin Uecker, 2026-10-24
	

	Previously, I discussed a [span type](./2025-07-02.html), 
	[a vector type](./2025-07-20.html), an optional type
	called [maybe](./2025-08-10.html), and a variant type
	[variadic](./2025-09-21.html) (now renamed to
	`variant` which makes more sense). I experiment with these types in my
	[noplate library](https://codeberg.org/uecker/noplate/). A couple
	of months ago, I explored how GCC's optimizer in combination with sanitizer can  be (mis-)used to prove 
	[safety properties at compile-time](https://uecker.codeberg.page/2026-05-22.html).
	Interestingly, the idea of using sanitizers for proving safety properties is
	also used by Anthropic to [verify](https://www.anthropic.com/research/formal-verification)
	C code, although there it is applied to the generated RISC-V code.
	

	Today, I want to discuss the design of a generic `result` type. 
	We want a type that behaves similar to `maybe`, but which can
	not be ignored without a warning and which also encodes an error message.
	

```
static result(int) divide(int a, int b)
{
	if (b == -1 && a == INT_MIN)
    		return result_fail(int, "overflow in division");

	if (b == 0)
		return result_fail(int, "division by zero");

	return result_success(int, a / b);
}
```

	We start by defining the result type as a generic macro that simply wraps
	an algebraic `sum` type that contains the type of the result
	and an error type that simply wraps a string. 
	

``` js
typedef struct { const char *error; } error_t;
#define result(T) struct [[nodiscard]] result_##T { sum(T, error_t) sum; }
```

	Here, we used the `[[nodiscard]]` attribute that tells the
	compiler to emit a warning when the result is ignored when returned
	from a function.
	

	Side note: A known limitation of our macro-based generics is that the
	type arguments must be typedef names, but this is only a minor
	inconvenience which hopefully can be addressed in the future. I still
	like to make the following observation at this point. An alternative design that
	uses tagless structures can use arbitrary type names, because it does not need
	to construct a new tag using token concatenation (`##`). Such types
	then always require a single unique declaration before use. But this has a major problem:
	Where do you place the declaration for `result(T)` and
	`sum(T, error_T)` for various types `T`?  These generic
	types should be vocabulary types that can freely be used anywhere.
	

	Let's continue constructing `result` by adding two constructors.
	

```
#define result_fail(T, e) 	(result(T)){ sum_init(T, error_t, (error_t){ (e) }) }
#define result_success(T, v) 	(result(T)){ sum_init(T, error_t, (v)) }
```

And finally also some accessor functions.

```
#define result_ok(T, r)		(!(r).sum.sel)
#define result_value(T, r)	sum_car(r.sum)
#define result_error(T, r)	sum_cdr(r.sum)
```

	The `sum` type is simply a tagged union which works similar as
	`maybe` as it traps when the inactive member is accessed.  
	I will write about it another time, as the design of the algebraic
	types is not yet completed.
	

 

	Now let's look how it works in an example ([godbolt](https://godbolt.org/z/7Y8q8szhP)).
	

```
int main()
{
    int a = 2;
    int b = 1;

    divide(a, b);
}

: In function 'main':
:34:5: warning: ignoring return value of type 'struct result_int', declared with attribute 'nodiscard' [-Wunused-result]
   34 |     divide(a, b);
      |     ^~~~~~~~~~~~~~~~~
:17:20: note: in call to 'divide', declared here
   17 | static result(int) divide(int a, int b)
```

	If we ignore the return value, we get the expected warning. Of course,
	`divide` is a poor example for this, because ignoring the result
	is not a problem. For certain other functions this is very useful.
	

 

	Now let's consider a complete example ([godbolt](https://godbolt.org/z/491ec3n4T)).

```
int main()
{
	int a = 2;
	int b = 1;

	result(int) r = divide(a, b);

	if (result_ok(int, r)) {

		printf("The result is %d.\n", result_value(int, r));

	} else {

		puts(result_error(int, r).str);
	}
}
```

But is this now safe? Let's try to use the result value in the false branch.

```
	} else {

		printf("The result is %d.\n", result_value(int, r));
		puts(result_error(int, r).str);
	}
```

	If you also change `b` to zero, this will trigger a run-time error.
	

	But what if I want to avoid run-time errors?
	Let's try our [previously described trick](https://uecker.codeberg.page/2026-05-22.html)
	of re-declaring `__builtin_trap` with the `[[warning("UNSAFE")]]`
	attribute. It works! Try it yourself on [godbolt](https://godbolt.org/z/rzYGWMoWK)
	by changing the `#if 0` to `#if 1`. This works because because noplate
	internally also uses `__builtin_trap>`.
	

	There is still a minor potential issue with the design of the type.
	If we add `[[gnu::noinline,gnu::noipa]]`
	to the `divide` function, so that the compiler will not look into
	it, it will then complain that the argument to `puts` maybe null. 
	This is actually nice, as it tells us that there might be a run-time issue!
	Note, that in this example, without the attributes, it can see that the pointers
	to the error strings are not null, but when the `divide` function were
	moved into a library, the information about the pointers would be inaccessible to the optimizer.
	

	To avoid this, we could modify the `error_t` type to ensure that the error
	string is always initialized in a way the optimizer can see this.  For example, a version which uses
	the `strv` (string view) type from the noplate library instead of a traditional
	C string does not have this problem (the noplate's README contains a link to such an example).
	For now, we can just add a check for this. The complete example, that the compiler now
	has proven statically safe with regard to signed integer operations, null pointers,
	and error handling can be found [here (godbolt)](https://godbolt.org/z/YKfnYdqb7).
	

	Of course, the compile-time check using GCC's optimizer is hack, but it shows what can be
	done relatively easily with existing technology already shipped with compilers.
	We only need a better interface to it. There is a proposal to add a `compiler_assert`
	feature to C ([PDF](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3950.pdf)),
	but WG14 was not yet convinced.
