While getting more familiar with the axum web framework & its eco system I stumbled upon a programming pattern that I did not immediately recognize, even though I was familiar with it in other places, namely pattern matching of function parameters.
The web framework axum
uses pattern matching of parameters in functions to make certain types available to handler functions.
With the recent release of axum version 0.6.0
there is now a State
type that can be matched against in a function to access the application state. It's a type safe extractor checked at compile time.
Other similar extractors exists, e.g. for forms, paths, queries or json data.
Let's see an example axum application with some state to illustrate.
// Cargo.toml uses these dependencies
// axum = "0.6.0"
// tokio = { version = "1.0", features = ["full"] }
use std::net::SocketAddr;
use axum::{extract::State, response::IntoResponse, routing::get, Router};
/// Our application state we want to access later
#[derive(Clone)]
struct AppState {
name: String,
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/hello", get(hello_handler))
.with_state(AppState {
name: String::from("World"),
});
let address = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&address)
.serve(app.into_make_service())
.await
.expect("Failed to start server.");
}
// Note the `State(state)` pattern matching that extracts the `AppState`.
async fn hello_handler(State(state): State<AppState>) -> impl IntoResponse {
format!("Hello {}", state.name)
}
This extracts the AppState
into the parameter state
.
The Rust Programming Language book actually mentions function parameters as a location
of this pattern in Chapter 18 "Patterns and Matching".
Other probably better known places of this pattern are match
arms, conditional if let
expressions,
while let
& for
loops or let
statements.
For example a let
statement that uses a pattern could look like:
let (x, y) = (1, 2);
where 1
is assigned to x
& 2
assigned to y
.
The same pattern works for function parameters as well. Let's see how we can come up with our own type that illustrates the same behaviour.
struct Title(pub String);
fn hello(Title(title): Title) {
println!("Hello {}", title);
}
fn main() {
hello(Title(String::from("World")));
}
Running this program prints: Hello World
. The alternative to access the inner String
type would be:
fn hello(title: Title) {
println!("Hello {}", title.0);
}
The difference with the State
type of the axum crate is, this type uses a type parameter.
pub struct State<S>(pub S);
That's basically it, apart from a few macro derives & conversion functions. The generic type parameter S
is in place to hold any type.