Federico's Blog

  1. Writing a command-line program in Rust

    - gnome, librsvg, rust

    As a library writer, it feels a bit strange, but refreshing, to write a program that actually has a main() function.

    My experience with Rust so far has been threefold:

    • Porting chunks of C to Rust for librsvg - this is all work on librsvg's internals and no users are exposed to it directly.

    • Working on gnome-class, the procedural macro ("a little compiler") to generate GObject boilerplate from Rust. This feels like working on the edge of the exotic; it is something that runs in the Rust compiler and spits code on behalf of the programmer.

    • A few patches to the gtk-rs ecosystem. Again, work on the internals, or something that feels library-like.

    But other than toy programs to test things, I haven't written a stand-alone tool until rsvg-bench. It's quite a thrill to be able to just run the thing instead of waiting for other people to write code to use it!

    Parsing command-line arguments

    There are quite a few Rust crates ("libraries") to parse command-line arguments. I read about structopt via Robert O'Callahan's blog; structopt lets you define a struct to hold the values of your command-line options, and then you annotate the fields in that struct to indicate how they should be parsed from the command line. It works via Rust's procedural macros. Internally it generates stuff for the clap crate, a well-established mechanism for dealing with command-line options.

    And it is quite pleasant! This is basically all I needed to do:

    #[derive(StructOpt, Debug)]
    #[structopt(name = "rsvg-bench", about = "Benchmarking utility for librsvg.")]
    struct Opt {
        #[structopt(short = "s",
                    long  = "sleep",
                    help  = "Number of seconds to sleep before starting to process SVGs",
                    default_value = "0")]
        sleep_secs: usize,
    
        #[structopt(short = "p",
                    long  = "num-parse",
                    help  = "Number of times to parse each file",
                    default_value = "100")]
        num_parse: usize,
    
        #[structopt(short = "r",
                    long  = "num-render",
                    help  = "Number of times to render each file",
                    default_value = "100")]
        num_render: usize,
    
        #[structopt(long = "pixbuf",
                    help = "Render to a GdkPixbuf instead of a Cairo image surface")]
        render_to_pixbuf: bool,
    
        #[structopt(help = "Input files or directories",
                    parse(from_os_str))]
        inputs: Vec<PathBuf>
    }
    
    fn main() {
        let opt = Opt::from_args();
    
        if opt.inputs.len() == 0 {
            eprintln!("No input files or directories specified\n");
            process.exit(1);
        }
    
        ...
    }
    

    Each field in the Opt struct above corresponds to one command-line argument; each field has annotations for structopt to generate the appropriate code to parse each option. For example, the render_to_pixbuf field has a long option name called "pixbuf"; that field will be set to true if the --pixbuf option gets passed to rsvg-bench.

    Handling errors

    Command-line programs generally have the luxury of being able to just exit as soon as they encounter an error.

    In C this is a bit cumbersome since you need to deal with every place that may return an error, find out what to print, and call exit(1) by hand or something. If you miss a single place where an error is returned, your program will keep running with an inconsistent state.

    In languages with exception handling, it's a bit easier - a small script can just let exceptions be thrown wherever, and if it catches them at the toplevel, it can just print the exception and abort gracefully. However, these nonlocal jumps make me uncomfortable; I think exceptions are hard to reason about.

    Rust makes this easy: it forces you to handle every call that may return an error, but it lets you bubble errors up easily, or handle them in-place, or translate them to a higher-level error.

    In the Rust world the [failure] crate is getting a lot of traction as a convenient, modern way to handle errors.

    In rsvg-bench, errors can come from several places:

    • I/O errors when reading files and directories.

    • Errors from librsvg's parsing stage; you get a GError.

    • Errors from the rendering stage. This can be a Cairo error (a cairo_status_t), or a simple "something bad happened; can't render" from librsvg's old convenience api in C. Don't you hate it when C code just gives up and returns NULL or a boolean false, without any further details on what went wrong?

    For rsvg-bench, I just needed to be able to represent Cairo errors and generic rendering errors. Everything else, like an io::Error, is automatically wrapped by the failure crate's mechanism. I just needed to do this:

    extern crate failure;
    #[macro_use]
    extern crate failure_derive;
    
    #[derive(Debug, Fail)]
    enum ProcessingError {
        #[fail(display = "Cairo error: {:?}", status)]
        CairoError {
            status: cairo::Status
        },
    
        #[fail(display = "Rendering error")]
        RenderingError
    }
    

    Whenever the code gets a Cairo error, I can translate it to a ProcessingError::CairoError and bubble it up:

    fn render_to_cairo(handle: &rsvg::Handle) -> Result<(), Error> {
        let dim = handle.get_dimensions();
        let surface = cairo::ImageSurface::create(cairo::Format::ARgb32,
                                                  dim.width,
                                                  dim.height)
            .map_err(|e| ProcessingError::CairoError { status: e })?;
    
        ...
    }
    

    And when librsvg returns a "couldn't render" error, I translate that to a ProcessingError::RenderingError:

    fn render_to_cairo(handle: &rsvg::Handle) -> Result<(), Error> {
        ...
    
        let cr = cairo::Context::new(&surface);
    
        if handle.render_cairo(&cr) {
            Ok(())
        } else {
            Err(Error::from(ProcessingError::RenderingError))
        }
    }
    

    Here, the Ok() case of the Result does not contain any value — it's just (), as the generated images are not stored anywhere: they are just rendered to get some timings, not to be saved or anything.

    Up to where do errors bubble?

    This is the "do everything" function:

    fn run(opt: &Opt) -> Result<(), Error> {
        ...
    
        for path in &opt.inputs {
            process_path(opt, &path)?;
        }
    
        Ok(())
    }
    

    For each path passed in the command line, process it. The program sees if the path corresponds to a directory, and it will scan it recursively. Or if the path is an SVG file, the program will load the file and render it.

    Finally, main() just has this:

    fn main() {
        let opt = Opt::from_args();
    
        ...
    
        match run(&opt) {
            Ok(_) => (),
            Err(e) => {
                eprintln!("{}", e);
                process::exit(1);
            }
        }
    }
    

    I.e. process command line arguments, run the whole thing, and print an error if there was one.

    I really appreciate that most places that can return an error an just put a ? for the error to bubble up. This is much more legible than in C, where every call must have an if (something_bad_happened) { deal_with_it; } after it... and Rust won't let me get away with ignoring an error, but it makes it easy to actually deal with it properly.

    Reading an SVG file quickly

    Why, just mmap() it and feed it to librsvg, to avoid buffer copies. This is easy in Rust:

    fn process_file<P: AsRef<Path>>(opt: &Opt, path: P) -> Result<(), Error> {
        let file = File::open(path)?;
        let mmap = unsafe { MmapOptions::new().map(&file)? };
    
        let bytes = &mmap;
    
        let handle = rsvg::Handle::new_from_data(bytes)?;
        ...
    }
    

    Many things can go wrong here:

    • File::open() can return an io::Error.
    • MmapOptions::map() can return an io::Error from the mmap(2) system call, or from the fstat(2) to read the file's size to map it.
    • rsvg::Handle::new_from_data() can return a GError from parsing the file.

    The little ? characters after each call that can return an error mean, just give me back the result, or convert the error to a failure::Error that can be examined later. This is beautifully legible to me.

    Summary

    Writing command-line programs in Rust is fun! It's nice to have neurotically-safe scripts that one can trust in the future.

    Rsvg-bench is available here.

  2. rsvg-bench - a benchmark for librsvg

    - gnome, librsvg, performance, rust

    Librsvg 2.42.0 came out with a rather major performance regression compared to 2.40.20: SVGs with many transform attributes would slow it down. It was fixed in 2.42.1. We changed from using a parser that would recompile regexes each time it was called, to one that does simple string-based matching and parsing.

    When I rewrote librsvg's parser for the transform attribute from C to Rust, I was just learning about writing parsers in Rust. I chose lalrpop, an excellent, Yacc-like parser generator for Rust. It generates big, fast parsers, like what you would need for a compiler — but it compiles the tokenizer's regexes each time you call the parser. This is not a problem for a compiler, where you basically call the parser only once, but in librsvg, we may call it thousands of times for an SVG file with thousands of objects with transform attributes.

    So, for 2.42.1 I rewrote that parser using rust-cssparser. This is what Servo uses to parse CSS data; it's a simple tokenizer with an API that knows about CSS's particular constructs. This is exactly the kind of data that librsvg cares about. Today all of librsvg's internal parsers work using rust-cssparser, or they are so simple that they can be done with Rust's normal functions to split strings and such.

    Getting good timings

    Librsvg ships with rsvg-convert, a command-line utility that can render an SVG file and write the output to a PNG. While it would be possible to get timings for SVG rendering by timing how long rsvg-convert takes to run, it's a bit clunky for that. The process startup adds noise to the timings, and it only handles one file at a time.

    So, I've written rsvg-bench, a small utility to get timings out of librsvg. I wanted a tool that:

    • Is able to process many SVG images with a single command. For example, this lets us answer a question like, "how long does version N of librsvg take to render a directory full of SVG icons?" — which is important for the performance of an application chooser.

    • Is able to repeatedly process SVG files, for example, "render this SVG 1000 times in a row". This is useful to get accurate timings, as a single render may only take a few microseconds and may be hard to measure. It also helps with running profilers, as they will be able to get more useful samples if the SVG rendering process runs repeatedly for a long time.

    • Exercises librsvg's major code paths for parsing and rendering separately. For example, librsvg uses different parts of the XML parser depending on whether it is being pushed data, vs. being asked to pull data from a stream. Also, we may only want to benchmark the parser but not the renderer; or we may want to parse SVGs only once but render them many times after that.

    • Is aware of librsvg's peculiarities, such as the extra pass to convert a Cairo image surface to a GdkPixbuf when one uses the convenience function rsvg_handle_get_pixbuf().

    Currently rsvg-bench supports all of that.

    An initial benchmark

    I ran this

    /usr/bin/time rsvg-bench -p 1 -r 1 /usr/share/icons

    to cause every SVG icon in /usr/share/icons to be parsed once, and rendered once (i.e. just render every file sequentially). I did this for librsvg 2.40.20 (C only), and 2.42.{0, 1, 2} (C and Rust). There are 5522 SVG files in there. The timings look like this:

    version time (sec)
    2.40.20 95.54
    2.42.0 209.50
    2.42.1 97.18
    2.42.2 95.89

    Bar chart of timings

    So, 2.42.0 was over twice as slow as the C-only version, due to the parsing problems. But now, 2.42.2 is practically just as fast as the C only version. What made this possible?

    • 2.40.20 - the old C-only version
    • 2.42.0 - C + Rust, with a lalrpop parser for the transform attribute
    • 2.42.1 - Servo's cssparser for the transform attribute
    • 2.42.2 - removed most C-to-Rust string copies during parsing

    I have started taking profiles of rsvg-bench runs with sysprof, and there are some improvements worth making. Expect news soon!

    Rsvg-bench is available in Gnome's gitlab instance.

  3. Help needed for librsvg 2.42.1

    - librsvg

    Would you like to help fix a couple of bugs in librsvg, in preparation for the 2.42.1 release?

    I have prepared a list of bugs which I'd like to be fixed in the 2.42.1 milestone. Two of them are assigned to myself, as I'm already working on them.

    There are two other bugs which I'd love someone to look at. Neither of these requires deep knowledge of librsvg, just some debugging and code-writing:

    • Bug 141 - GNOME's thumbnailing machinery creates an icon which has the wrong fill: it's an image of a builder's trowel, and the inside is filled black instead of with a nice gradient. This is the only place in librsvg where a cairo_surface_t is converted to a GdkPixbuf; this involves unpremultiplying the alpha channel. Maybe the relevant function is buggy?

    • Bug 136: The stroke-dasharray attribute in SVG elements is parsed incorrectly. It is a list of CSS length values, separated by commas or spaces. Currently librsvg uses a shitty parser based on g_strsplit() only for commas; it doesn't allow just a space-separated list. Then, it uses g_ascii_strtod() to parse plain numbers; it doesn't support CSS lengths generically. This parser needs to be rewritten in Rust; we already have machinery there to parse CSS length values properly.

    Feel free to contact me by mail, or write something in the bugs themselves, if you would like to work on them. I'll happily guide you through the code :)

  4. Librsvg gets Continuous Integration

    - gitlab, librsvg

    One nice thing about gitlab.gnome.org is that we can now have Continuous Integration (CI) enabled for projects there. After every commit, the CI machinery can build the project, run the tests, and tell you if something goes wrong.

    Carlos Soriano posted a "tips of the week" mail to desktop-devel-list, and a link to how Nautilus implements CI in Gitlab. It turns out that it's reasonably easy to set up: you just create a .gitlab-ci.yml file in the toplevel of your project, and that has the configuration for what to run on every commit.

    Of course instead of reading the manual, I copied-and-pasted the file from Nautilus and just changed some things in it. There is a .yml linter so you can at least check the syntax before pushing a full job.

    Then I read Robert Ancell's reply about how simple-scan builds its CI jobs on both Fedora and Ubuntu... and then the realization hit me:

    This lets me CI librsvg on multiple distros at once. I've had trouble with slight differences in fontconfig/freetype in the past, and this would let me catch them early.

    However, people on IRC advised against this, as we need more hardware to run CI on a large scale.

    Linux distros have a vested interest in getting code out of gnome.org that works well. Surely they can give us some hardware?

  5. Loving Gitlab.gnome.org, and getting notifications

    - gitlab, gnome

    I'm loving gitlab.gnome.org. It has been only a couple of weeks since librsvg moved to gitlab, and I've already received and merged two merge requests. (Isn't it a bit weird that Github uses "pull request" and Everyone(tm) knows the PR acronym, but Gitlab uses "merge request"?)

    Notifications about merge requests

    One thing to note if your GNOME project has moved to Gitlab: if you want to get notified of incoming merge requests, you need to tell Gitlab that you want to "Watch" that project, instead of using one of the default notification settings. Thanks to Carlos Soriano for making me aware of this.

    Notifications from Github's mirror

    The github mirror of git.gnome.org is configured so that pull requests are automatically closed, since currently there is no way to notify the upstream maintainers when someone creates a pull request in the mirror (this is super-unfriendly by default, but at least submitters get notified that their PR would not be looked at by anyone, by default).

    If you have a Github account, you can Watch the project in question to get notified — the bot will close the pull request, but you will get notified, and then you can check it by hand, review it as appropriate, or redirect the submitter to gitlab.gnome.org instead.

  6. Librsvg 2.40.20 is released

    - gnome, librsvg, rust

    Today I released librsvg 2.40.20. This will be the last release in the 2.40.x series, which is deprecated effectively immediately.

    People and distros are strongly encouraged to switch to librsvg 2.41.x as soon as possible. This is the version that is implemented in a mixture of C and Rust. It is 100% API and ABI compatible with 2.40.x, so it is a drop-in replacement for it. If you or your distro can compile Firefox 57, you can probably build librsvg-2.41.x without problems.

    Some statistics

    Here are a few runs of loc — a tool to count lines of code — when run on librsvg. The output is trimmed by hand to only include C and Rust files.

    This is 2.40.20:
    -------------------------------------------------------
     Language      Files   Lines   Blank   Comment    Code
    -------------------------------------------------------
     C                41   20972    3438      2100   15434
     C/C++ Header     27    2377     452       625    1300
    
    This is 2.41.latest (the master branch):
    -------------------------------------------------------
     Language      Files   Lines   Blank   Comment    Code
    -------------------------------------------------------
     C                34   17253    3024      1892   12337
     C/C++ Header     23    2327     501       624    1202
     Rust             38   11254    1873       675    8706
    
    And this is 2.41.latest *without unit tests*, 
    just "real source code":
    -------------------------------------------------------
     Language      Files   Lines   Blank   Comment    Code
    -------------------------------------------------------
     C                34   17253    3024      1892   12337
     C/C++ Header     23    2327     501       624    1202
     Rust             38    9340    1513       610    7217
    

    Summary

    Not counting blank lines nor comments:

    • The C-only version has 16734 lines of C code.

    • The C-only version has no unit tests, just some integration tests.

    • The Rust-and-C version has 13539 lines of C code, 7217 lines of Rust code, and 1489 lines of unit tests in Rust.

    As for the integration tests:

    • The C-only version has 64 integration tests.

    • The Rust-and-C version has 130 integration tests.

    The Rust-and-C version supports a few more SVG features, and it is A LOT more robust and spec-compliant with the SVG features that were supported in the C-only version.

    The C sources in librsvg are shrinking steadily. It would be incredibly awesome if someone could run some git filter-branch magic with the loc tool and generate some pretty graphs of source lines vs. commits over time.

  7. Librsvg moves to Gitlab

    - gnome, librsvg

    Librsvg now lives in GNOME's Gitlab instance. You can access it here.

    Gitlab allows workflows similar to Github: you can create an account there, fork the librsvg repository, file bug reports, create merge requests... Hopefully this will make it nicer for contributors.

    In the meantime, feel free to take a look!

    This is a huge improvement for GNOME's development infrastructure. Thanks to Carlos Soriano, Andrea Veri, Philip Chimento, Alberto Ruiz, and all the people that made the move to Gitlab possible.

  8. A mini-rant on the lack of string slices in C

    - librsvg, rust

    Porting of librsvg to Rust goes on. Yesterday I started porting the C code that implements SVG's <text> family of elements. I have also been replacing the little parsers in librsvg with Rust code.

    And these days, the lack of string slices in C is bothering me a lot.

    What if...

    It feels like it should be easy to just write something like

    typedef struct {
        const char *ptr;
        size_t len;
    } StringSlice;
    

    And then a whole family of functions. The starting point, where you slice a whole string:

    StringSlice
    make_slice_from_string (const char *s)
    {
        StringSlice slice;
    
        assert (s != NULL);
    
        slice.ptr = s;
        slice.len = strlen (s);
        return slice;
    }
    

    But that wouldn't keep track of the lifetime of the original string. Okay, this is C, so you are used to keeping track of that yourself.

    Onwards. Substrings?

    StringSlice
    make_sub_slice(StringSlice slice, size_t start, size_t len)
    {
        StringSlice sub;
    
        assert (len <= slice.len);
        assert (start <= slice.len - len);  /* Not "start + len <= slice.len" or it can overflow. */
                                            /* The subtraction can't underflow because of the previous assert */
        sub.ptr = slice.ptr + start;
        sub.len = len;
        return sub;
    }
    

    Then you could write a million wrappers for g_strsplit() and friends, or equivalents to them, to give you slices instead of C strings. But then:

    • You have to keep track of lifetimes yourself.

    • You have to wrap every function that returns a plain "char *"...

    • ... and every function that takes a plain "char *" as an argument, without a length parameter, because...

    • You CANNOT take slice.ptr and pass it to a function that just expects a plain "char *", because your slice does not include a nul terminator (the '\0 byte at the end of a C string). This is what kills the whole plan.

    Even if you had a helper library that implements C string slices like that, you would have a mismatch every time you needed to call a C function that expects a conventional C string in the form of a "char *". You need to put a nul terminator somewhere, and if you only have a slice, you need to allocate memory, copy the slice into it, and slap a 0 byte at the end. Then you can pass that to a function that expects a normal C string.

    There is hacky C code that needs to pass a substring to another function, so it overwrites the byte after the substring with a 0, passes the substring, and overwrites the byte back. This is horrible, and doesn't work with strings that live in read-only memory. But that's the best that C lets you do.

    I'm very happy with string slices in Rust, which work exactly like the StringSlice above, but &str is actually at the language level and everything knows how to handle it.

    The glib-rs crate has conversion traits to go from Rust strings or slices into C, and vice-versa. We alredy saw some of those in the blog post about conversions in Glib-rs.

    Sizes of things

    Rust uses usize to specify the size of things; it's an unsigned integer; 32 bits on 32-bit machines, and 64 bits on 64-bit machines; it's like C's size_t.

    In the Glib/C world, we have an assortment of types to represent the sizes of things:

    • gsize, the same as size_t. This is an unsigned integer; it's okay.

    • gssize, a signed integer of the same size as gsize. This is okay if used to represent a negative offset, and really funky in the Glib functions like g_string_new_len (const char *str, gssize len), where len == -1 means "call strlen(str) for me because I'm too lazy to compute the length myself".

    • int - broken, as in libxml2, but we can't change the API. On 64-bit machines, an int to specify a length means you can't pass objects bigger than 2 GB.

    • long - marginally better than int, since it has a better chance of actually being the same size as size_t, but still funky. Probably okay for negative offsets; problematic for sizes which should really be unsigned.

    • etc.

    I'm not sure how old size_t is in the C standard library, but it can't have been there since the beginning of time — otherwise people wouldn't have been using int to specify the sizes of things.

  9. Code Hospitality

    - software with living structure

    Recently on the Greater than Code podcast there was an episode called "Code Hospitality", by Nadia Odunayo.

    Nadia talks about thinking of how to make people comfortable in your code and in your team/organization/etc., and does it in terms of thinking about host/guest relationships. Have you ever stayed in an AirBnB where the host carefully prepares some "welcome instructions" for you, or puts little notes in their apartment to orient/guide you, or gives you basic guidance around their city's transportation system? We can think in similar ways of how to make people comfortable with code bases.

    This of course hit me on so many levels, because in the past I've written about analogies between software and urbanism/architecture. Software that has the Quality Without A Name talks about Christopher Alexander's architecture/urbanism patterns in the context of software, based on Richard Gabriel's ideas, and Nikos Salingaros's formalization of the design process. Legacy Systems as Old Cities talks about how GNOME evolved parts of its user-visible software, and makes an analogy with cities that evolve over time instead of being torn down and rebuilt, based on urbanism ideas by Jane Jacobs, and architecture/construction ideas by Stewart Brand.

    I definitely intend to do some thinking on Nadia's ideas for Code Hospitality and try to connect them with this.

    In the meantime, I've just rewritten the README in gnome-class to make it suitable as an introduction to hacking there.

  10. Rust+GNOME Hackfest in Berlin, 2017

    - Berlin, gnome, hackfests, rust

    Last weekend I was in Berlin for the second Rust+GNOME Hackfest, kindly hosted at the Kinvolk office. This is in a great location, half a block away from the Kottbusser Tor station, right at the entrance of the trendy Kreuzberg neighborhood — full of interesting people, incredible graffitti, and good, diverse food.

    Rug of Kottbusser Tor

    My goals for the hackfest

    Over the past weeks I had been converting gnome-class from the old lalrpop-based parser into the new Procedural Macros framework for Rust, or proc-macro2 for short. To do this the parser for the gnome-class mini-language needs to be rewritten from being specified in a lalrpop grammar, to using Rust's syn crate.

    Syn is a parser for Rust source code, written as a set of nom combinator parser macros. For gnome-class we want to extend the Rust language with a few conveniences to be able to specify GObject classes/subclasses, methods, signals, properties, interfaces, and all the goodies that GObject Introspection would expect.

    During the hackfest, Alex Crichton, from the Rust core team, kindly took over my baby steps in compiler writing and made everything much more functional. It was invaluable to have him there to reason about macro hygiene (we are generating an unhygienic macro!), bugs in the quoting system, and general Rust-iness of the whole thing.

    I was also able to talk to Sebastian Dröge about his work in writing GObjects in Rust by hand, for GStreamer, and what sort of things gnome-class could make easier. Sebastian knows GObject very well, and has been doing awesome work in making it easy to derive GObjects by hand in Rust, without lots of boilerplate — something with which gnome-class can certainly help.

    I was also looking forward to talking again with Guillaume Gomez, one of the maintainers of gtk-rs, and who does so much work in the Rust ecosystem that I can't believe he has time for it all.

    Graffitti heads

    Extend the Rust language for GObject? Like Vala?

    Yeah, pretty much.

    Except that instead of a wholly new language, we use Rust as-is, and we just add syntactic constructs that make it easy to write GObjects without boilerplate. For example, this works right now:

    #![feature(proc_macro)]
    
    extern crate gobject_gen;
    
    #[macro_use]
    extern crate glib;
    use gobject_gen::gobject_gen;
    
    gobject_gen! {
        // Derives from GObject
        class One {
        }
    
        impl One {
            // non-virtual method
            pub fn one(&self) -> u32 {
                1
            }
    
            virtual fn get(&self) -> u32 {
                1
            }
        }
    
        // Inherits from our other class
        class Two: One {
        }
    
        impl One for Two {
            // overrides the virtual method
            // maybe we should use "override" instead of "virtual" here?
            virtual fn get(&self) -> u32 {
                2
            }
        }
    }
    
    #[test]
    fn test() {
        let one = One::new();
        let two = Two::new();
    
        assert!(one.one() == 1);
        assert!(one.get() == 1);
        assert!(two.one() == 1);
        assert!(two.get() == 2);
    }
    

    This generates a little boatload of generated code, including a good number of unsafe calls to GObject functions like g_type_register_static_simple(). It also creates all the traits and paraphernalia that Glib-rs would create for the Rust binding of a normal GObject written in C.

    The idea is that from the outside world, your generated GObject classes are indistinguishable from GObjects implemented in C.

    The idea is to write GObject libraries in a better language than C, which can then be consumed from language bindings.

    Current status of gnome-class

    Up to about two weeks before the hackfest, the syntax for this mini-language was totally ad-hoc and limited. After a very productive discussion on the mailing list, we came up with a better syntax that definitely looks more Rust-like. It is also easier to implement, since the Rust parser in syn can be mostly reused as-is, or pruned down for the parts where we only support GObject-like methods, and not all the Rust bells and whistles (generics, lifetimes, trait bounds).

    Gnome-class supports deriving classes directly from the basic GObject, or from other GObject subclasses in the style of glib-rs.

    You can define virtual and non-virtual methods. You can override virtual methods from your superclasses.

    Not all argument types are supported. In the end we should support argument types which are convertible from Rust to C types. We need to finish figuring out the annotations for ownership transfer of references.

    We don't support GObject signals yet; I think that's my next task.

    We don't support GObject properties yet.

    We don't support defining new GType interfaces yet, but it is planned. It should be easy to support implementing existing interfaces, as it is pretty much the same as implementing a subclass.

    The best way to see what works right now is probably to look at the examples, which also work as tests.

    Digression on macro hygiene

    Rust macros are hygienic, unlike C macros which work just through textual substitution. That is, names declared inside Rust macros will not clash with names in the calling code.

    One peculiar thing about gnome-class is that the user gives us a few names, like a class name Foo and some things inside it, say, a method name bar, and a signal baz and a property qux. From there we want to generate a bunch of boilerplate for GObject registration and implementaiton. Some of the generated names in that boilerplate would be

    Foo              // base name
    FooClass         // generated name for the class struct
    Foo::bar()       // A method
    Foo::emit_baz()  // Generated from the signal name
    Foo::set_qux()   // Generated property setter
    foo_bar()        // Generated C function for a method call
    foo_get_type()   // Generated C function that all GObjects have
    

    However, if we want to actually generate those names inside our gnome-class macro and make them visible to the caller, we need to do so unhygienically. Alex started started a very interesting discussion on macro hygiene, so expect some news in the Rust world soon.

    TL;DR: there is a difference between a code generator, which gnome-class mostly intends to be, and a macro system which is just an aid in typing repetitive code.

    Fuck wars

    People for whom to to be thankful

    During the hackfest, Nirbheek has been porting librsvg from Autotools to the Meson build system, and dealing with Rust peculiarities along the way. This is exactly what I needed! Thanks, Nirbheek!

    Sebastian answered many of my questions about GObject internals and how to use them from the Rust side.

    Zeeshan took us to a bunch of good restaurants. Korean, ramen, Greek, excellent pizza... My stomach is definitely thankful.

    Berlin

    I love Berlin. It is a cosmopolitan, progressive, LGBTQ-friendly city, with lots of things to do, vast distances to be traveled, with good public transport and bike lanes, diverse food to be eaten along the way...

    But damnit, it's also cold at this time of the year. I don't think the weather was ever above 10°C while we were there, and mostly in a constant state of not-quite-rain. This is much different from the Berlin in the summer that I knew!

    Hackers at Kimchi Princess

    This is my third time visiting Berlin. The first one was during the Desktop Summit in 2011, and the second one was when my family and I visited the city two years ago. It is a city that I would definitely like to know better.

    Thanks to the GNOME Foundation...

    ... for sponsoring my travel and accomodation during the hackfest.

    Sponsored by the GNOME Foundation

Page 1 / 3 »