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

125
Patterns of Refactoring C to Rust: The case of librsvg Federico Mena Quintero [email protected] 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.

Upload: others

Post on 26-May-2020

37 views

Category:

Documents


0 download

TRANSCRIPT

Patterns of Refactoring C to Rust:

The case of librsvg

Federico Mena [email protected]

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.

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 VibberChris LambDavid MichaelFabrice FontaineGuillaume GomezIgor GnatenkoJehanJeremy BichaJuraj Fiala

Lovell FullerMassimoPhilip WithnallAndreas SmasSebastian DrögeTim LunnTimm BäderTing-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 hopeyou 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 sureyou 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.

Working Effectivelywith 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/ <built lib>

[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;

...

};

<rect x=”5” y=”5” width=”10” height=”10” stroke=”black” stroke_width=”2”/>

RsvgState holds the CSS state for an SVG element.

Here at the bottom you see a <rect> 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 fields, instead of using them directly.

This makes the code more verbose for a while, but we’ll end up making it pretty in the end.

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;

...

};

Let’s start draining the struct

Okay, let’s start to drain the struct 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;

...

StateRust *state_rust;};

typedef struct StateRust StateRust;

First, we add an escape hatch: an pointer to an opaque struct that will live in the Rust heap.

This is like drilling a hole in the C struct. Every field will exit through the hole and end up in a Rust struct.

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;

...

StateRust *state_rust;};

struct StateRust { }

#[no_mangle]pub extern fn state_rust_new() -> *mut StateRust;

#[no_mangle]pub extern fn state_rust_free( state: *mut StateRust);

We start with an empty struct in Rust, and functions that we can call from C to allocate it and free it.

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;

...

StateRust *state_rust;};

struct StateRust { }

We take our empty struct...

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>,}

And move the first field to it.

In librsvg we were able to replace has_fill_rule and fill_rule with Option<FillRule>.

Here we were able to use the FillRule enum from the cairo-rs bindings. You may need to define your own types or port them from C.

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>,}

#[no_mangle]pub externfn state_rust_has_fill_rule( state: *const StateRust) -> gboolean;

#[no_mangle]pub externfn state_rust_get_fill_rule( state: *const StateRust) -> cairo::FillRule;

We also add accesors for the Rust struct, in Rust, that can be called from the C code. You can see state_rust_has_fill_rule() and state_rust_get_fill_rule().

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>,}

#[no_mangle]pub externfn state_rust_has_fill_rule( state: *const StateRust) -> gboolean;

#[no_mangle]pub externfn state_rust_get_fill_rule( state: *const StateRust) -> cairo::FillRule;

Pointersto struct

These functions take a pointer to a StateRust struct...

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>,}

#[no_mangle]pub externfn state_rust_has_fill_rule( state: *const StateRust) -> gboolean;

#[no_mangle]pub externfn state_rust_get_fill_rule( state: *const StateRust) -> cairo::FillRule;

C types

... and since they will be called from C code, we have to return C types.

struct RsvgState { cairo_matrix_t affine;

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>,}

#[no_mangle]pub externfn state_rust_has_fill_rule( state: *const StateRust) -> gboolean;

#[no_mangle]pub externfn state_rust_get_fill_rule( state: *const StateRust) -> cairo::FillRule;

On the C side, we remove the fields from the C struct...

struct RsvgState { cairo_matrix_t affine;

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>,}

#[no_mangle]pub externfn state_rust_has_fill_rule( state: *const StateRust) -> gboolean;

#[no_mangle]pub externfn state_rust_get_fill_rule( state: *const StateRust) -> cairo::FillRule;

gbooleanrsvg_state_has_fill_rule( RsvgState *state) { return state_rust_has_fill_rule( state->state_rust);}

... and we change the accessors to use the Rust functions to get the values from the Rust struct instead.

This might seem like too much indirection, and it *is*. But remember, this is a temporary state. All the code will be in Rust eventually, and will be able to access the Rust struct’s fields directly instead of going through functions all the time.

struct RsvgState { cairo_matrix_t affine;

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>,}

We do the same. Here, we do the clip_rule field...

struct RsvgState { cairo_matrix_t affine;

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>,}

... remove the C version...

struct RsvgState { cairo_matrix_t affine;

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;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>,}

We do the stroke field...

struct RsvgState { cairo_matrix_t affine;

guint8 stroke_opacity; gboolean has_stroke_opacity;

RsvgLength stroke_width; gboolean has_stroke_width;

double miter_limit; gboolean has_miter_limit;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>,}

Remove the C version...

struct RsvgState { cairo_matrix_t affine;

guint8 stroke_opacity; gboolean has_stroke_opacity;

RsvgLength stroke_width; gboolean has_stroke_width;

double miter_limit; gboolean has_miter_limit;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>,}

Stroke_opacity...

struct RsvgState { cairo_matrix_t affine;

RsvgLength stroke_width; gboolean has_stroke_width;

double miter_limit; gboolean has_miter_limit;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

Etcetera, for all the remaining fields that have a similar shape.

struct RsvgState { cairo_matrix_t affine;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

Now we only have a few remaining fields in the C struct on the left.

struct RsvgState { cairo_matrix_t affine;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

I asked my daughter if she could draw Ferris moving things from one place to another. Baskets of eggs seem like an appropriate metaphor.

struct RsvgState { cairo_matrix_t affine;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

Let’s fix that whitespace...

struct RsvgState { cairo_matrix_t affine;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

Much better.

struct RsvgState { cairo_matrix_t affine;

...

StateRust *state_rust;};

struct StateRust { fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

We now turn to the fields that are “different”. Here we had to move the affine field in a different way, since it is not computed in the same way as other CSS properties.

struct RsvgState { cairo_matrix_t affine;

...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

Anyway, it’s moved to Rust...

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

... and deleted from the C structure.

struct RsvgState { ...

StateRust *state_rust;};

cairo_matrix_t rsvg_state_get_affine(RsvgState *state){ return state_rust_get_affine(state->state_rust);}

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

As a reminder, here is the C accessor, which calls the Rust accessor...

struct RsvgState { ...

StateRust *state_rust;};

cairo_matrix_t rsvg_state_get_affine(RsvgState *state){ return state_rust_get_affine(state->state_rust);}

#[no_mangle]pub extern fn state_rust_get_affine(s: *const StateRust) -> cairo::Matrix{ let state = unsafe { &*s };

state.affine}

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

Which accesses the Rust struct directly.

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

Meanwhile, port all the C functions that deal with RsvgState

Plenty of refactoring involved

After the fields are moved, we ported the code that actually used them from C to Rust.

But on the Rust side, how can we access this structure, if part of it still lives in C and part lives in Rust?

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

The Rust side of things looks like this.

We want to be passed a pointer from C, to an RsvgState struct. From Rust’s viewpoint, this is an opaque structure that lives in the C world.

The nomicon recommends using an unconstructible struct like this, just to be able to take a pointer to it; see the link to read an explanation of why this struct in particular. This trick will get easier once extern types are supported.

Nomicon’s recommendation: https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs

Extern types: https://github.com/rust-lang/rust/issues/43467

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

On the C side, we export a function...

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

... called rsvg_state_get_state_rust().

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

Given a pointer to an RsvgState struct, which lives in C, ...

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

... it will give us back a pointer to the StateRust struct. This comes from the state_rust field above.

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

We create a helper function, state_get_rust(). Any drawing function that needs to access one of the CSS properties, when it has a pointer to a C state, will call this function.

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

It takes a pointer to an RsvgState...

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

... returns a reference to a StateRust...

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

... and calls the function that we exported from C.

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

Now, whenever we port one of the drawing functions that take in a pointer to an RsvgState, ...

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

We can first call state_get_rust() to get our Rust struct, ...

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

#[repr(C)]pub struct RsvgState { _private: [u8; 0]}

extern “C” { fn rsvg_state_get_state_rust(state: *const RsvgState) -> *const StateRust;}

fn state_get_rust(state: *const RsvgState) -> &StateRust { &*rsvg_state_get_state_rust(state)}

fn do_something(state: *const RsvgState) { let rstate = state_get_rust(state);

blah_blah(rstate.stroke_width);}

... and actualy use its fields directly.

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

fn do_something(state: &State) {

blah_blah(state.stroke_width);}

In the end, we won’t be taking raw pointers to C structs, since everything will live completely in Rust. We will also be able to access Rust fields directly without first calling a helper function to obtain the struct that contains them.

struct RsvgState { ...

StateRust *state_rust;};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

Once we have ported all the code that actually used those struct fields...

struct RsvgState {

};

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

... the C struct can actually become empty...

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

... we can remove it...

struct StateRust { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

... and ...

struct State { affine: cairo::Matrix;

fill_rule: Option<FillRule>, clip_rule: Option<FillRule>, stroke: Option<PaintServer>, stroke_opacity: Option<u8>, stroke_width: Option<RsvgLength>, miter_limit: Option<f64>,}

... we can rename our Rust struct to be the one and only one.

I haven’t found a good way to make this change, other than fixing all the compiler errors one by one to catch all the renamings.

Also, every time a C struct goes away and only a Rust struct remains, I have had to fix a bunch of borrow-checker errors: where functions took *const RsvgFoo before, now they take &Foo - and lots of assumptions about mutability, etc. were wrong and had to be fixed. This is painful while you get it to compile, but once you are done, the code is nice and sound.

let state = drawing_ctx::get_current_state(draw_ctx);let rstate = state::get_state_rust(state);

foo(rstate.some_prop); // we have the properties in Rust

bar(state::get_other_prop(state)); // prop that is not in Rust yet

In the middle of a refactor

Remember when we had part of the struct in C, and part of it in Rust?

let state = drawing_ctx::get_current_state(draw_ctx);let rstate = state::get_state_rust(state);

foo(rstate.some_prop); // we have the properties in Rust

bar(state::get_other_prop(state)); // prop that is not in Rust yet

In the middle of a refactor

This is what some of the relevant code looks like.

First we obtain a “state”, which comes from somewhere else, and is a pointer to the C struct.

let state = drawing_ctx::get_current_state(draw_ctx);let rstate = state::get_state_rust(state);

foo(rstate.some_prop); // we have the properties in Rust

bar(state::get_other_prop(state)); // prop that is not in Rust yet

In the middle of a refactor

Then we get the Rust part of that opaque struct.

let state = drawing_ctx::get_current_state(draw_ctx);let rstate = state::get_state_rust(state);

foo(rstate.some_prop); // we have the properties in Rust

bar(state::get_other_prop(state)); // prop that is not in Rust yet

In the middle of a refactor

Then we can directly use the fields that have already been moved to Rust...

let state = drawing_ctx::get_current_state(draw_ctx);let rstate = state::get_state_rust(state);

foo(rstate.some_prop); // we have the properties in Rust

bar(state::get_other_prop(state)); // prop that is not in Rust yet

In the middle of a refactor

And for the fields that are still in C, we use helper functions to access them instead.

In the middle of a refactorlet state = drawing_ctx::get_current_state(draw_ctx);let rstate = state::get_state_rust(state);

foo(rstate.some_prop); // we have the properties in Rust

bar(state::get_other_prop(state)); // prop that is not in Rust yet

This makes the code look quite ugly for a while, but I promise you, this is fine.

All the code will become clean in the end, and you won’t have functions to go back and forth between C and Rust.

#[repr(C)]● It works great!● But we wanted fields that are not #[repr(C)]● We use it for small, shared structs

/* Keep this in sync with rust/src/length.rs:RsvgLength */typedef struct { double length; LengthUnit unit; LengthDir dir;} RsvgLength;

// Keep this in sync with ../../rsvg-private.h:RsvgLength#[repr(C)]#[derive(Debug, PartialEq, Copy, Clone)]pub struct RsvgLength { pub length: f64, pub unit: LengthUnit, dir: LengthDir,}

People who have interfaced Rust and C before may be asking themselves, what if you just made the whole C struct visible from Rust, using #[repr(C)]?

That works great when all the fields in the struct can be represented by both C and Rust.

But we wanted to have a better representation, made possible by Rust’s richer type system.

In librsvg we use #[repr(C)] for small structs or enum with simple data types. It works very well.

We also have comments to remind people to keep the memory layout of the structs in sync, just in case.

Refactoring to beautiful Rustpub enum StrokeLinejoin { Miter, Round, Bevel, Inherit,}

I’ve found that refactoring Rust is a very pleasant experience.

Let me show you how we refactored some verbose code into very powerful, simple code.

Some CSS properties, like StrokeLinejoin there, have different possible values. That property is for how vertices look in paths.

pub enum StrokeLinejoin { Miter, Round, Bevel, Inherit,}

impl Default for StrokeLinejoin { fn default() -> StrokeLinejoin { StrokeLinejoin::Miter }}

The CSS spec says that if the StrokeLinejoin property is not specified, it defaults to Miter. So, we encode that with the Default trait.

impl Parse for StrokeLinejoin { type Data = (); type Err = AttributeError;

fn parse(s: &str, _: Self::Data) -> Result<StrokeLinejoin, AttributeError> { match s.trim() { "miter" => Ok(StrokeLinejoin::Miter), "round" => Ok(StrokeLinejoin::Round), "bevel" => Ok(StrokeLinejoin::Bevel), "inherit" => Ok(StrokeLinejoin::Inherit), _ => Err(AttributeError::from(ParseError::new("bad syntax"))), } }}

We also need to parse the property. The details are not very important; it’s basically seeing if it is one of the strings “miter”, “round”, “bevel”, and using the corresponding enum value.

pub enum StrokeLinecap { Butt, Round, Square, Inherit,}

impl Default for StrokeLinecap { fn default() -> StrokeLinecap { StrokeLinecap::Butt }}

Here’s a similar property, for StrokeLinecap. It has some possible values,

pub enum StrokeLinecap { Butt, Round, Square, Inherit,}

impl Default for StrokeLinecap { fn default() -> StrokeLinecap { StrokeLinecap::Butt }}

And the default is Butt.

Yes, the default is Butt.

make_ident_property!( StrokeLinecap, default: Butt,

"butt" => Butt, "round" => Round, "square" => Square, "inherit" => Inherit,);

make_ident_property!( StrokeLinejoin, default: Miter,

"miter" => Miter, "round" => Round, "bevel" => Bevel, "inherit" => Inherit,);

We started with pretty much the same code for specifying the Default and the parser.

That was a cut-and-paste job. The next step was to turn it into a macro.

Here we have the make_ident_property! macro (macros in Rust end with a bang ! character).

It simply expands to the code we had before, but now we can call it with different value names, instead of having lots of code duplication.

make_property!( ComputedValues, StrokeOpacity, default: UnitInterval(1.0), inherits_automatically: true, newtype_parse: UnitInterval, parse_data_type: ());

The macro then evolved to support properties that are not simple symbols.

Here we can define the StrokeOpacity property,

make_property!( ComputedValues, StrokeOpacity, default: UnitInterval(1.0), inherits_automatically: true, newtype_parse: UnitInterval, parse_data_type: ());

Whose default value is 1.0,

make_property!( ComputedValues, StrokeOpacity, default: UnitInterval(1.0), inherits_automatically: true, newtype_parse: UnitInterval, parse_data_type: ());

And for which we use a type called UnitInterval. SVG says that opacity values must be in the range [0, 1], and that any values out of that range should get clipped to be in range.

Our UnitInterval type performs this checking and clipping automatically.

make_property!( ComputedValues, TextDecoration, inherits_automatically: false,

fields: { overline: bool, default: false, underline: bool, default: false, strike: bool, default: false, }

parse_impl: { impl Parse for TextDecoration { ... } }}

The make_property! macro has evolved a lot; now we can even define properties that are not simple symbols, but instead have custom types and parsers.

For example, the text-decoration property is pretty complex. We use the variant of the macro that lets us define custom-everything.

This is Ferris in her Yak shaving boutique.

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

Let me show you an example of turning C code that used iterators into Rust.

Here we are extracting key/value pairs from a property bag, parsing the values, and stopping if we find an error during parsing.

First we initialize a boolean variable for success.

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

We create an iterator on “atts”, short for “attributes”, which is a property bag.

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

While everything is okay,

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

... and there is an extra item in the iterator, get the key/value pair from it...

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

Try to parse it, and update our “success” value.

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

Finally, free the iterator.

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

If we found an error, well, the C code didn’t really have machinery for propagating errors up in the call chain. I decided to do it until we ported that code to Rust, since it would be easier there.

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

The Rust version is a small for() loop.

For each (key, attr, value) tuple in the property bag’s iterator, ...

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

... parse the value, ...

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

return an error value if parsing is not successful,

Iterators are awesome- success = TRUE;-- iter = rsvg_property_bag_iter_begin (atts);-- while (success && - rsvg_property_bag_iter_next (- iter, &key, &attr, &value))- {- success = rsvg_state_parse_style_pair(- state, attr, value, FALSE, FALSE);- }-- rsvg_property_bag_iter_end (iter);-- if (!success) {- return; /* FIXME: propagate errors upstream */- }

+ for (_key, attr, value) in pbag.iter() {+ self.parse_style_pair(attr, value, false, false)?;+ }++ Ok(())

and finally return Ok if we didn’t find any errors.

Exposing an iterator to Cimpl Foo { pub fn iter(&self) -> FooIter { ... }}

struct FooIter { ... };

impl Iterator for FooIter { type Item = MyItem;

fn next(&mut self) -> Option<MyItem> { ... }};

How do we expose that iterator to C?

Let’s say we have an object called Foo,

Exposing an iterator to Cimpl Foo { pub fn iter(&self) -> FooIter { ... }}

struct FooIter { ... };

impl Iterator for FooIter { type Item = MyItem;

fn next(&mut self) -> Option<MyItem> { ... }};

with an iter() method that returns a FooIter.

Exposing an iterator to Cimpl Foo { pub fn iter(&self) -> FooIter { ... }}

struct FooIter { ... };

impl Iterator for FooIter { type Item = MyItem;

fn next(&mut self) -> Option<MyItem> { ... }};

The details of how FooIter is implemented are not important here; it’s just whatever you would use to hold your iterator’s state, like the index of the current element in an array.

Exposing an iterator to Cimpl Foo { pub fn iter(&self) -> FooIter { ... }}

struct FooIter { ... };

impl Iterator for FooIter { type Item = MyItem;

fn next(&mut self) -> Option<MyItem> { ... }};

Rust asks that we implement the Iterator trait for FooIter,

Exposing an iterator to Cimpl Foo { pub fn iter(&self) -> FooIter { ... }}

struct FooIter { ... };

impl Iterator for FooIter { type Item = MyItem;

fn next(&mut self) -> Option<MyItem> { ... }};

We specify the type of items that the iterator will return, in this case MyItem,

Exposing an iterator to Cimpl Foo { pub fn iter(&self) -> FooIter { ... }}

struct FooIter { ... };

impl Iterator for FooIter { type Item = MyItem;

fn next(&mut self) -> Option<MyItem> { ... }};

And we implement the next() method for Iterator. Rust iterators return an Option<> of the type you specified, which is Some(MyType) if there is a following element, and None if there are no more elements.

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_begin( foo: *const Foo,) -> *mut FooIter { assert!(!foo.is_null()); let foo = &*foo; Box::into_raw(Box::new(foo.iter()))}

Now let’s expose this iterator to C.

We create a foo_iter_begin() function visible from C. This will allocate an iterator in the heap and return a pointer to it.

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_begin( foo: *const Foo,) -> *mut FooIter { assert!(!foo.is_null()); let foo = &*foo; Box::into_raw(Box::new(foo.iter()))}

This function takes a raw pointer to the object we will be iterating,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_begin( foo: *const Foo,) -> *mut FooIter { assert!(!foo.is_null()); let foo = &*foo; Box::into_raw(Box::new(foo.iter()))}

Returns a pointer to the iterator we will create,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_begin( foo: *const Foo,) -> *mut FooIter { assert!(!foo.is_null()); let foo = &*foo; Box::into_raw(Box::new(foo.iter()))}

Check that we didn’t get passed a null pointer,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_begin( foo: *const Foo,) -> *mut FooIter { assert!(!foo.is_null()); let foo = &*foo; Box::into_raw(Box::new(foo.iter()))}

Convert the raw pointer to a reference,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_begin( foo: *const Foo,) -> *mut FooIter { assert!(!foo.is_null()); let foo = &*foo; Box::into_raw(Box::new(foo.iter()))}

Call its iter() method,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_begin( foo: *const Foo,) -> *mut FooIter { assert!(!foo.is_null()); let foo = &*foo; Box::into_raw(Box::new(foo.iter()))}

Puts the iterator object on the heap by boxing it,

(You can think of this of calling g_malloc())

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_begin( foo: *const Foo,) -> *mut FooIter { assert!(!foo.is_null()); let foo = &*foo; Box::into_raw(Box::new(foo.iter()))}

And finally returns a raw pointer to the value in the heap.

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

Now let’s implement the iter_next() function. It is callable from C, ...

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

... and takes a pointer to the iterator,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

... a pointer to the location where it will write the next item,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

... and returns a boolean, saying whether it actually returned an item or not.

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

Check the arguments,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

Get a reference to the iterator,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

If the actual iterator returns an item,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

Copy the item to the out location,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

and return a true gboolean.

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

Otherwise, zero out the output value, just to avoid having uninitialized memory,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

And return false, meaning that we didn’t have an item to return.

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

#[no_mangle]pub unsafe extern "C" fn foo_iter_end(iter: *mut FooIter) { assert!(!iter.is_null()); Box::from_raw(iter);}

Finally, to free the iterator from C, we expose a function called iter_end(),

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

#[no_mangle]pub unsafe extern "C" fn foo_iter_end(iter: *mut FooIter) { assert!(!iter.is_null()); Box::from_raw(iter);}

which checks its argument,

Exposing an iterator to C#[no_mangle]pub unsafe extern "C" fn foo_iter_next( raw_iter: *mut FooIter, out_item: *mut MyItem,) -> glib_sys::gboolean { assert!(!iter.is_null()); assert!(!out_item.is_null()); let iter = &mut *raw_iter;

if let Some(item) = iter.next() { *out_item = item; true.to_glib() } else { *out_item = mem::zeroed(); false.to_glib() }}

#[no_mangle]pub unsafe extern "C" fn foo_iter_end(iter: *mut FooIter) { assert!(!iter.is_null()); Box::from_raw(iter);}

And frees the boxed iterator on the heap.

Ferris is happy to present you with a nice drawing!

Debugging by print statements

● printf() doesn’t flush stdout?● println!() flushes● Make output look like a Lisp sexp; indent with

an external tool

Sometimes I had to do printf-based debugging, because gdb is great, but printf is forever.

Word of warning: while C’s printf doesn’t always seem to flush stdout, Rust’s does. So, keep that in mind.

I wanted to look at some complicated code flow and needed logging with indentation. I didn’t want to write a logging mechanism with indentation.

The simplest hack was to output Lisp-like expressions, nested with parentheses...

Debugging by print statements

● printf() doesn’t flush stdout?● println!() flushes● Make output look like a Lisp sexp; indent with

an external tool

(myfunc arg1=”foo” arg2=”bar”(child_func x y zblah blah(somethinghappenedhere too)trace!))

The raw output looks like this,

Debugging by print statements

● printf() doesn’t flush stdout?● println!() flushes● Make output look like a Lisp sexp; indent with

an external tool

(myfunc arg1=”foo” arg2=”bar”(child_func x y zblah blah(somethinghappenedhere too)trace!))

(myfunc arg1=”foo” arg2=”bar” (child_func x y z blah blah (something happened here too ) trace! ))

And when I loaded it in my text editor, I could indent it with one keystroke to make it easy to read.

This let me find a few bugs conveniently.

Finally, how are we doing?

This is the Gitlab graph for programming languages. We have more Rust code than C code now, and the C code is shrinking very rapidly thanks to my Summer of Code intern, who is translating the filters code to Rust.

Resources

● Carol Nichols, Porting Zopfli from Rust to C - https://github.com/carols10cents/rust-out-your-c-talk

● Convert C to Rust automatically - https://c2rust.com/

● Katrina Owen’s refactoring talks - http://www.kytrinyx.com/talks/

Carol’s talk is what inspired me to learn Rust and start porting librsvg.

The second link is a relatively new tool, very interesting.

The third link is to Katrina Owen’s talks, the best material you will find on refactoring gnarly code without tests.

Thanks

● Luciana Mena-Silva (my daughter) for the Ferris drawings.

● Katrina Owen for the technique of narrating code live - http://www.kytrinyx.com/talks/

● Alberto Ruiz, Joaquín Rosales, Zeeshan Ali for the GNOME+Rust hackfests.

● irc.mozilla.org #rust-beginners● Jordan Petridis for making our CI amazing.● Paolo Borelli for relentlessly porting big chunks of code.

Blog posts

● https://people.gnome.org/~federico/blog/tag/librsvg.html

● https://people.gnome.org/~federico/blog/tag/rust.html