Greets,

Python uses exceptions for error handling rather than return values, but from
the C API it's different.  When an error occurs in a C extension written for
Python, you are expected to set thread-local exception info and then report
failure to the Python interpreter by returning NULL from the function wrapper.

    // Python wrapper for `do_stuff` C function.
    static PyObject*
    S_do_stuff(PyObject *self, PyObject *args)
    {
        if (do_stuff()) {
            PyErr_SetString(PyExc_Exception, "D'oh");
            return NULL;
        }
        Py_RETURN_NONE;
    }

This doesn't play well with Clownfish-flavored C code which throws exceptions.

With the Perl bindings, we were able to tie into the host exception handling
code and trigger an exception which would `longjmp` across multiple stack
frames and return control to where the Perl interpreter itself had called
`setjmp`.  That doesn't seem to be possible from the Python C API because
Python has never called `setjmp`[1].

Since we can't integrate closely with the Python exception mechanism, what
we'll need to do is implement Clownfish's exceptions using setjmp/longjmp,
trap any Clownfish exceptions in the glue code, use the trapped exception
object to set the Python exception info, and then return NULL to signal the
Python interpreter that it should trigger a Python exception.

    static PyObject*
    S_do_stuff(PyObject *self, PyObject *args)
    {
        Err *err = Err_trap(do_stuff, NULL);
        if (err != NULL) {
            set_py_exc_from_cf_err(err);
            return NULL;
        }
        Py_RETURN_NONE;
    }

(The generated code is actually much uglier, but that sample gets the point
across.)

If we were to implement MAYBE types in Clownfish as contemplated a while
back[2] to indicate error conditions, then for recoverable errors we wouldn't
need to use `Err_trap`.

    static PyObject*
    S_Foo_get_count(PyObject *self, PyObject *args)
    {
        MAYBEint32_t maybe = Foo_get_count((Foo*)self);
        Err *err = MAYBEint32_t_Err(maybe);
        if (err != NULL) {
            set_py_exc_from_cf_err(err);
            return NULL;
        }
        return PyLong_FromLong(MAYBEint32_t_VALUE(maybe));
    }

However, for unrecoverable exceptions thrown from Clownfish C code, the
longjmp will not go through Python's exception mechanism -- it will probably
just cause the process to terminate, which isn't very friendly or Pythonic.

Finally, working with the Python error mechanism has reinforced my opinion
that if Clownfish is to be a "least-common-denominator" object model,
maximally compatible with as many hosts as possible, it should prefer return
codes over exceptions -- because return codes are easy to transform into
exceptions, but exceptions are hard to turn into return codes.

Marvin Humphrey

[1] Some interesting threads from the Python dev list on setjmp/longjmp:
    https://mail.python.org/pipermail/python-list/2006-October/364076.html
    https://mail.python.org/pipermail/python-list/1999-November/000716.html
    https://mail.python.org/pipermail/python-dev/2003-October/039358.html

[2] Previous discussion regarding "MAYBE" sum types:
    http://markmail.org/message/px4c25kopw2h7f2t

Reply via email to