Actors in Actix, actors in life
I previously wrote an article back in November 2017: Replacing Elasticsearch with Rust and SQLite. In it, I needed to create a few HTTP endpoints that ingested JSON, perform a database lookup, and return JSON. Very simple. No query / path parameters, authentication, authorization, H2, or TLS. Any
v0.1 microframework would do (Project repo).
I went with Rocket and I knew what I was getting into back then:
[Rocket] has the best testing story, serde support, and contains minimal boilerplate. The downside is that nightly Rust is required.
I became enamored with succint endpoints (a differentiating feature at the time):
I didn’t understand how stability was such an important feature. I was familiar with needing new versions of the nightly compiler to stay current with clippy and rustfmt, but it was a blindspot when it came to dependencies.
Six Months Later
It’s been six months since the article. There have been a half dozen updates to Rocket and numerous updates to the nightly compiler. Since I only revisit the project about once every other week, I was always met with a compiler error, as I had most likely updated the nightly compiler in the meantime to grab a new rustfmt or clippy version, and the Rocket version wouldn’t work with that nightly. Syncing Rocket and the nightly compiler to the latest version normally fixed the issue. I’m very thankful that Rocket is maintained to this high degree.
There was a time when the latest Rocket broke on nightly because the nightly version broke ring, a dependency. A combination of cross linking, alpha versions, module paths, and herd mentality led to a temporary impasse. The only solution was to pin the nightly version of Rust with a specific version of Rocket. Everyone worked together and eventually resolved the issue. Still, I find myself wary.
This did have the side effect of me pinning the nightly compiler via
rustup override. Though, I came back a couple weeks later to update dependencies (including Rocket) and this time my usual incantation of
cargo +nightly build failed. Syncing versions didn’t help. It took me an embarrassingly long amount of time to realize that I needed to either update the nightly pin or unpin.
I don’t want a project where I have to remember its unique setup. I like it when a
cargo build or
cargo build --all is all I need.
In these last six months, I have been really impressed with the actix.rs project, specifically actix web. It’s a relatively new project, in fact the first released version on crates.io was shortly before I started working on my project. To me, it has everything Rocket has to offer but it also compiles on stable. As a plus, it integrates with tokio for asynchronous endpoints. I don’t utilize this feature, but it’s nice to know actix is closely tracking the future of scalable Rust networking.
I can’t overstate how similar actix web endpoints resemble Rocket endpoints. The following diff is the migration for the endpoint posted earlier:
That was too easy. How about an endpoint that takes application state too?
The only thing that changed in the migration were the function signatures! Hats off to the actix project for reaching the same level of ergonomics as Rocket, making it a painless migration, all the while working on stable Rust.
Really, the only code I wrote for the migration was for creating an
I completed the migration in under an hour with zero experience with actix and only using the official docs. I kicked myself for not doing this sooner, it was almost too easy.
One hiccup, though: integration testing.
It took me longer to convert a few tests than to go from zero to migrated with actix. Testing with actix has it’s own documentation section which got me 95% of the way.
I was used to Rocket’s way of testing (example taken from Rocket docs):
In the example, it’s immediately obvious how to test for the response’s body contents. Actix lacked this example, so I created it. I won’t bore you with why it took me so long to figure it out, so for posterity, here is the same test but with actix.
I’m keeping a Rocket branch alive so I can do some comparisons:
Compile times with Rocket
Compile times with Actix-Web (I compile with same nightly to control for any compiler improvements)
- Rocket: 9.5MB (5.3MB
- Actix-Web: 13MB (8.5MB
- Actix-Web (no default features): 12MB (7.6MB
So Actix-Web results in a slower compile time and a larger executable. Migrating to actix web didn’t result in wins across the board, but it is a fine trade-off. For curiosity, I ran cargo bloat to see if there were obvious culprits:
Eh, nothing stands out. That’s ok.
Last difference that I felt is that Rocket has its built in Rocket.toml, which I used to change the bound address. This was simple enough to move to a commandline argument.
I’m more than happy with the migration and will recommend anyone who uses Rocket and feels the pain of nightly breakage to use actix web. My intent for this article was not to come across like “X is better than Y framework” as both are exceptional, but rather showcase actix for Rocket users. It’s easy, fast, and stable.