On Wed, May 20, 2015 at 7:43 AM, Nick Wellnhofer <[email protected]> wrote:
> On 18/05/2015 02:09, Marvin Humphrey wrote:
>>
>> As an alternative to throwing exceptions or storing exception objects in
>> thread-local variables, let's consider encoding error information into
>> return values using a crude form of algebraic data types: pre-defined
>> "MAYBE" types which can be either an Err or something else.
>
> +1. This is a great idea.

This won't work with Ruby's GC, but here's another variant implementing a
quasi-tagged-union using the low bit to distinguish error from value.

https://gist.github.com/rectang/d229dfd3e27057540940

*   MAYBE types are typedef'd to `size_t`.  (This can encode enough bits to
    hold pointers and primitive types narrower than a pointer.  MAYBE types
    for wider primitives will need to be implemented as structs.)
*   The low bit is set to 1 to indicate success and 0 for failure.
*   Access to the MAYBE type is funneled through subroutines.

Unlike the previous proof-of-concept code which inspects the class pointer,
using the low bit as a type tag allows us to differentiate between an error
condition and a deliberate Err* return value.

The reason this approach is incompatible with the MRI Ruby runtime is that
MRI's conservative GC scans the C stack looking for Ruby object pointers, and
setting the low bit would hide them from it.  However, if we only manipulate
MAYBE types through subroutines, they could be two-slot structs under some
hosts (such as Ruby) and integers under others.

Marvin Humphrey

// -------------------------------------------------------------------------


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAYBE_SUCCESS_FLAG 1
#define MAYBE_SUCCESS_MASK (((size_t)-1) ^ MAYBE_SUCCESS_FLAG)

typedef enum {
    OBJ,
    ERR,
    GREETER
} Class;

typedef struct Obj {
    Class klass;
} Obj;

typedef struct Err {
    Class klass;
    char *message;
} Err;

typedef struct Greeter {
    Class klass;
    char *greeting;
} Greeter;

typedef size_t MAYBEGreeter;

static inline MAYBEGreeter
MAYBEGreeter_good(Greeter *greeter) {
    return ((MAYBEGreeter)greeter) | MAYBE_SUCCESS_FLAG;
}

static inline MAYBEGreeter
MAYBEGreeter_bad(void *error) {
    return (MAYBEGreeter)error;
}

static void
S_fail_unwrap(size_t maybe, Class klass) {
    Err *err = (Err*)maybe;
    (void)klass;
    const char *message = err == NULL
                          ? "unexpected NULL"
                          : err->message;
    fprintf(stderr, "An error occurred: %s\n", message);
    exit(EXIT_FAILURE);
}

static inline Obj*
SI_unwrap_any(size_t maybe, Class klass) {
    if (!(maybe & MAYBE_SUCCESS_FLAG)) {
        S_fail_unwrap(maybe, klass);
    }
    return (Obj*)(maybe & MAYBE_SUCCESS_MASK);
}

Err*
Err_new(const char *message) {
    Err *self = (Err*)malloc(sizeof(Err));
    self->klass = ERR;
    self->message = strdup(message);
    return self;
}

MAYBEGreeter
Greeter_new() {
    Greeter *self = (Greeter*)malloc(sizeof(Greeter));
    self->klass    = GREETER;
    self->greeting = "Hello, world!";
    return MAYBEGreeter_good(self);
}

MAYBEGreeter
Greeter_bad() {
    return MAYBEGreeter_bad(Err_new("*earth-shattering ka-boom*"));
}

static inline Greeter*
Greeter_UNWRAP(MAYBEGreeter maybe) {
    return (Greeter*)SI_unwrap_any(maybe, GREETER);
}

int
main() {
    Greeter *good = Greeter_UNWRAP(Greeter_new());
    printf("Good Greeter says: %s\n", good->greeting);
    Greeter *bad = Greeter_UNWRAP(Greeter_bad());
    printf("Bad Greeter says: %s\n", bad->greeting);
    return 0;
}

Reply via email to