On Wed, May 13, 2015 at 4:26 AM, Nick Wellnhofer wrote:

> It seems that supporting the global Err variable is a non-trivial issue for
> the Go bindings. In fact, Go doesn’t support thread-local storage, leading
> to clever hacks like this one:
>
> https://github.com/jtolds/gls
>
> Do you have any thoughts on how to address this issue?

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.

    // Verbose error handling.
    MAYBEGreeter greeter = Greeter_new("Hello, world!");
    if (MAYBEGreeter_ERROR(greeter)) {
        // handle error
    }
    Greeter *greeter = MAYBEGreeter_VALUE(maybe);

    // Succinct but throws exception on failure.
    Greeter *greeter = MAYBEGreeter_UNWRAP(Greeter_new("Hello, world!"));

A sample C app using a union to implement a MAYBE type is pasted below my
sig, and can also be viewed here:

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

For object return types, MAYBE types can be realized using either unions,
structs, or struct pointers.

    Union:

        typedef union {
            Greeter *value;
            Err     *err;
            Obj     *object;
        } MAYBEGreeter;

        static inline Greeter*
        MAYBEGreeter_UNWRAP(MAYBEGreeter maybe) {
            if (maybe.err != NULL && Err_Is_A(maybe.err, ERR)) {
                // handle error
            }
            return maybe.value;
        }

    Struct:

        typedef struct {
            Greeter *value;
            Err     *err;
        } MAYBEGreeter;

        static inline Greeter*
        MAYBEGreeter_UNWRAP(MAYBEGreeter maybe) {
            if (maybe.err != NULL)) {
                // handle error
            }
            return maybe.value;
        }

    Struct pointer:

        typedef struct MaybeGreeter MaybeGreeter;

        static inline Greeter*
        MaybeGreeter_UNWRAP(MaybeGreeter *maybe) {
            if (maybe != NULL && Obj_Is_A((Obj*)maybe, ERR) {
                // handle error
            }
            return (Greeter*)maybe;
        }

MAYBE types for primitives can only be implemented using a struct:

        typedef struct {
            int32_t  value;
            Err     *err;
        } MAYBEint32_t;

        static inline int32_t
        MAYBEint32_t_UNWRAP(MAYBEint32_t maybe) {
            if (maybe.err != NULL)) {
                // handle error
            }
            return maybe.value;
        }

Different hosts can have different behaviors for subroutines which return
MAYBE types...

Languages which use unchecked exceptions can detect that an error occurred and
raise the exception implicitly:

    # Python (implicit exception)
    greeter.greet = Greeter("Hello, world!")

    # Perl (implicit exception)
    my $greeter = Greeter->new(greeting => "Hello, world");

    # Ruby (implicit exception)
    greeter = Greeter.new("Hello, world!")

In Go, exceptions are frowned up and the tradition is to return error
information alongside the return value in a separate variable.

    // Go (multiple return values)
    greeter, err := hello.NewGreeter("Hello, world!")

In certain C environments, exceptions are not allowed at all, so manual error
checking would be required.

    // C (manual error checking)
    MAYBEGreeter greeter = Greeter_new("Hello, world!");
    if (MAYBEGreeter_ERROR(greeter)) {
        // handle error
    }
    Greeter *greeter = MAYBEGreeter_VALUE(maybe);

The Clownfish runtime is reasonably small.  Retrofitting it to avoid
exceptions entirely (including out-of-memory errors) should be doable.

For Lucy, the immediate objective would not be to avoid exceptions,
but to avoid thread-local storage of error variables and facilitate
goroutine-style concurrency.  That should also be doable.

For background on this approach to error handling, see this recent article on
"Error Handling in Rust" and the attendant Hacker News discussion:

    http://blog.burntsushi.net/rust-error-handling/
    https://news.ycombinator.com/item?id=9545647

Thoughts?

Avoiding exceptions in the Clownfish runtime is an important use case for
Proton.

Marvin Humphrey

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

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

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 union {
    Greeter *value;
    Err     *err;
    Obj     *object;
} MAYBEGreeter;

static inline Obj*
SI_unwrap_any(Obj *obj, Class klass) {
    if (obj != NULL && obj->klass != klass) {
        if (obj->klass == ERR) {
            Err *err = (Err*)obj;
            fprintf(stderr, "An error occurred: %s\n", err->message);
            exit(EXIT_FAILURE);
        }
    }
    return obj;
}

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!";
    MAYBEGreeter retval = {self};
    return retval;
}

MAYBEGreeter
Greeter_bad() {
    MAYBEGreeter retval = { .err = Err_new("*earth-shattering ka-boom*") };
    return retval;
}

static inline Greeter*
Greeter_UNWRAP(MAYBEGreeter maybe) {
    return (Greeter*)SI_unwrap_any(maybe.object, 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