Closures and asynchronous methods in Vala

It’s time for the release of Vala 0.7.6. Tarballs are available from the GNOME FTP servers. This release includes a couple of new features worth explaining in a bit more detail.

Closures

While Vala has lambda expressions for a long time, they haven’t supported accessing local variables from the outer method. With the release of Vala 0.7.6 this has changed, and you can access and set any local variable from lambda expressions. One place where I’d expect this to be convenient is in signal handlers:

void main (string[] args) {
    Gtk.init (ref args);
 
    var window = new Gtk.Window (Gtk.WindowType.TOPLEVEL);
    window.set_default_size (300, 50);
    window.destroy.connect (Gtk.main_quit);
 
    var button = new Gtk.Button.with_label ("Click me!");
    button.clicked.connect (() => {
        window.title = "Closures in Vala";
    });
 
    window.add (button);
    window.show_all ();
 
    Gtk.main ();
}

You can even write recursive lambda expressions:

delegate int Func (int i);
 
void main () {
	Func fib = null;
	fib = (i) => (i <= 1) ? i : (fib (i - 2) + fib (i - 1));
 
	for (int i = 0; i < 10; i++) {
		message ("%d", fib (i));
	}
}

Asynchronous methods

The idea of integrating async methods into the language is to make it easier to write code that calls or implements async methods. Async methods are frequently used in I/O API such as GIO and in D-Bus clients and servers. Traditionally, writing async code has beeen quite painful as you often end up writing callback chains and completely lose track over the control flow in the process.

Experimental support for async methods has been implemented in Vala almost a year ago. However, the implementation was rather a proof of concept than a finished solution. While I still expect a few bugs in that area with 0.7.6, it’s a lot more robust and complete than it used to be a short time ago.

So how does it look like? It’s not unlike Alex’s AsyncRunner in JavaScript except that it’s even more concise as it’s fully integrated into the language. The following code defines an async method list_dir that asynchronously enumerates all files in the home directory:

async void list_dir () {
    var dir = File.new_for_path (Environment.get_home_dir ());
    try {
        var e = yield dir.enumerate_children_async (FILE_ATTRIBUTE_STANDARD_NAME, 0, Priority.DEFAULT, null);
        while (true) {
            var files = yield e.next_files_async (10, Priority.DEFAULT, null);
            if (files == null) {
                break;
            }
            foreach (var info in files) {
                print ("%s\n", info.get_name ());
            }
        }
    } catch (GLib.Error err) {
        warning ("Error: %s\n", err.message);
    }
}
 
void main () {
    list_dir.begin ();
 
    var loop = new GLib.MainLoop (null, false);
    loop.run();
}

As you can see, it’s very easy to call the async methods enumerate_children_async and next_files_async in try-catch blocks and loops. There is no need for manually creating structs for user_data or similar inconveniences.

Async methods in Vala can also be used to implement D-Bus servers that can process multiple requests at the same time. Using it couldn’t be easier, just write an async method such as the above example, add it to a class annotated with [DBus (name = “org.example.Test”)], and register an instance of that class with the D-Bus connection. Vala also supports async methods on client-side D-Bus interfaces.

12 thoughts on “Closures and asynchronous methods in Vala”

  1. Wow, Vala is getting cooler all the time! One question, which probably just reflects my lack of experience with lambdas and stuff in general: what happens if I do something like the following?

    delegate void Func (void);

    Func make_func()
    {
    int i = 10;
    Func f = () => print (“i is %i\n”, i);
    return f;
    }

    void main()
    {
    Func f = make_func();
    f();
    }

    I probably messed up some syntax in there, as I haven’t really done much with Vala so far, but hopefully you get my meaning: what happens if I reference a local variable that doesn’t exist anymore?

  2. The asynchronous method stuff Alex did for Javascript and the similar functionality in Python is built on top of the language’s generator concept (a special type of function that generates some kind of sequence).

    Writing easy to read async function/method is just one of the many things you can use the language feature (i.e. have a generator that yields objects that can be used to wait for operation completion, and then have a wrapper function that calls back into the generator at the right time).

    Is that what you’ve implemented for vala, or have you just borrowed the generator syntax and repurposed it?

  3. I am just wondering:
    does “while (true)” really iterates until we get a callback?
    (and takes 100% of CPU)

    or is it waiting for the callback? (and the thread is sleeping)

  4. @Yann: no. The idea of this kind of syntax is to let you write functions that look synchronous but run asynchronously. Each of the yield calls indicates a point where the function will pause, waiting for completion of that operation.

    Internally, this would probably be implemented by storing the locals for the function inside a structure so that they can be maintained over multiple invocations, some glue to wait on the GAsyncResults and code to handle reentering the code path (possibly using Simon Tatham’s switch statement method).

  5. @Matt: I was going to post a similar comment. It looks like it stores the variables referenced in the closure to a ref-counted struct and each delegate variable now makes two C variables – one for the function pointer and one for the data pointer. So the local ‘i’ variable referenced in your closure should outlive the scope of ‘make_func’. However it looks like there’s some bugs there because the generated code does not take a reference to the struct when make_func returns so the data gets destroyed prematurely and your programs outputs “i is 0”!

  6. This is some insanely cool and impressive stuff. Well done Vala team!

    @Yann: AFAIK it will not spin, the generated code is all async callback based, so no CPU is being used as long as no new data is available, and no threads involved (next to the main thread, obviously) either.

  7. @Matt: As Neil correctly explains, the variable is kept alive as long as it’s referenced in a closure. The mentioned bug has been fixed now and your example will print 10 as expected.

    @James: It is not based on generators as this would not allow the same level of integration with languages that are not as dynamic as, e.g., Python or JavaScript. However, support for generators is planned as well, and a large part of the implementation will be shared.

  8. Hey Vala team, congrats here from the ooc team =)

    What you implemented looks very much like what I had in mind for ooc closures. Mind to share some code/structs? (Or maybe you’re not comfortable with getting intimate that fast ^^) Interoperability might be nice, since we’re both C-generating languages.

    Anyway, kudos, and keep up the great work =)

Comments are closed.