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.