Implementing Result Types for C

· 517 words · 3 minute read

While working on SeaScript - a small programming language that compiles to c, I wanted to add result types to allow functions to return errors and values without a big hussle. This in turn enables nice and clean error handling for functions such as:

 1sqrt [n:number]: Result[number] -> {
 2    if n < 0 {
 3        return Result{
 4            error: "can't compute sqrt for negative numbers"
 5        }
 6    }
 7    res = sqrt(n)
 8    if res < 0 {
 9        return Result{error: "couldn't compute sqrt of {n}"}
10    }
11    return Result{value: res}
12}

Because seascript compiles to c, i have to implement this in a way that its not a compile time feature only. So i sat down and implemented Result types in C.

 1#ifndef RESULT_H
 2#define RESULT_H
 3
 4#include <stdint.h>
 5
 6typedef struct {
 7  // if the Result holds no error, this contains the success value
 8  void *value;
 9  // if the Result holds an error, this contains the error message
10  const char *error;
11  // this indicates if the Result holds an error
12  int8_t hasError;
13} SeaScriptResult;
14
15// allocates a new Result, sets ->hasError to 0 and ->value to the given value
16SeaScriptResult *SeaScriptResultNewSuccess(void *value);
17
18// allocates a new Result, sets ->hasError to 1 and ->error to the given error
19SeaScriptResult *SeaScriptResultNewError(const char *error);
20
21// returns the value of the Result and destroys it. If the Result contains an
22// error the error message is printed to stderr and the process exists with
23// EXIT_FAILURE
24void *SeaScriptResultUnwrap(SeaScriptResult *result);
25
26// returns the value of the Result and destroys it. If the Result contains an
27// error the provided error message is printed to stderr and the process exists
28// with EXIT_FAILURE
29void *SeaScriptResultExpect(SeaScriptResult *result, const char *error);
30
31// frees the allocated memory region for the given Result, sets the
32// pointer to point to NULL
33void SeaScriptResultFree(SeaScriptResult *result);
34
35#endif

The API manages its allocations by itself and exposes SeaScriptResultNewSuccess and SeaScriptResultNewError for creating results. It also exposes SeaScriptResultUnwrap and SeaScriptResultExpect for consuming the result.

The best example is the before defined sqrt method written in in seascript but translated to c:

 1#include <stdio.h>
 2#include <stdlib.h>
 3#include "sealib/result.h"
 4
 5SeaScriptResult *squareRoot(double n) {
 6    if (n < 0) {
 7      return SeaScriptResultNewError(
 8          "can't compute square root of negative integer");
 9    }
10    double res = sqrt(n);
11    if (res < 0) {
12      return SeaScriptResultNewError(
13          "failed to compute square root");
14    }
15    double *r = malloc(sizeof(double))
16    *r = res;
17    return SeaScriptResultNewSuccess(r);
18}

The SeaScriptResultNewSuccess function requires a pointer to be passed in, therefore the consumer of the SeaScriptResult has to free the embedded pointer after consuming the result.

1int main(void) {
2    SeaScriptResult* res = squareRoot(-5);
3    double res = (double *) SeaScriptResultUnwrap(res);
4    printf("%f\n", *res);
5    free(res);
6    return EXIT_SUCCESS;
7}

Both SeaScriptResultUnwrap and SeaScriptResultExpect consume the Result, write to stderr and exit the process, therefore the library consumer does not need to free these Results.

Tip

Interested in the implementation of this API? View the source code here.