Note: This post is not only about Rust, but rather my experiences, problems and solutions throughout 2017.

My problems

In February 2017 I registered my own company. It was a really weird step not to be told what to do anymore and having to survive through your own merits. I felt unsafe, inexperienced and not sure what to do. I had a business plan, yes, but I had never written programs full time before, only side projects in C++. So I just started doing what I could do best — writing C++ at full speed ahead. But the more I wrote, the more I understood that I did not know why I was doing certain things and just did them because I learned that if you see Error A, do thing B and hope that it’ll make the compiler complain less. I needed someone or something to guide me, to tell me why things are wrong — not just ad-hoc solutions.

Another problem I noted was that it seemed impossible to me to integrate any libraries from other people — a common solution are header-only libraries. Or you just downloaded a .dll file, hoped that it was the compiled for the exact same compiler you are using, hoped that it didn’t have any dependencies. Easy updating of libraries? Dependency management? Reproducible builds across machines? Forget it. Eventually, I moved to Linux, hoping that the dependency story would be better — it is, unless you need a library that doesn’t have a package, that’s when it becomes hell. Yes, I probably could have learned CMake and Make. But if a dependency doesn’t even build, that won’t help you much.

I was very concerned about performance, which is why I chose C++ in the first place. You see, at my old job, we had to use a cartography program to do semi-automatic label-placement. It was completely single-threaded and slow as hell, (not only due to very frequent disk access). The program was extremely slow. I should mention that the application was taking heavy advantage of Win32 functions (we had to run it in a VM). Ex. it used Windows’ PDF driver to export a PDF (which could take up to half an hour). I don’t know if it was due to Windows or the application programmers, but I attributed Windows’ built-in functions with being slow and having shitty PDF output, which is why I refused to use anything related to Win32 or .NET.

So in short, what were the main problems I had, which Rust excelled at?

I feared that garbage-collected languages were not fast enough (given the previous experience). I wanted bare-metal performance (like C or C++).

Dependency management in C++ was — and still is — hell. I am not alone with this opinion.

Error messages in C++ are cryptic. Yes, I know about C++20 concepts. Get back to me in 2025 when they are finally implemented.

There are other reasons, but I think you can see why I was frustrated with C++. So I quit the old company and C++ is awful, now what?

The good

I came across Rust by reading a random comment. In general, I have three steps towards evaluating any new technology / library / framework / language:

Do I need it? What does it do better than the current thing I’m using? Are there tradeoffs to be aware about?

Can I replicate the “Hello World” example? How hard is it to set up? Is the build easily reproducible? Does it integrate with what I already have or do I have to jump through hoops?

Can I “discover” the API? All parts of an API are important, but some are more relevant than others (to a beginner). Many libraries / technologies fail at that.

Rust did not fail any of these tests. But there are tradeoffs. I needed a PDF library in order to produce maps. Rust didn’t have one. I first tried integrating libharu and podofo but couldn’t generate the necessary bindings. On the other hand, I had time, lots of time and I wanted to make it perfect. There was no PDF library I knew of that supported PDF layers, which are incredibly important for maps.

Here we get to the first point why Rust has an awesome ecosystem: Building blocks. There was a library called lopdf which provides the basic blocks to serialize and deserialize PDF objects. That’s it, it doesn’t do anything else. And this is awseome, because I could build on top of that. I didn’t have to write my own serializer. Composition over reinventing the wheel, as I would say. Since you don’t have to do much work to track dependencies, it does make things very easy to compose and split libraries, enabling you to pick-and-choose what you need.

So two months later, I got done making my library called printpdf, which had the features I wanted. Now what? I still didn’t have any maps. Looking back, I then took the completely wrong approach toward software management - I tried to design everything upfront, with UML and cute diagrams. Sadly, I lost the diagram, but looking back I was extremely inexperienced. If you are working in a group, this might be something to show off to your manager, but in practice it simply doesn’t work out unless you already have extensive domain knowledge.

Use the technologies you already know when starting a (commercial) project.

This was a quote from a presentation at GDC 2017. If you want to finish a project, you need knowledge about the domain and tools you are working with. I needed an editor to edit my maps.

Now you might say, hold on, why don’t you use ArcGIS or QGIS or any of that? Why are you trying to reinvent the wheel? ArcGIS and QGIS (two popular GIS desktop applications) are full of features. But they are not well suited for cartography, which is a shame. QGIS PDF output is horrible to work with in Illustrator. Every time I tried importing the file, the line thickness would be different than what I expected. QGIS options for semi-automatic label placement are limited. Either you go full automatic, in which case you just use what the algorithm spits out. Or you need to place the labels manually. QGIS has no understanding of automating map styling (i.e. sharing settings across maps, map templates, automated map creation). I also needed version-control for maps. Maps need to be updated from time to time and you don’t want to redo the whole map. You need to view changes between maps, changes to the map layout. Plus I’d still have to write all the other tools for street indexes, heightmap rendering, labeling for contours, etc. It took QGIS 4 years to implement a simple feature where the line thickness gets scaled according to your zoom scale (like if you zoom in on a printed map). Which is, you know, kind of essential for correct label placement. In short, I wasn’t convinced that QGIS would get me to my goal. In the end, nobody cares how you make your map, only the end result counts.

So let’s get make an graphical editor in Rust then, shall we? Or, in other words, let’s get to

The bad

GUI libraries in Rust. Well, where do I start? There are none. Well, that’s not quite true:

limn is currently the most promising pure-Rust library I’ve seen. What it does is that it takes Firefox’s rendering engine webrender (only the rendering, no JS or layout) and uses an Android-like layout algorithm (cassowary) for layouting. It is however, very early in development. The layout still has bugs.

conrod is heavily focused on games. It uses a directed graph to determine if elements need to be re-styled and how they are positioned in relation to each other. I tried it and it was a pain to develop for. Every widget needs an ID to be represented in the graph. At the time I tried it (a year ago), doing this with dynamic insertion and removal (generating dynamic IDs at runtime) needed a heap of boilerplate code to get working. It was also under rapid development, so I wasn’t sure about the longevity of my UI.

GTK and QT bindings. The reason I did not choose these was rather simple: In order to draw a map efficiently I need OpenGL. Neither GTK nor QT (at least the Rust bindings) have good OpenGL integration.

Short, I needed OpenGL for efficient, fast drawing, there was no way around it. So let’s build a GUI framework from scratch then, shall we? Here’s your recipe:

1 OpenGL shader that can translate from pixels to screen-space coordinates

1 OpenGL shader that does the same, but for textures

2½ layout frameworks to determine the coordinates of your squares to render on the screen (more on that later)

500g AABB tests for hit testing + 1 windowing framework

5 teaspoons of function pointers and one mutable reference to the application state

A pinch of insanity

I had no experience in building GUI frameworks, so what do you do? Build a prototype to gain domain knowledge. I refactored and refactored my GUI again and again. I made a simple 2D game for the recent Ludum Dare to test my GUI concepts. What I found was an architecture that is neither retained nor immediate.

Both retained mode and immediate mode don’t work very well with Rust’s borrowing model, the reason being that UIs are in essence two-directional (output / input) while Rust’s borrowing model is optimal for one direction (either output or input). Rust requires you to do output (where you usually have an immutable reference to the GUI somehow) and input (where you need to have callbacks that can change your GUI and application state). So you can create callbacks that can change the whole application, but then you run into borrowing issues. You can solve them by spamming RefCell everywhere or you could try and adjust the model.

A lot of the problems came from the way I thought about GUIs. A button is an object, right? Wrong. Start to think of your GUI as a big, big function. Because that’s what it is, in the end, a rendering loop. Each button or widget or thing is simply a sub-function that describes how it is laid out. A UI is (in my model) an “immutable view into your applications state”. What I ended up with was something like this diagram: