Zero2Prod: Day 0 – Hello actix and tokio
I’m start learning Rust by following the excellent book Zero to Production by Luca Palmieri. The book introduce to async programming in Rust by implementing an email newsletter.
- Resources: Code on GitHub
- Reviews by Chris Allen
- References: actix.rs, actix-web, actix/examples, tokio.rs
Building an email newsletter
- A blog visitor can subscribe to the newsletter, and receive emails.
- The blog author can send an email to all subscribers.
- A blog subscriber can unsubscribe from the newsletter.
Setting up
Setup rust-analyzer
with VSCode
I use target-ra
to not conflict with target
from cargo. I also try the Cursor editor to see if it helps on learning a new language.
// settings.json
{
"rust-analyzer.cargo.extraEnv": {
"CARGO_TARGET_DIR": "target-ra"
},
"rust-analyzer.check.extraEnv": {
"CARGO_TARGET_DIR": "target-ra"
}
}
Install cargo-watch
and cargo-clippy
Use it to quickly see the errors, build then run the program:
cargo watch -x check -x test -x run
cargo watch -x clippy
Other tools
cargo fmt
cargo fmt -- --check
cargo audit
Hello actix and tokio
The following code create a simple server in actix.
use actix_web::{web, App, HttpRequest, HttpServer, Responder};
use tokio;
#[tokio::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
// register routes
App::new()
.route("/health", web::get().to(health))
.route("/", web::get().to(greet))
.route("/{name}", web::get().to(greet))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
async fn health() -> &'static str {
"OK"
}
async fn greet(req: HttpRequest) -> impl Responder {
let name = req.match_info().get("name").unwrap_or_else(|| "World");
format!("Hello {}", name)
}
[tokio::main]
is a macro to make themain()
function work with tokio runtime in asynchronous.App::new()
returns anApp<AppEntry>
fn greet(req: HttpRequest) -> impl Responder
implementsF: Handler<FromRequest + 'static> F::Output: Responser + 'static'
Run cargo expand
to see the generated code
cargo install cargo-expand
Run it on the project:
cargo expand # 👈 require nightly toolchain as default
cargo +nightly expand # 👈 use nightly toolchain on demand
How the macro is expanded:
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use actix_web::{web, App, HttpRequest, HttpServer, Responder};
use tokio;
fn main() -> std::io::Result<()> {
let body = async {
HttpServer::new(|| {
App::new()
.route("/health", web::get().to(health))
.route("/", web::get().to(greet))
.route("/{name}", web::get().to(greet))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
};
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
{
return tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(body);
}
}
async fn health() -> &'static str {
"OK"
}
async fn greet(req: HttpRequest) -> impl Responder {
let name = req.match_info().get("name").unwrap_or_else(|| "World");
::alloc::__export::must_use({
let res = ::alloc::fmt::format(format_args!("Hello {0}", name));
res
})
}
Author
I'm Oliver Nguyen. A software maker working mostly in Go and JavaScript. I enjoy learning and seeing a better version of myself each day. Occasionally spin off new open source projects. Share knowledge and thoughts during my journey. Connect with me on , , , and .