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;
}