Rust 2021: GUI
This is a response to the Rust call for blogs 2021 and also a followup to last year’s entry. It will be entirely focused on GUI.
There is considerable interest in GUI toolkits for Rust. As one data point, it was the 6th highest rated challenge for adoption in the 2019 Rust survey, just behind async I/O. There is also a fair amount of activity towards this goal, though as a community it still feels a bit unfocused. A characteristic sign is that a new GUI toolkit seems to pop up every couple of months or so.
I believe there is great potential for a high-quality GUI toolkit in Rust. At the same time, it’s an incredibly ambitious task. Subtasks within it, for example accelerated GPU drawing and text layout, are in and of themselves incredibly ambitious tasks. I wouldn’t consider a toolkit “ready” for production use until it supported accessibility, and as far as I know there is nothing in the Rust space even starting to work on this.
Yet, perhaps against my better judgment, I find myself devoting most of my time and energy towards building GUI in Rust. In this post I will set out my hopes but also frankly discuss the challenges.
Why GUI in Rust?
Simply put, I believe that the strengths of Rust translate well to writing GUI applications, and that the missing piece is the existence of a good toolkit. One strength is Rust’s wide “dynamic range” – the ability to describe application logic in high level terms while still being attentive to low level details. Another is strong cross-platform compatibility. The increasingly rich crate ecosystem is compelling in many domains. And don’t lose sight of the importance of safety. In traditional object-oriented GUI in C++ especially, object lifetimes can be complicated, and it’s not hard to cause crashes, especially on takeoffs and landings.
I do pay attention to the competitive space, and one thing I see is Electron being used more and more, because it solves real problems. But I also believe that the success of Electron creates a real opportunity for a higher performance, lighter weight alternative. And in general for the projects I see in other languages, I find myself wanting to compete against them.
About Druid
The Druid toolkit has made impressive progress in the last year, but is still nowhere near stable or complete. If you are looking for a GUI toolkit to develop your application today, Druid is not it.
We are developing the font editor Runebender as the primary motivating application, but, while a lot of pieces are in place, it is sadly not yet usable for day to day font creation work. One of my goals for the rest of the year is to start creating a font in it.
That said, I am very proud of the work that’s been done in the last year. To hit on some of the highlights, we’re just landing basic but capable rich text layout. The keyboard event is close to browser quality (based on keyboard-types). There is incremental painting based on damage regions. Multi-window support is solid, with support for controlling window placement and dynamic hi-dpi. There is tab-focusing between text boxes. All of these are hard problems. Even more so, I am pleased that a lot of the work came from people in the community.
Converging a vision
Imagine a thought experiment for a bit. Obviously Rust is promising for implementing async, but there isn’t a consensus on the best way to do it. Some people feel it should be done with callbacks, and invest considerable effort into overcoming the serious problems with that approach. Others feel it should be done with a polling future trait, but there are multiple versions of that trait: some get the context from thread local storage, others pass it into the poll method. And of course some people feel the syntax should be future.await
while others insist on await!(future)
. Every couple of months somebody pops up on /r/rust with a new crate that promises to solve “the async problem,” complete with a nice-looking echo server demo.
That’s about where we are today with GUI toolkits. In many ways, I think converging on a single vision in GUI is a harder problem than for async. For one, people have different things they want to do. I’m personally most interested in things that resemble document editors. Others want 3D or video content. In the future, there might be commercial interest in enterprise line-of-business apps or interfaces for medical devices. These all have quite different requirements in the best ways to express UI logic, and how to build them. Not to mention the endless opportunities to bikeshed.
I am not (yet) proposing Druid as the singular vision that the Rust community should converge on. I’m enjoying reading codebases and learning from other Rust GUI projects. In particular, I’m finding lots to like about Iced: it has good solutions to async, 3D graphics (through wgpu), being able to function in a guest window (important for VST plug-ins), among other problems. And I’m getting the sense that it’s easier for developers. The Elm-like reactive architecture maps nicely to Rust, and depending on exactly what you’re trying to do, it’s not hard to figure out how to express your app-specific logic. By contrast, Druid’s reactive model, while efficient and powerful in many ways, has complex concepts such as Haskell-like lenses, and places a burden on the developer to carefully design the “app data” to fit the Druid model. The Crochet research prototype is an active exploration into making that simpler. I am thankful to Iced (and other toolkits) for being a model to study.
The work to build consensus is complex and multifaceted, and it cannot be rushed. From my side, I hope to improve the designs and implementations to the point where they are compelling. I also hope to listen to criticisms, many of which are valid. I also think there is more work the community can be doing here. I’d love to see more active effort in trying to learn from the ongoing work and try to synthesize it. The GUI-related threads on /r/rust are sadly not a place where that happens; they most often consist of a statement of requirements (usually presented very informally), followed by a bit of bikeshedding. I don’t have a good answer for how to improve this situation, but put it out there as a problem I’m feeling.
While I think a converged vision is an admirable and ambitious goal, it may not be necessary for a successful GUI ecosystem in Rust. It’s possible that different types of GUI programs will simply require different infrastructure, so even in the long term it makes sense to have ecosystem diversity. Certainly that’s the case in the short and medium term as well, just to explore the space. And even without a grand unifying vision, there is lots of scope to work on infrastructural crates for important pieces of the GUI story, including text layout and related problems.
Learning and community
Many Rust projects these days come with what’s basically a marketing pitch: “adopt this codebase, it’s awesome.” I am starting to see the Druid project in a somewhat different light. I consider its primary mission to be teaching and learning the knowledge required to build GUI. To that extent, the community we’re building, hosted on our Zulip instance, is just as important as the code.
The knowledge needed to build GUI has many aspects, and is at all levels. Some of it is at a high level, like the best way to express reactive UI. Some of it is at a low level, like the keyboard event processing. A common thread is that a lot of it is very arcane, not really written down properly anywhere. Fortunately, a lot of it is accessible through reading the code of other open source projects, whether in Rust or in other languages (I’ve found both Chromium and Gecko to be especially useful).
So I consider this a goal, a success criterion, of the Druid project. If somebody wants to know how to solve a problem in GUI, the Druid codebase should be one of the best places to look for answers.
I love research more than most anything, and a lot of my own work has a strong research flavor. That has caused some confusion; some of the things I’m exploring are very speculative and will likely take years to come to fruition. Certainly my research into compute-centric GPU rendering is of that nature; I’m excited about the fact that it promises dramatic performance improvements over the current state of the art, but it’s nowhere near ready to put into production yet. I’m striving for clearer communication so people can have a better idea what is speculative, based on grand futuristic visions, and what is on track to being usable reasonably soon. But both are important aspects of what I consider to be the main mission: fostering learning about how to build GUI.
Baby steps
While I am driven by a long-term, ambitious vision, the goal of Druid in 2021 is not to deliver a general UI toolkit. Rather, we are deliberately continuing to follow a narrow scope. The primary goal remains the font editor project, and we plan to re-focus attention on that. I do think this is an attainable goal. I also think that what we learn from trying to build a real application with users will be extremely valuable to the more ambitious task.
One project management technique that is proving effective is “cycles.” Instead of trying to solve the most ambitious version of a problem, we choose up front what to push to a future cycle, reducing the scope for the current implementation cycle. An example is the choice to defer BiDi from our recent text work. This is obviously an essential feature for a real GUI toolkit, but we also know it could take weeks or months to get it right. To have any chance of shipping, we have to carefully budget our time and energy on subprojects that easily could expand to absorb our full attention.
A common development pattern for a fledgling GUI toolkit is to have a “hero app” that drives development. It really helps clarify requirements, and also makes it easier to change things without having to worry as much about churn in the ecosystem. For Druid, that is the Runebender font editor. We want to increase our focus on that in coming months, and that is also reflected in our approach to community: we will happily mentor Druid features that fit on the Runebender roadmap. Other features that don’t massively stretch the scope can happen, but will really need to be driven by the people in the community. And we will push back against things that are off roadmap and require a huge amount of work, simply because our bandwidth for mentoring and review is finite. Even so, I remain impressed by the scope and quality of contributions from the community.
A wishlist
Here are some things I’d especially like from the rest of the Rust community.
First, I’d love for wgpu to become more mature, as I think it is the most promising approach to GPU for the future. Our roadmap for 3D basically depends on it. Much love to that community for the progress they’ve made so far and hope for the future.
Second, while we generally find Windows platform bindings to be straightforward, thanks to the excellent winapi bindings (and hopefully a stable com-rs shortly), the situation on macOS is not as good. There are real problems interoperating with Objective-C code, including type-safety in wrappers that also allow subclassing, and continual friction around autorelease pools. There are also different, incompatible conventions around wrapping foreign types (foreign_object vs TCFType). None of this is blocking, but it makes developing macOS platform integration less fun. (See this HN thread for a bit more background)
Third, some perhaps surprisingly good news. I prefer Rust crates when possible rather than relying on C++, but for OpenType font shaping, we would have to call into HarfBuzz. Now not one but two Rust solutions are on the horizon: Allsorts from YesLogic, and rustybuzz from RazrFalcon. Both are young, but I can see adopting them (primarily for use on Linux, which is still work in progress; on Windows and macOS we use the platform text layout capabilities). I know of no other language besides C++ that has working OpenType font shaping. Almost every language community has its own language-native GUI toolkit project, and most fall short. I do believe that Rust will be different, and seeing projects like this is to me evidence why.
Fourth, if I could have one wishlist from the Rust language, it would be proper keyword arguments, as the builder pattern feels clunky, and the struct-with-default pattern is no better. But overall there’s a lot to be said for the language itself basically being stable, and the primary focus being on implementation, tools, and ecosystem.
To 2021
GUI is a hard problem, but the Rust community has a track record of solving hard problems, even if the journey is sometimes long. I find the progress so far impressive, both in Druid and other related projects.
It is my sincere hope that 2021 will be a year of recovery from trauma, re-building what has been lost, and building new and better things. I’m looking forward to the work on Druid and Runebender, and walking with the Rust community on the journey, hopefully ultimately towards my most ambitious vision of what a high-performance, richly expressive GUI toolkit could look like.
Work on Druid and Runebender is generously funded by Google Fonts.
Discuss on Hacker News and /r/rust.