This is the sixth in a series of blog posts on extensibility in gEDA using Guile Scheme.
- Finding Scheme API code in gEDA
- Compiling against multiple Guile versions
- Safe handling of non-local exits
- Dealing with deprecated libguile functions
- Checking arguments to Scheme functions in C
- How and when to use Scheme errors
- Reducing boilerplate with "snarfing macros"
- Opportunities to get involved
In this post, I will try to make it clear how and when to raise Scheme errors in procedures implemented in C.
As explained in an earlier blog post in this series, Guile has a fully-fledged exception mechanism, which is used as the basis for its error reporting system. Because this allows execution to jump non-locally when an error occurs, it requires resources to be carefully managed.
Other than checking types using the SCM_ASSERT() macro, when else might it be useful to raise Scheme errors from C functions in gEDA?
When to raise Scheme errors
In Scheme functions written in Scheme, the answer is obvious: raise a Scheme error using error or scm-error. In C, it's not so simple.
Recall that there are currently four approaches to error handling in gEDA's C source code:
- Give up (log an error message and quit immediately).
- The GError mechanism from GLib.
- Best-effort (log an warning message and return a default value).
- Raise a Scheme error.
We try and avoid the "giving up" option, because quite often the situation is not actually hopeless, and the error could be handled by a function further up the stack or at the very least the user might be able to save his or her data.
In functions that are actual C API (i.e. designed primarily to be called by other C functions), Scheme errors are never appropriate, because they're quite complex to catch and handle in C. It's much easier to get from a GError to a Scheme error than the other way round! These sorts of functions should either use GError (for run-time errors outside the developers' control, such as being asked to load a file that doesn't exist), or use a "best-effort" approach (for programming errors, e.g. return NULL when asked for a list of objects in a NULL page).
On the other hand, functions that are designed primarily to be called from Scheme code should exclusively use Scheme errors. If there's a problem, either it'll be caught and handled, or it won't and an error message will be generated (and the program may exit).
This means that you shouldn't use best-effort helper macros like g_return_if_fail() — if you're called with invalid arguments, generate a Scheme error (e.g. using SCM_ASSERT()). You can apply a simple rule of thumb: if the SCM type appears in the function prototype, you should normally use Scheme error reporting.
How to raise Scheme errors
With libguile, the main function to use to raise Scheme errors is scm_error_scm(). However, the libguile headers provide some additional undocumented helper functions which make raising errors from C functions much more convenient. For example:
- scm_error() is similar to scm_error_scm() but allows you to pass the raising function's name as a char * string rather than a Scheme string.
- scm_misc_error() is the easiest way to generate errors for which it's not worth assigning a specific error key, and is the C equivalent to the error function in Scheme.
A full list of functions for raise errors is available in libguile/error.h, and since they are much better than scm_error_scm(), use them.
Don't forget that raising a Scheme error causes a non-local exit, and make provisions (such as using dynamic wind) to release any resources you hold correctly.
Guile provides a full-featured error-reporting system, which gEDA applications should use in their Scheme APIs more than they currently do. The best way to raise a Scheme error isn't via scm_error_scm(), but through the undocumented helper functions that libguile provides (and I should probably submit a documentation patch to upstream Guile to fix that...)
In my next post, I'll talk about the guile-snarf tool, and how to use Guile's "snarfing macros" to simplify the boilerplate needed to export C functions as callable from Scheme.