On 04/06/2015 04:01, Marvin Humphrey wrote:
Changing the Clownfish runtime to avoid exceptions will mean more explicit
error checking in client code. Whether this is an improvement in general is
an old debate -- some people prefer the elegance of exceptions, some people
prefer the transparency of local status code handling. But for Clownfish
specifically, preferring return status codes over exceptions offers a key
advantage:
* It is reasonable to promote return codes to raised exceptions at the
Clownfish/host border.
* In contrast, it is challenging to trap all raised exceptions at the
Clownfish/host border and convert them to return codes.
And we don't have the problem of memory leaks that can't be avoided when
throwing exceptions via longjmp in C.
For sample usage from C, let's consider a refactored version of Vector's
constructor, which can fail with either an out-of-memory error or an overflow
error.
I'd simply keep treating OOM errors as fatal. Overflow errors are essentially
the same as OOM in this context. Applications that can deal with OOM errors
are extremely rare. For many host languages resolving an OOM condition is
impossible anyway. For host languages with GC, Clownfish could trigger a GC
run if a memory allocation fails, then retry once.
It's unrealistic to expect that Clownfish applications always check for the
return value of methods like Vec_Push. Typically, they will just ignore it. An
unhandled OOM error only leads to hard-to-diagnose problems down the way.
We should probably keep the current exception system for fatal errors. Some
applications might be able to handle exceptions more gracefully even if they
leak memory. But we should make it clear that these are fatal exceptions that
either indicate a programming error in the application code or signal some
rare kind of resource exhaustion that we're not prepared to deal with.
MAYBEVector
one_through_ten(void) {
MAYBEVector maybe_vec = Vec_new(10);
if (MAYBEVec_ERR(maybe_vec)) {
return maybe_vec;
}
Vector *vector = MAYBEVec_VALUE(maybe_vec);
for (int32_t i = 1; i <= 10; i++) {
MAYBEString maybe_str = Str_newf("%i32", i);
String *string = MAYBEStr_VALUE(maybe_str);
if (string == NULL) {
DECREF(vector);
return MAYBEVec_bad(MAYBEStr_ERR(maybe_str));
}
Err *error = MAYBEbool_ERR(Vec_Push(vector, (Obj*)string));
if (error != NULL) {
DECREF(vector);
return MAYBEVec_bad(error);
}
}
return MAYBEVec_good(vector);
}
Pretty verbose!
Indeed.
Instead, I'd suggest declaring the possible error types that a subroutine can
return using a `drops` clause:
public inert incremented MAYBEVector
new(size_t capacity)
drops OverflowErr*, OutOfMemoryErr*;
An error hierarchy is certainly a nice feature but I wouldn't include it in
the initial implementation. To get started, it should be enough to teach CFC
how to handle MAYBE return types.
Nick