actix-webの柔軟なリクエストハンドラの仕組み

きっかけ

業務で Rust を使っていこうということになったので、Rust を勉強しながら何か Web アプリでも作ってみようと思って actix-web を触り始めた。サンプルコードをいじっていて気になったのはリクエストハンドラの引数の柔軟さで、まるでスクリプト言語のように扱える。

例えば以下は http://localhost:8080/hoge にリクエストを受けると path 部分である /hoge をレスポンスとして返す。

fn index(
    req: HttpRequest,
) -> impl IntoFuture<Item = String, Error = Error> {
    Ok(String::from(req.path()))
}

fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .default_service(web::route().to_async(index))
    })
    .bind("127.0.0.1:8080")?
    .run()
}

リクエストハンドラに何かデータ(普通はアプリケーションの状態など)を渡すこともできる。この例だと http://localhost:8080/spacecat にアクセスすると http://www.example.com/spacecat が表示される。

fn index(
    req: HttpRequest,
    prefix: web::Data<&str>,
) -> impl IntoFuture<Item = String, Error = Error> {
    Ok(format!("{}{}", prefix, req.path()))
}

fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .data("http://www.example.com")
            .default_service(web::route().to_async(index))
    })
    .bind("127.0.0.1:8080")?
    .run()
}

ここで「どこまで柔軟性があるんだろうか?」と思って、試しにリクエストハンドラの引数の順番を入れ替えてみた。

fn index(
    prefix: web::Data<&str>,
    req: HttpRequest,
) -> impl IntoFuture<Item = String, Error = Error> {
    Ok(format!("{}{}", prefix, req.path()))
}

これ、ちゃんと動いてしまう。

Rust のようなガチガチに検証するタイプの言語でこの柔軟性を一体どうやって実現しているんだろう・・・? と思ったのがきっかけ。