patterns of refactoring c to rust: the case of librsvg code testable. refactoring, refactoring,...

Download Patterns of Refactoring C to Rust: The case of librsvg code testable. Refactoring, refactoring, refactoring

Post on 26-May-2020

3 views

Category:

Documents

0 download

Embed Size (px)

TRANSCRIPT

  • Patterns of Refactoring C to Rust:

    The case of librsvg

    Federico Mena Quintero federico@gnome.org

    GUADEC 2018

    This is Ferris the Crustacean; she is the Rust mascot.

    http://www.rustacean.net/

    Last year I gave a talk at GUADEC about why I wanted to port librsvg from C to Rust. I highlighted the benefits of the language, and showed that the overall strategy was working fine.

    A year has passed, and now we have a team of people working on the Rustification of librsvg. I want to show you how we are doing it, and some common code patterns that have emerged.

    http://www.rustacean.net/

  • Purpose in life: Parse an SVG, render it to a Cairo context.

    GNOME icon themes, SVG file thumbnailer, image viewer, games.

    Applications: GIMP, gcompris, claws-mail, darktable.

    Desktop environments: Mate-panel, Enlightenment, Emacs.

    Everywhere: ImageMagick, Wikipedia, “we render PDFs of business data with rsvg-convert”.

    Librsvg

    Librsvg is an old library. We use it in many places in GNOME. Many other projects use it, too. I think it’s fair to say that librsvg is a basic infrastructure library.

  • Librsvg team Ivan Molodetskikh - Summer of Code 2018, SVG filter effects

    Jordan Petridis - Gitlab CI extraordinaire, rustification

    Paolo Borelli - long-time GNOME contributor, rustification/refactoring

    Chun-wei Fan - Windows build infrastructure

    Dmitry Kontsevoy - rustification, refactoring

    Saurav Sachidanand - rustification, refactoring

    Brion Vibber Chris Lamb David Michael Fabrice Fontaine Guillaume Gomez Igor Gnatenko Jehan Jeremy Bicha Juraj Fiala

    Lovell Fuller Massimo Philip Withnall Andreas Smas Sebastian Dröge Tim Lunn Timm Bäder Ting-Wei Lan

    It’s hard to correlate things, but librsvg got an influx of new people with the advent of gitlag.gnome.org and our adoption of the Contributor Covenant code of conduct.

    I am very, very thankful to everyone that has contributed to librsvg.

  • As users, I hope you didn't notice changes

    ● Rule: Don't break existing code.

    ● Keep the public API / ABI intact.

    Librsvg has a very small API: basically, load this SVG file, or a stream, and render it to a raster image.

    We have kept the API/ABI intact; as far as I know, no programs have required changes in how they use librsvg.

  • As distributors, I'm sure you noticed changes!

    ● Sorry that you had to introduce a Rust toolchain.

    ● I feel your pain - did this at Suse.

    ● Firefox would have compelled encouraged you anyway.

    For distributors, it’s a different story. I have no shame in admitting that I hoped that the introduction of Rust in Firefox would make it easier to push Rust in librsvg, because now distros are required to ship a Rust toolchain.

    And it is a pain in the ass to introduce a new language toolchain in distros! As a Suse person, ask me how I know...

  • What is with all that old code?

    ● https://twitter.com/sarahmei/status/893237308316565505

    ● Librsvg started in 2001. ● It was written while the SVG spec was being written. ● Lots of loose ends. ● Predates Cairo (got introduced into librsvg in 2005). ● Provided a Mozilla plugin, before Mozilla had an SVG engine. ● Provided a GTK+ theme engine.

    Librsvg was created in rather dire circumstances. It was written while the SVG spec was being written, so some of the code was... questionable.

    https://twitter.com/sarahmei/status/893237308316565505

  • Working Effectively with Legacy Code

    ● Legacy code is code without tests.

    ● Many techniques for breaking dependencies and making code testable.

    ● Refactoring, refactoring, refactoring.

    Let me recommend this book. It has very practical advice for how to approach code which you don’t know, which has few or no tests, and which is just old and creaky.

    “Working Effectively with Legacy Code”, by Michael C. Feathers.

    I found it through Katrina Owen’s bibliography. You should check out her videos; I think they are some of the best material on refactoring that you’ll find.

  • Disclaimers about librsvg ● Computation-heavy library. ● Uses few external APIs. ● Some libxml2, some libcroco. ● A lot of Cairo. ● No interesting I/O. ● No threading (yet). ● No internal GObjects. ● Your code is probably very different!

    Librsvg is not a typical GNOME library. We don’t do GUI stuff; we don’t have internal GObjects, we don’t do fancy I/O, we don’t have threads. It’s just a load of computation.

    So, the patterns that will emerge if you Rustify other kinds of code will be different. Still, I hope you’ll find useful ideas here.

  • Acceptance ● I knew very little of librsvg's internals.

    ● "Keep it working" - the only thing to hang on to.

    ● Will end up touching every line of code.

    ● Does not automatically mean having to understand what everything does.

    ● Or why.

    One point I want to make in this talk is that you don’t need to know a code base well at all to start improving it, or even porting it to Rust.

    I barely knew the librsvg code when I started maintaining it. Refactoring it has made me learn the source code gradually. There are still parts of the code that I don’t really know what they do.

  • General Strategy ● Get the build system going.

    ● Compute-only code is easier than API-heavy code.

    ● Wrapping unsafe C APIs with safe Rust APIs works very well!

    ● Accessors on opaque pointers are your friends.

    Some general recommendations. Integrating a Rust sub-library into your existing build system is awkward. Just do it once and improve it gradually. Feel free to steal librsvg’s autotools code!

    It’s easier to start porting computation-only code first. Look for leaf functions that don’t call lots of external stuff.

    The most important tool for porting librsvg’s code gradually was to wrap everything under opaque pointers, and provide accessor functions to that data. Those functions can be called from C or from Rust, as needed.

  • Integrating build systems ● Librsvg uses autotools ● Rust uses cargo ● librsvg.a - built from C ● librsvg_internals.a - built from

    Rust ● In Cargo.toml:

    ● Do this once, improve it gradually.

    librsvg/ configure.ac Makefile.am librsvg/ *.[ch]

    rsvg_internals/ Cargo.toml src/ *.rs target/

    [lib] name = "rsvg_internals" crate-type = ["staticlib"]

    A 10,000-meter view of librsvg’s build system. It’s based on autotools. librsvg.so gets linked from a bunch of .o files built from C, and a librsvg_internals.a which is built from Rust.

    There’s plenty of autotools / cargo magic for things like cross-compilation; just steal them from librsvg’s build system and be done with it.

  • ~ Musical Interlude ~

  • Draining a massive struct

    Field by field

    From C to Rust

    Let’s look at something we have done a couple of times in librsvg. We take a big struct full of fields, and migrate them one by one to Rust.

  • struct RsvgState { cairo_matrix_t affine;

    cairo_fill_rule_t fill_rule; gboolean has_fill_rule;

    cairo_fill_rule_t clip_rule; gboolean has_clip_rule;

    RsvgPaintServer *stroke; gboolean has_stroke_server;

    guint8 stroke_opacity; gboolean has_stroke_opacity;

    RsvgLength stroke_width; gboolean has_stroke_width;

    double miter_limit; gboolean has_miter_limit;

    ...

    };

    RsvgState holds the CSS state for an SVG element.

    Here at the bottom you see a element with a stroke-width property.

    Since the property has a value, the has_stroke_value field will be set to true, and the stroke_width field will have a length of 2.

  • struct RsvgState { cairo_matrix_t affine;

    cairo_fill_rule_t fill_rule; gboolean has_fill_rule;

    cairo_fill_rule_t clip_rule; gboolean has_clip_rule;

    RsvgPaintServer *stroke; gboolean has_stroke_server;

    guint8 stroke_opacity; gboolean has_stroke_opacity;

    RsvgLength stroke_width; gboolean has_stroke_width;

    double miter_limit; gboolean has_miter_limit;

    ...

    };

    Some isolated fields

    RsvgState also has some independent fields that sometimes come directly from SVG elements, and sometimes get computed afterwards.

  • struct RsvgState { cairo_matrix_t affine;

    cairo_fill_rule_t fill_rule; gboolean has_fill_rule;

    cairo_fill_rule_t clip_rule; gboolean has_clip_rule;

    RsvgPaintServer *stroke; gboolean has_stroke_server;

    guint8 stroke_opacity; gboolean has_stroke_opacity;

    RsvgLength stroke_width; gboolean has_stroke_width;

    double miter_limit; gboolean has_miter_limit;

    ...

    };

    cairo_fill_rule_t rsvg_state_get_fill_rule (RsvgState *state);

    gboolean rsvg_state_get_has_fill_rule (RsvgState *state);

    Add accessors

    Use them everywhere

    We start by changing all the C code to use accessor functions for these

View more >