Sorting pixels in Rust

Some time ago I decided to make something in Rust so my snobbish commentaries regarding the language would, at least, be honest.

Truthful to my style, and because it’s usually the best way to try a language, I decided to do something industrialistic, something that takes command line arguments, has error messages and is efficient. I find that this kind of project leaves you with a bigger picture of the language, community and libraries than doing just an exercise on it’s features and ideas.

The project I’ll go trough in this blog post is a pixel sorter. It takes an image like this

and turns it into this

In this example, the pixels where sorted by redness which is the default, but it can sort them by many dimensions: red, green, blue, alpha, hue, saturation and lightness.

Cargo is love

Having worked with C++ a lot this year I think now I can really appreciate how great cargo is, and rustup too. Dependency handling, which can take literally between a day and a week in a new C++ project, is solved with 3 commands. Just wanted to say that, and cheers to the cargo team!

Also, for reference, this was done in Rust nightly 1.24.

First impressions

First of all, let me say I have but the deepest admiration to the team behind Rust. The language is barely 7 years old but it already feels extremely robust, and can, in occasion, feel really elegant.

On the other hand, getting my head around lifetimes was, I admit, tricky, but even worse were traits! I spent days just browsing trough the image crate’s documentation, trying to get my head around all the possible ways to access the underlying pixel data.

Where the sorters namespace has functions of the type (p: &&image::Rgba<u8>) -> u8.

Now, what’s up with *mode? This is actually quite interesting. We are matching on a reference, thus we can either match on references (for example, &options::Mode::Red) or dereference it. This language wart isn’t a big deal, but is nevertheless being looked at over at RFC 2005.

Here buf is an RgbImage = ImageBuffer<Rgb<u8>, Vec<u8>>. As hard as I tried, I couldn’t get to sort that without copying the data (at least I assume it is copying? Could clone() be optimized away?). And I can’t just access the underlying Vec<u8> because that’s flattened to it’s channels, elements aren’t pixels but single colors.

As it is now, it first copies everything into a temporary buf2, sorts that and then iterates over the original buffer, assigning each position the sorted result.

I think it is possible to avoid the copy, but I already spent too much time trying so if you kind reader know how, please contact me.

Tests?

Testing in cargo is okay. It’s cool to have testing directly built on the build tool, but it’s a little awkward to use and extremely dependent on file and folder names.

For example you need to include the test module in main:

#[cfg(test)]mod tests;

Nevertheless, it works like a charm.

Wrapping up

For me, the most amazing bit was the tooling. I haven’t tried the debugger (I’ve no idea about this, but LLDB, the llvm debugger, is an excellent tool and I guess a plug-in would work like a charm) but cargo, docs.rs and rustup make it really simple and ergonomic to work with other people’s code, which for me is a crucial aspect of modern software development.

Safety was also not a problem. I had zero out-of-range-access errors but the code is kind of high level, which makes it not so surprising.

Build times where quite short too: while an order of magnitude slower than C++, think about this: This blog is made in Haskell, and I had to rebuild it from scratch when starting to write this post because I had deleted the ~/.stack/ folder … well, it just finished building right now.