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.

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 the main() function work with tokio runtime in asynchronous.
  • App::new() returns an App<AppEntry>
  • fn greet(req: HttpRequest) -> impl Responder implements
      F: 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 .

Back Back