Rebuilding a Rocket Server with Actix and Other Projects
Topics:
- Rebuilding a Rocket Server with Actix via @mysteriouspants
- Dark Forest Tweeter via @jacobrosenthal
- Thinking about Mobile Development with Rust via @DanielPBank
- Rant about the State of Rust Web Frameworks via @ChristopherSebastian
Rebuilding a Rocket Server with Actix via @mysteriouspants
Repo: https://github.com/idevgames/idevgames.com
Farewell Rocket, Hello Actix
Chris is trying Actix because Rocket has been rather irritating him. It boils down to some of the boilerplate that Rocket has when you start to do nontrivial things. For example, in Rocket to return a result of a handler, you do something that can give an Outcome
. This can be a Template
or a Redirect
- but critically they're two different types. So if you want "a redirect to the login page, or the page template itself, or an error page if the database has gone on holiday" you end up with some abomination of a return type like Result<Template, Result<Redirect, MyError>>
and then your handler code gets really messy converting into that.
The thing that drew Chris to want to try Actix is that its Actix-Web return type is a plain HttpResponse
which has some builder methods to construct what you actually want - e.g. HttpResponse::Ok().content_type("text/html").body("<!doctype html>...")
While it looks wordier it makes that initial decision between Template
and Redirect
much easier, since the redirect case turns into HttpResponse::Unauthorized().header("Location", "/login")
or some such.
This is kind of counter to Chris's first reaction, that the more granular types in Rocket were better, where after some thunking around and writing all too much boilerplate it seems that the more general types in Actix look to be a better fit for purpose.
Routing
One difference between Rocket and Actix is how they handle routing. With Rocket, routing happens on your handlers. With Actix, routing happens when you actually connect controllers.
Error Handling
Chris used thiserror to convert the different types of errors that could occur in the wild into a HandlerError
which can produce an HTTP error code.
Magical Destructuring of a Web Path
One interesting point was how Actix was able to destructure a snippet_id
from a parameterized tuple of web::Path()
in the controller logic.
// GET /snippets/{taxonomy}/{snippet_id}/edit form for editing an existing snippet
pub async fn edit(
ctxt: web::Data<ApplicationContext>,
session: Session,
web::Path((taxonomy, snippet_id)): web::Path<(String, i32)>,
) -> Result<HttpResponse, super::HandlerError> {
...
}
We verified that it is not type-checking as the following also works without compiler errors:
// GET /snippets/{taxonomy}/{snippet_id}/edit form for editing an existing snippet
pub async fn edit(
ctxt: web::Data<ApplicationContext>,
session: Session,
web::Path(taxonomy): web::Path<String>,
) -> Result<HttpResponse, super::HandlerError> {
...
}
Using the Tera Templating Engine
Previously, Chris was using Zola for a templating engine, but he didn't like that it required a code change every time he wanted to update the site. He changed to Tera which is inspired by Jinja2 and the Django templating engine from Python. He also likes horrorshow, which uses a macro-based approach.
Dark Forest Tweeter via @jacobrosenthal
Repo: https://github.com/jacobrosenthal/sophon_tweets
Jacob shared more details of his Sophon Tweeter project.
Simple Polling of Events
It uses the same pattern for polling futures using futures_micro that he used in his Rust SHTCx / SHTWx Driver project.
futures_micro::or!(
ctrl_c,
collect_from_graph(wrapped_state.clone()), //COLLECT_DELAY
collect_from_node(wrapped_state.clone()), //COLLECT_DELAY
tweets(wrapped_state.clone()), //STAGGER_DELAY
tweet_counts(), //COUNTS_DELAY
)
.await
.unwrap();
Shared State Across Threads
It loads / saves state to a state file and the threads share the state using a Mutex
:
async fn tweets(wrapped_state: Arc<Mutex<SophonShare>>) -> Result<(), SophonError> {
loop {
// scope for mutex release
{
let mut share = wrapped_state.lock().await;
...
}
}
}
wrapped_state.lock()
returns a MutexGuard
which unlocks the lock when it is dropped (falls out of scope). So there is no issue with forgetting to unlock the Mutex.
Pulling Data from the Dark Forest Graph
There is a GraphQL query for pulling data from the Dark Forest Graph and serializing it into purpose-built structs. This all happens in the graph.rs file.
The Repo for creating the graph is written in TypeScript: https://github.com/jacobrosenthal/darkforest-graph
Thinking about Mobile Development with Rust via @DanielPBank
Daniel is on a quest to build a mobile app. Naturally he would like to use Rust, but is unsure how to go about it. There were two possible options discussed.
- Option 1: Using cargo-mobile, the entire app could be built in Rust (without even needing to touch Android Studio or XCode!). You simply run
cargo android run
and it will build and run on a connected Android device. Cargo-mobile projects are based off of template packs which, at the time of writing, include packs for bevy, wgpu, and winit. Jacob likes this approach because you don't have to touch stinky IDE's and can write entirely in Rust. Daniel was scared by the limited documentation, lack of tutorials, and newness of the project. If you were building a Bevy game, this would be a cool project to check out though.
Under the hood, cargo-mobile is using android-ndk-rs, a wrapper for the Android NDK.
Random aside about Bevy: there is a really cool demo of running the MNIST classifier with Bevy and Tract
- Option 2: We can write the bulk of the mobile apps in their respective language (Kotlin or Swift) and only write cross-platform functionality with Rust. There is a good tutorial that outlines the process for building a shared Rust library that interfaces using Foreign Function Interface.
Rant about the State of Rust Web Frameworks via @ChristopherSebastian
Chris rants about the difficulty in working with the various Rust Web Frameworks. There are too many dependencies, too many layers of abstractions, and it creates an unpredictable amount of work. The types are more important than the memory or performance. The ecosystem still feels immature.
Of all the frameworks, he likes tide the best.
Crates You Should Know
- cargo-mobile: Coming soon, a way to build mobile apps with Rust
- trunk: Build WASM Web Apps without the intermediary NPM Modules
- tera: A template engine inspired by Jinja2 and the Django template language
- horrorshow: A templating library written in Rust macros