Choosing a Rust web framework, 2020 edition
- 2501 words
- 13 min
This article is a spin-off from Zero To Production In Rust, a book on web development in Rust.
You can get a copy of the book on zero2prod.com.
As of July 2020, the main web frameworks in the Rust ecosystem are:
Which one should you pick if you are about to start building a new production-ready API in Rust?
I will break down where each of those web frameworks stands when it comes to:
- Comprehensiveness;
- Community and adoption;
- Sync vs Async, as well as their choice of futures runtime;
- Documentation, tutorials and examples;
- API and ergonomics.
I will in the end make my recommendation. Worth remarking that there are no absolutes: different circumstances (and taste) might lead you to a different pick.
1. Comprehensiveness
actix-web
, tide
and warp
are slim web frameworks: they offer you an HTTP web server, routing logic, middleware infrastructure and basic building blocks and abstractions to parse, manipulate and respond to HTTP requests.
rocket
takes a different approach - it aims to be batteries-included: the most common needs should be covered by functionality provided out-of-the-box by rocket
itself, with hooks for you to extend rocket
if your usecase needs it.
It should not come as a surprise then that rocket
ships an easy-to-use integration to manage connection pools for several popular database (e.g. Postgres, Redis, Memcache, etc.) as well as its own configuration system in rocket-contrib
, an ancillary crate hosted in rocket
's own repository.
We can compare them to frameworks available in other ecosystems:
actix-web
,tide
andwarp
are closer in spirit toFlask
from Python orExpress
from Javascript - they might be opinionated, but they do not ship a configuration management system or an ORM integration out of the box. You are in charge of structuring your API as you deem appropriate, bringing all the necessary crates and patterns into the picture;rocket
is closer toDjango
from Python orSymphony
from PHP: a stable and solid core with a set of high-quality in-tree components to fulfill your every day needs when building a solid web application.rocket
has still a long way to go to match its peers in breadth and scope, but it is definitely off to a good start.
Of course this is a snapshot of the landscape as of today, but the situation is continuously shifting according to the maintainers' intentions - e.g. actix-web
has slowly been accumulating more and more supporting functionality (from security to session management) in actix-extras
, under the umbrella of the actix
GitHub organization.
Furthermore, using a slim web framework does not force you to write everything from scratch as soon as the framework is falling short of your needs: you can leverage the ecosystem built by the community around it to avoid re-inventing the wheel on every single project.
2. Community and adoption
Numbers can be misleading, but they are a good conversation starting point. Looking at crates.io, we have:
Framework | Total Downloads | Daily Downloads |
---|---|---|
actix-web | ~1250k | ~3000 |
rocket | ~525k | ~1000 |
warp | ~435k | ~3000 |
tide | ~47k | ~300 |
The number of total downloads is obviously influenced by how long a framework has been around (e.g. actix-web:0.1.0
came out at the end of 2017!) while daily downloads are a good gauge for the current level of interest around it.
You should care about adoption and community size for a couple of reasons:
- consistent production usage over years makes it way less likely that you are going to be the first one to spot a major defect. Others cried so that you could smile (most of the time);
- it correlates with the number of supporting crates for that framework;
- it correlates with the amount of tutorials, articles and helping hands you are likely to find if you are struggling.
The second point is particularly important for slim frameworks.
You can get a feel of the impact of community size, once again, by looking at the number of results popping up on crates.io when searching a framework name:
Framework | # results |
---|---|
rocket | 178 |
actix-web | 113 |
warp | 57 |
tide | 20 |
Will all those crates be relevant? Unlikely.
Will a fair share of them be outdated or unproven? Definitely.
Nonetheless it is a good idea, before starting a project, to have a quick look for functionality you know for a fact you will need. Let's make a couple of quick examples with features we will be relying on in the email newsletter implementation we are building in Zero To Production:
- if you need to add Prometheus' metrics to your API you can get off the ground in a couple of minutes with
actix-web-prom
orrocket-prometheus
, both with thousands of downloads. If you are usingwarp
ortide
you will have to write the integration from scratch; - if you want to add distributed tracing,
actix-web-opentelemetry
has your back. You will have to re-implement it if you choose any other framework.
Most of these features are not too much work to implement, but the effort (especially maintenance) compounds over time. You need to choose your framework with your eyes wide open on the level of commitment it is going to require.
3. Sync vs Async
Rust landed its async
/await
syntax in version 1.39
- a game changer in terms of ergonomics for asynchronous programming.
It took some time for the whole Rust ecosystem to catch up and adopt it, but it's fair to say that crates dealing with IO-bound workloads are now generally expected to be async-first (e.g. reqwest
).
What about web frameworks?
actix-web
adopted async
/await
with its 0.2.x
release, same as warp
, while tide
was using async
/await
before its stabilisation relying on the nightly
Rust compiler.
rocket
, instead, still exposes a synchronous interface. async
/await
support is expected as part of its next 0.5
release, in the making since last summer.
Should you rule out rocket
as a viable option because it does not yet support asynchronous programming?
It depends.
If you are implementing an application to handle high volumes of traffic with strict performance requirements it might be better to opt for an async web framework.
If that is not the case, the lack of async support in rocket
should not be one of your primary concerns.
3.1. Futures runtime
async
/await
is not all sunshine and roses.
Asynchronous programming in Rust is built on top of the Future
trait: a future exposes a poll
method which has to be called to allow the future to make progress. You can think of Rust's futures as lazy: unless polled, there is no guarantee that they will execute to completion.
This is often been described as a pull model compared to the push model adopted by other languages1, which has some interesting implications when it comes to performance and task cancellation.
Wait a moment though - if futures are lazy and Rust does not ship a runtime in its standard library, who is in charge to call the poll
method?
BYOR - Bring Your Own Runtime!
The async runtime is literally a dependency of your project, brought in as a crate.
This provides you with a great deal of flexibility: you could indeed implement your own runtime optimised to cater for the specific requirements of your usecase (see the Fuchsia project or bastion
's actor framework) or simply choose the most suitable on a case-by-case basis according to the needs of your application.
That sounds amazing on paper, but reality is a bit less glamorous: interoperability between runtimes is quite poor at the moment; mixing runtimes can be painful, often causing issues that are not straight-forward either to triage, detect or solve.
While most libraries should not depend on runtimes directly, relying instead on the interfaces exposed by the futures
crate, this is often not the case due to historical baggage (e.g. tokio
was for a long time the only available runtime in the ecosystem), practical needs (e.g. a framework has to be able to spawn tasks) or lack of standardisation (e.g. the ongoing discussion on the AsyncRead
/AsyncWrite
traits - see here and here).
Therefore picking an async web framework goes beyond the framework itself: you are choosing an ecosystem of crates, suddenly making it much more cumbersome to consume libraries relying on a different async runtime.
The current state of affairs is far from ideal, but if you are writing async Rust today I'd recommend you to make a deliberate choice when it comes to your async runtime.
The two main general-purpose async runtimes currently available in Rust are tokio
and async-std
.
tokio
has been around for quite some time and it has seen extensive production usage. It is fairly tunable, although this results in a larger and more complex API surface.
async-std
was released almost a year ago, around the time of async
/await
stabilization. It provides great ergonomics, while leaving less room for configuration knobs.
crates.io can once again be used as a gauge for adoption and readiness:
Runtime | Total Downloads | Daily Downloads |
---|---|---|
tokio | ~9600k | ~30k |
async-std | ~600k | ~4k |
How do frameworks map to runtimes?
Framework | Runtime |
---|---|
actix-web | tokio |
rocket (0.5.x ) | tokio |
tide | async-std |
warp | tokio |
4. Documentation, tutorials and examples
Having to dive into the source code to understand how something works can be fun (and educational!), but it should be a choice, not a necessity.
In most situations I'd rather rely on the framework being well-documented, including non-trivial examples of relevant usage patterns.
Good documentation, tutorials and fully-featured examples are mission-critical if you are working as part of a team, especially if one or more teammates are not experienced Rust developers.
Rust's tooling treats documentation as a first class concept (just run cargo doc --open
to get auto-generated docs for your project!) and it grew to be part of the culture of the Rust community itself. Library authors generally take it seriously and web frameworks are no exception to the general tendency: what you can find on docs.rs is quite thorough, with contextual examples where needed.
rocket
and actix-web
provide high-level guides on the respective websites and all frameworks maintain a rich collection of examples as part of their codebases2.
Tutorials outside of the project documentation are mostly a function of age: it's very easy to find material (articles, talks, workshops) on actix-web
and rocket
while the offering is somewhat more limited for warp
and tide
. On the flip side, some of what is out there for actix-web
and rocket
might target older versions, leaving room for confusion.
5. API and ergonomics
Well, difficult to give an opinion on API design that sounds legitimately objective.
We all have wildly different tastes when it comes to what we consider a pleasant API and there is no substitute for a quick hack-and-go to really get a feel for what it is like to use a certain web framework.
If you are short on time, you can have a look at worked out examples: actix-web's examples, warp's examples, tide's examples and rocket's examples.
If you are curious about warp
and tide
, Image decay as a service provides an in-depth analysis of their APIs.
6. Our choice
As of July 2020, I'd suggest picking actix-web
if you are writing a production API in Rust.
To recap what we covered, actix-web
:
- has seen extensive production usage;
- relies on
tokio
as its async runtime, thus minimising the likelihood of compatibility issues with the most popular crates in the async ecosystem; - boasts a significant collection of mature plugins as well as the largest community.
While some of its APIs are definitely not the most ergonomic (I am looking at you, Transform
trait), the inconvenience is definitely minor all things considered.
On the flip side, Rust itself would not be where it is today if nobody had been willing to take a bet on a promising but less proven technology:
tide
andwarp
are pushing the boundary of what is possible in terms of ergonomics using async Rust;- the upcoming
rocket
release is going to be massive, both for its adoption ofasync
/await
as well as for the migration fromnightly
to the stable Rust compiler.
A rising tide lifts all boats.
The way of saying from which tide
takes its name, the way forward for the whole Rust async ecosystem.
See you again in a year for another overview!
Thanks to o0Ignition0o and vertexclique for taking the time to review the draft of this article.
This article is a spin-off from Zero To Production In Rust, a book on web development in Rust.
You can get a copy of the book on zero2prod.com.Discuss the article on HackerNews or r/rust.
If you want to be notified when new articles are released on this blog, subscribe to the email newsletter.
Check out the release notes of async
/await
for more details. The talk by withoutboats at Rust LATAM 2019 is another excellent reference on the topic. If you prefer books to talks, check out Futures Explained in 200 Lines of Rust.