Search code examples
error-handlingclosuresvala

How do I factor out my use of `try {...} catch (Error e) {log_error(e);}`


I need errors to be logged in the same way across a large number of function calls. Here I want errors from foo.create(...) and File.new_tmp(...) to be logged by handle_error(...).

// compile with `valac --pkg gio-2.0 main.vala` 

void log_error(Error e) {
    // error logging here
}

void main() {
    var foo = File.new_for_path("foo");
    try {
        foo.create(FileCreateFlags.NONE);
    } catch (Error e) {
        log_error(e);
    }

    FileIOStream tmp_stream;
    try {
        File.new_tmp(null, out tmp_stream);
    } catch (Error e) {
        log_error(e);
    }
}

(Yes, main should continue with the FileIOStream stuff if foo.create fails, which is why they're in separate try/catch blocks.)

I want to factor out the use of try {...} catch (Error e) {log_error(e);} into a function like so:

delegate void Action();

void log_error(global::Action action) {
    try {
        action();
    } catch (Error e) {
        // error logging here
    }
}

void main() {
    var foo = File.new_for_path("foo");
    log_error(() => foo.create(FileCreateFlags.NONE));

    FileIOStream tmp_stream;
    log_error(() => File.new_tmp(null, out tmp_stream));
}

But valac gives the warning unhandled error 'GLib.IOError' because you can't seem to catch errors thrown within a closure, nor can I just rewrite log_error(...) as a #define macro as vala doesn't support them. So what can I do?


Solution

  • You can catch exceptions thrown in closures, you just need to have the delegate throw the exception. What you want is probably something like this:

    public delegate T? Action<T> () throws GLib.Error;
    
    T? log_error<T> (global::Action<T> func) {
      try {
        return func ();
      } catch (GLib.Error e) {
        // error logging here
        return null;
      }
    }
    
    void main () {
      var foo = File.new_for_path("foo");
      log_error<GLib.FileOutputStream> (() => foo.create (FileCreateFlags.NONE));
    
      FileIOStream? tmp_stream = null;
      GLib.File? f = log_error<GLib.File> (() => File.new_tmp (null, out tmp_stream));
    }
    

    Note that I've made it a generic so you can actually use a return value. If you want it should be trivial to remove the generic type argument and just return void, though you'll lose some flexivility.