Five years of Rust - a full-stack programming language for the next decade(s)
To celebrate the five years of the Rust programming language, this blog post is the second of a series where I explain why I think Rust will be the programming language for the next decade(s), and why you should learn and use it too! In the first blog post, we have seen why Rust is truly cross-platform. In this post, we’ll explore the range of application domains where Rust can be used to build software. Or rather, we’ll only scratch the tip of the iceberg, given that Rust is growing in so many domains!
Whether Rust is a full-stack programming language is still a hot debate, my opinion is that it is, and I hope that this blog post will convince you as well!
After that, I’ll conclude this series of posts with some perspectives on the Rust ecosystem, and Rust’s upcoming role in the job market.
A wide range of applications: some examples
Now that we’ve seen that Rust is truly cross-platform, you may wonder what is the ideal application to write in Rust. From my previous post, you may think that Rust mostly targets performance-critical applications. But even for applications where performance matters less, Rust’s expressiveness and reliability make it a good choice as well. With the nice side-effect that you won’t regret this investment if performance becomes relevant in the future, due to scaling your application, or because Moore’s law is coming to an end.
Still, in which domains is Rust mature enough to use?
A mandatory example comes from Rust’s history. The first sponsor of Rust was Mozilla Research, in particular for the Servo parallel browser engine project (source code), part of which have been integrated in Firefox. Although there is no fully-fledged web browser written 100% in Rust today, a browser is in itself quite an ambitious application which encompasses many elements through the software stack.
In this section, I want to review some application domains where Rust already – or will soon – shines. Just looking at the list of categories on lib.rs, you can see that there are Rust software in many domains, well beyond the four target domains identified in the 2018 roadmap. So this section is by no means an exhaustive list, but it will hopefully provide you with relevant pointers for your own programs!
Web: servers and clients
A major selling point of Rust is that it offers strong memory-safety and thread-safety guarantees, while being fast and memory efficient. One domain where all of this naturally matters is servers. In order to handle as many requests as possible with as few hardware resources as possible, efficient usage of CPU and memory resources is essential, while multiple threads help balancing the load. And due to the exposure to the Internet, memory and thread safety are also a pre-requisite from a security point of view.
Therefore, web servers are a natural application domain of Rust. And indeed, a whole ecosystem has already formed around that.
- HTTP servers. There are multiple frameworks in this category, some of which score quite well according to the Web Framework Benchmarks by TechEmpower. I haven’t tried them much, but you’ll find actix-web, rocket and warp (itself based on hyper) in this category.
- Template engines. If you want to serve dynamic content, you’ll likely use one of these under the hood. There are plenty in this category, re-implementing common template languages or proposing new ones, such as handlebars, tera, askama and liquid.
On the client side as well, Rust is used to implement many HTTP client libraries.
- We can cite hyper, a “fast and correct” HTTP implementation, which is the foundation for reqwest, itself providing a more friendly API for clients.
- On the client-side as well, crates like serde_json make it very easy to parse a JSON file into a Rust structure, by adding only a couple lines of code! This is made possible by Rust’s excellent macro system, in particular the ability to “derive” all sorts of methods for types. But JSON is just an example among the many formats that Rust can parse and serialize – parsing is also a natural application for a memory safe and efficient language like Rust.
An important development in the past year has been the stabilization of the async
/await
syntax, which allows to seamlessly write code that depends on asynchronous operations (e.g. network requests), while writing a relatively straightforward control-flow, and without wasting resources.
This is a very useful support from the language for any application related to networking.
There’s a lot to be said on this topic beyond the scope of this blog post, but we can mention support for common asynchronous operations by many libraries, such as tokio, futures and async-std.
Besides web servers, Rust has also found use in implementing many networking protocols.
In short, we can say that Rust is suitable for all components of the web ecosystem: servers, front-end, browsers. Not forgetting the networking protocols that glue them together, such as DNS.
Scripts
This may seem controversial for a compiled language, but I think that Rust is a good scripting language as well.
Traditionally, the main advantage of scripting languages such as Bash or Python, as opposed to compiled languages, has been that the source code is immediately ready to be interpreted and run, without having to wait for a slow compiler to produce an executable – as well as plenty of intermediary build artifacts. However, by nature, this means that there are no compile-time checks that your code is sane enough, which can lead to plenty of unexpected runtime errors.
Also, compilers are not as slow and resource-hungry as they used to be.
- CPU performance and memory size have increased faster than the complexity of programs we write. A simple 100-line script had roughly the same complexity 20 years ago as today, but it will compile much faster today than in 2000, and similarly the on-disk footprint of build artifacts are much less of a concern today. Disks are also faster thanks to SSD, and we can cache more data in larger RAM and CPU caches.
- Incremental compilation allows to cache many build artifacts, such as dependencies, to vastly speed-up the re-compilation times after small source code changes. It’s been an essential part of Rust for multiple years.
- Tools like Cargo make it very smooth to compile and run Rust programs on the fly.
Essentially, running
cargo run -- <args>
in your program’s source repository will compile it (incrementally) and run it with the provided arguments. It’s not much different than a Bash or Python script starting with a#!/bin/bash
or#!/usr/bin/env python
shebang. - Bash and Python still have the advantage that their interpreters are installed by default (on Unix systems). But we could well imagine packaging Rust as a compile-and-run seamless “interpreter” shipped on such systems as well.
Conciseness is also an important aspect for scripts, and indeed a Rust “Hello World” fits in 3 lines of code.
So, all of this means that Rust can reasonably be used as a replacement for Bash or Python scripts.
And indeed, Rust is already used for build scripts of many crates, in the form of a build.rs
file.
On the source code side of things, Rust has great support for invoking other programs, for example with std::process::Command
.
This isn’t much different than Python’s subprocess
.
And contrary to Bash, I find it quite reassuring to pass arguments as an array type-checked by the Rust compiler, rather than a space-delimited string which may be split improperly if you forgot the expansion rules of Bash, or the semantic difference between single or double quotes.
Command-line interfaces
Beyond simple scripts, Rust is also great for more complex CLI applications. For these, the clap crate allows to easily define command line arguments for a program. Then, clap will automatically parse the arguments, and in case of error it will point the user to the auto-generated help, and even recommend how to fix their typos. Therefore, clap is much more integrated than something like getopt in C, and allows you to focus on the core business logic of your application.
If you want to go beyond that and build interactive applications in the terminal, or Terminal User Interfaces (a.k.a. TUI), you can use the tui crate.
With this ecosystem, many command line utilities have been built in Rust.
For example, ripgrep is a faster alternative to other grep
-like programs (GNU grep, git grep, etc.).
Graphical User Interfaces
When starting my research for GUI applications written in Rust, I didn’t have high expectations, because building a GUI framework is a difficult task. However, I must say that I was pleasantly surprised by what’s already available, and there’s no doubt this area will continue to improve!
First of all, what frameworks are available for GUI in Rust?
- Bindings to existing frameworks. As mentioned in my previous post, Rust has good bindings to other programming languages. This allows to reuse existing frameworks by implementing a thin Rust wrapper. Therefore, there are bindings available for SDL2, XCB, GTK+. Some crates, like Wayland even implement both bindings to an existing C library, and a pure-Rust version of a GUI protocol.
- Low-level graphics. Multiple crates provide bindings to low-level graphic APIs, such as OpenGL or Vulkan. The gfx-rs crate aims at providing a seamless integration with any graphics back-end: OpenGL, Direct3D, Metal or Vulkan. There is also a pure-Rust implementation of TrueType fonts.
- Rust GUI frameworks. On top of that, some GUI frameworks written in Rust and providing all sorts of windows and widgets have started getting traction in the Rust ecosystem. Some examples: winit, conrod, piston-widow (a simpler, sort of predecessor to conrod) and druid.
On top of that, some GUI applications have been successfully developed in Rust. A couple of examples are: the xi-editor text editor (demo), the alacritty terminal emulator (cross-platform and GPU-accelerated!). Game engines as well as games have also been written in Rust.
Besides pure graphics, there is a whole Rust ecosystem for multimedia.
Science
When I started university, it was common to use proprietary software for scientific computing, such as MATLAB, Maple or Mathematica. These software implement all kinds of mathematical algorithms and often each come with their own custom programming language.
At the same time, Python grew in popularity as a language for open-source frameworks targeting a wide range of scientific applications. A few examples of libraries for data science are NumPy, Matplotlib or pandas. Some frameworks are more specialized in machine learning, such as TensorFlow or PyTorch. In the domain of algebra, there is also SageMath, which we used a lot in cryptography classes.
To complete the landscape, the R language is specially aimed at statistical computing.
Now, what role can Rust play in scientific computing? One common thing that all these frameworks have in common is that they have to be very fast, in order to process large amounts of data effectively. This means that the user interface in Python is only a convenient wrapper – the actual mathematical algorithms are implemented in a low-level language like C++.
So the first role Rust could play would be to replace C++ as a safer language for implementing these algorithms, without sacrificing performance. One interesting library for that is PyO3, which provides Rust bindings for Python. Another example is the Rust in R project, demonstrating R packages that bind over Rust code.
In terms of mathematical algorithms, the peroxide library looks promising in implementing many algorithms from linear algebra to numerical analysis and statistics. There are also multiple crates for linear algebra, such as ndarray, nalgebra or cgmath. Although many algorithms are still missing, one thing that I think will be relevant is the stabilization of const generics in Rust - and the good news is that there has been a lot of progress on const generics in the last year.
A second role for Rust is to bind itself as a wrapper around frameworks still implemented in C++. There are many such bindings in the machine learning space for example:
Last, Rust could entirely replace the computing stack, replacing the Python front-end by Rust code. For that, the plotters crate – a Rust equivalent to Matplotlib – looks promising, allowing to draw all kinds of charts. And because Rust is cross-platform, plotters allows to draw these plots in native applications or directly on web pages (via WebAssembly bindings). Plotters also supports integration in Jupyter notebooks (see the tutorial).
Beyond that, science is not limited to algebra, statistics and machine learning, and neither is Rust. There are plenty of Rust libraries for bioinformatics, geography or physics.
Cryptography
As a programming language focused on security and performance, with characteristics like strong typing, Rust is well suited to implement cryptography.
This space has traditionally been occupied by C, for the performance and cross-platform reasons.
Although, cross-platform in C means a lot of #ifdef
s to support various compilers, and a lot of tricks to use assembly or non-standard intrinsics, or to try to prevent the compiler(s) from optimizing constant-time code.
And C lacks memory safety, which doesn’t play well with a security-sensitive application like cryptography – ultimately leading to bugs like Heartbleed.
On a personal note, I have explored the domain of cryptography notably during my studies and master’s thesis, for research and implementation alike, and more recently at work. And even though re-implementing algorithms from C to another language is an ambitious task, due to all the pitfalls that come with writing secure cryptography code, I see a lot of future for Rust and cryptography.
The following features of Rust make it well suited to write cryptographic code.
- Memory safety. This is particularly relevant to cryptography, to avoid the next Heartbleed vulnerability and more generally leakage of secret keys by memory vulnerabilities like buffer overflows.
- Abstractions via a strong type system.
It’s notoriously hard to write constant-time code or to properly zeroize memory after use, in particular in C when many different compilers have to be supported.
There are entire research papers surveying existing techniques that (attempt to) zeroize memory, and how they can fail.
The same can be said about constant-time code, as I discussed in a previous blog post.
Yet, these can be important safeguards around cryptographic code, to make sure that secret keys are not leaked by side channels or by staying in memory after use.
Rust doesn’t make these problems inherently easier to solve, but first of all having a single de facto reference compiler makes it easier for libraries to support. And most importantly, Rust offers a strong type system, which allows to abstract away these problems and solve them once in libraries like zeroize – to clear memory after use – and subtle – to implement constant-time comparisons. This is contrary to traditional C/C++ cryptographic libraries, that each re-implement the wheel.
With Rust, all the implementation and auditing efforts can focus on a single library behind the abstraction. - Unit tests.
Rust has a testing framework directly baked into the language: all you have to do is annotate a function with
#[test]
and it becomes a unit test. It’s also very easy to create utility functions for testing, with a guarantee that they are not compiled in the production library or binary. - Const generics. I’ve mentioned const generics in multiple blog posts already. Their stabilization can help for all sorts of mathematical problems in general and for cryptography in particular, by providing abstractions over things like mathematical constants, without incurring a performance cost after compilation.
With all these features, Rust is already used in the following applications of cryptography, among others.
- Cryptocurrencies, and more generally zero-knowledge proofs.
For example, the
bulletproofs
crate advertises to be the fastest implementation of Bulletproofs. - A full TLS stack, such as rustls. Interestingly, the code just been audited, with very positive reviews and only low severity findings, which is very encouraging!
- The RustCrypto group aims at implementing common cryptographic algorithms in pure Rust, and is seeing active development at the moment.
- Security keys (U2F, FIDO2), such as the OpenSK proof-of-concept (disclaimer: I worked on it in my day job).
Concluding remarks about the Rust ecosystem
In these two blog posts, I’ve focused on two aspects of Rust: a multi-platform and full-stack language.
But there is another component to Rust’s ecosystem: a great tooling.
If you’ve started using Rust, you’re probably already familiar with its dependency manager & build system cargo
, the rustfmt
code formatter or the linter clippy
.
But there are many more community-contributed plugins.
In particular, I find quite relevant to see tools for code auditing, such as cargo-audit (powered by the RustSec Advisory Database), cargo-geiger to detect uses of unsafe
code, and the cargo-crev crowd-sourced code review system.
To sum up, my take is that the Rust ecosystem is already quite mature, and that the few remaining gaps in the language are actively being worked on, so you can expect them to land in the next few years. Given that, I’d be quite surprised if Rust isn’t successful in 10 years.
According to the StackOverflow surveys (2019, 2020), we’ve seen that Rust now represents 5% of developers, with a growth of 60% per year.
What does it mean for the job market?
- As a developer, starting to learn Rust now is a good investment as there will be more and more job opportunities, regardless of the application domain.
- As a company, starting to invest in Rust for some (or all) of your projects now might seem a bold move due to the 5% developer share, but will also be a good investment regardless of where you apply it in the software stack, which will bring you higher reliability, fewer bugs, fewer performance issues and – according to the StackOverflow surveys – excellent developer satisfaction.
In both cases, my take is that if you need say a couple of years to adopt Rust, the language and more generally the ecosystem will have improved by that time, and the market share will have increased as well. So my advice is: don’t wait and join the Rust community!
Comments
To react to this blog post please check the Reddit thread and the Twitter thread.
You may also like