Trying out Rust for Graphics Programming (March 1st, 2017)¶
This post is a brain-dump of my experience jumping into Rust, where I took time off my regular job for the Blender Foundation to spend 7 months of uninterrupted time learning Rust.
My general goal is to use Rust for graphics application development, as an alternative to C/C++ which is dominant in this area.
Starting out I was careful not to reject Rust out of hand simply because some problem happened to take me longer to solve than I expected.
I think Eric Raymond’s “Rust severely disappoints me” blog post is an example of this. I just don’t think its reasonable to attempt to a new language in a week or so.
While I’m by no means a guru, I thought my experience learning Rust may still be interesting for others.
See reddit post for comments.
First Impressions and Expectations¶
Rust promotes it’s self as a systems language that allows higher level abstractions, while keeping the option open for writing low level code.
So going into Rust I assumed I would be able to write low-level code (my own primitive data-structures for example)
Having spent some time with Rust, I feel a little mislead, many features required for writing low-level code need features from nightly Rust. Possibly it’s my own fault for restricting myself to stable, however one of the reasons I took the time to learn Rust in the first place is they made a stable release, and using unstable features gives the real potential for code breaking in future releases.
While Rust’s ecosystem is new, when trying to solve problems that I’d assumed would have been easy, or at least possible... I would be linked to an RFC or a discussion in the issue tracker.
Some examples of this:
- How to detect when calling ‘drop’ is needed for a type?
- How to ensure structs implement functions consistently, without callers having to explicitly ‘use’ the trait?
- How to get the number of elements in an enum as a constant value?
- Possible to avoid repeating struct name in an ‘impl’ functions body?
- Can I destructure a tuple without binding the result to a new variable in a let/match/for statement?
- Possible to declare functions that will warn on unused results in Rust?
- How to reference types from function signatures in private modules?
- How to quiet a warning for a single statement in Rust?
- Whats the best way to write an iterator supporting multiple logic branches?
- How to prefix/suffix identifiers within a macro?
- How to define a function with a generic fixed-length array?
I don’t mean this to be a big complaint, just listed some of the issues I ran into learning Rust, since its only fair to back my comments up with examples.
One of the well known difficulties for new comers is the borrow checker. While it wasn’t surprising, I did find it hard to know how to approach errors with the borrow checker.
The documentation for simple errors with the borrow checker are fine, it’s difficult when you’ve writing more complex code and the simple examples don’t seem to apply.
- Is the error in my code something that can be made to work with careful re-arrangement?
- Is my design going to need to be restructured to avoid borrow checker errors?
- Should I use
- Is this an exceptional case where using
Since a lot is written about this in the Rust community, I rather not focus on it, just to note that it’s easier then I expected to run into corner-cases where the right answer wasn’t obvious, even after reading docs on the topic.
On the other hand, issues with the borrow checker may be over-emphasized since developers immediately run into them.
Overall I found the type system to be fairly opaque. It’s just hard to know what’s possible, even if you read Rust’s source code it often uses features which aren’t available in Rust-stable.
Having said that, so far I’m impressed by Rust’s type-system - especially traits, how it works with name-spaces to give you functionality typically found in OOP languages.
Some practical limitations I ran into where:
- Generics can’t generalize over size. (useful for graphics programming for 2D/3D functions & tree-structures)
- Generics can’t generalize over mutability (copy-pasting code simply because one function returns a mutable result doesn’t feel right).
Apparently generalizing over size may be supported eventually.
My interaction with the Rust community consisted of getting help in the IRC chat room
stackoverflow.com and a little on Reddit’s
First of all, the Rust community were incredibly kind and helpful, to the point I even felt a little guilty about taking up their time.
Where possible I tried to formulate my questions on stack-overflow, so in future an online search would show the answer and not further burden people in the forums or IRC channel.
While learning Rust I asked over 150 questions, starting as a total beginner.
Having used C/Python I’d gotten used to nearly any problem I’d run into being asked already. (often with multiple useful answers).
It was a bit of a surprise to find many fundamental questions just didn’t exist on stack-overflow.
Asking beginner questions is tricky since the answer may exist online, but not show-up in searches without the correct terminology/vocabulary.
Over time I expect this to become less of a problem.
- Don’t write unsafe code!
Certain practices are frowned upon, (writing unsafe code, using raw pointers for e.g.)
There were times however where I set myself the limit of not using any more memory than the C equivalent, since this can impact the complexity of data you can edit at once (pixels, polygons, spacial tree structures etc...).
Of course I could just write the code in C, but writing wrappers is fairly inconvenient, and this is something Rust supports for a reason.
- Use crates.io!
While crates.io is great, I wasn’t overly keen to pull in dependencies, especially when the functionality I wanted could be written as a fairly short function.
Part of this was also my interest to learn Rust, and not pull in code which I barely understood, to avoid writing some ~20 line function.
Of course for large libraries it typically makes sense to use dependencies instead of writing your own. I’m interested to see how this works out and not entirely against using crates.io dependencies more often.
Just beware if you don’t like pulling in dependencies at the drop-of-a-hat, you may be going against the Rust communities conventional wisdom.
Setting up a good development environment took some time but that didn’t come as a surprise since the Rust community are fairly open about this being a known pain-point.
Even so, there doesn’t seem to be a defacto-standard to try as a starting point. Having used Vim for Python scripting, I was comfortable to use this for editing but missed typical IDE features, mainly jump-to-definition & auto-complete.
Fast forward some months and I’m happily using Emacs/evil-mode with Racer for IDE features, this is in fact a very nice development environment, even compared with C/C++ IDE’s. I could have used Vim’s Rust plugins too, but found Emacs a bit more extensible.
While I was happy with the result, configuring Emacs/Vim does take time and they aren’t for everyone, so its good to know Racer integration is available for editors with a lower learning curve.
- Rust Simple Examples
Early on I started a repository to solve some fairly simple problems, mostly writing basic algorithms I was already familiar with.
Drawing a polygon, a kd-tree using both stack and recursion for searches. Stand alone PNG image writer, and polygon triangulation (not all that simple, but a good test).
Doing these projects really helped me learn Rust and I ran into many problems, so keeping each task small and isolated made it easier to resolve them.
This uses a left-leaning binary-tree to implement a 1D range tree.
This is a port of a C library written for Blender.
The tree structure uses raw pointers which may be frowned upon.
My aim was to make a small utility which had some real-world use as well as being an exercise.
While I was fairly happy with the results, it turns out one of the most difficult problems with tracing an image is to extract a shape from the bitmap which has well placed points for the curve-fitting to operate on.
To be clear, my code for this worked but not quite as well as Potrace. Unlike Potrace it supports center-line tracing, so perhaps its worth putting this on crates.io for others to use.
I didn’t want to spend all the time making a tracing utility so I moved onto my next project.
A port of Blender’s mesh editing system, this turned into a small application, using SDL for windowing.
While it was interesting to work on, I ended up practically writing C-in-Rust, using a lot of pointers since vertices edges and faces store connectivity data, and I didn’t want to use more memory than the C library.
It may be a valid criticism that I should have written more idiomatic Rust however for this project I was aiming to get something working, accepting
unsafecode as needed, as long as it wasn’t causing regular problems.
This project isn’t finished so I don’t think I can draw too many conclusions from it. I’d like to spend more time on it, and hope I can take advantages of Rust’s improvements as they become available.
- Lots of Questions!
- Here is a weekly log of what I worked on, including questions asked 2016, 2017.
- In general my experience was positive and I’m sure Rust has a bright future.
- None of the limitations I ran into were show-stoppers.
- Macros, traits, name-spaces, module system and cargo are awesome!
I have the impression Rust is aimed for developing high performance web-services, as well as Servo though this seems to be one-of-a-kind.
That’s fine but it means if you want to use Rust for other tasks you might not be as well supported by the ecosystem.
Compile times can be slow!
Rust developers are aware of this, and its an area being worked on. I ended up with ~25k LOC, which took ~7 seconds to rebuild after a single file changed, not too bad but not great for running tests.
While the language is stable, libraries for what you might consider core functionality may not be.
Often attempting something new in Rust turned into full-day projects, where it wasn’t obvious which solutions were best.
In closing, I enjoyed learning Rust, while I’ve accepted some C/Python project work for the Blender Foundation. Rust seems very promising and I’m interested to use it as a replacement for C, if the opportunity arises.
Campbell Barton, 1st of March, 2017.