So I wanted to use Axum to implement a web server, where the logic of the handler is implemented as methods on a struct and where the struct takes care of setting up the server and listening.
My initial attempt came up with code that looks like this:
#[derive(Debug, Serialize, Deserialize)]
struct Person {
age: u32,
name: String
}
mod http {
use axum::{
routing::get,
http::StatusCode,
response::IntoResponse,
Json, Router,
};
use std::net::SocketAddr;
use crate::Person;
pub struct ServerLogic {
port: u16,
}
impl ServerLogic {
pub fn new(port: u16) -> Self {
Self {
port
}
}
pub async fn serve(&self) {
let port = self.port;
let app = Router::new()
.route("/get", axum::routing::get(self.hello));
let addr = SocketAddr::from(([127, 0, 0, 1], port));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
pub async fn hello(&self) -> impl axum::response::IntoResponse {
(axum::http::StatusCode::OK, axum::Json(Person { age: 42, name: "John".to_string() }))
}
}
}
#[tokio::main]
async fn main() -> () {
// build our application with a route
http::ServerLogic::new(3000).serve().await;
}
This did not compile right away. Fiddling with the compiler, here are some of the examples of errors I encountered:
error[E0615]: attempted to take value of method `hello` on type `&ServerLogic`
--> src/main.rs:45:56
|
45 | .route("/get", axum::routing::get(self.hello));
| ^^^^^ method, not a field
|
help: use parentheses to call the method
|
45 | .route("/get", axum::routing::get(self.hello()));
| ++
error: lifetime may not live long enough
--> src/main.rs:45:59
|
45 | .route("/get", axum::routing::get(move || self.hello()));
| ------- ^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure `impl Future<Output = impl IntoResponse>` contains a lifetime `'2`
| lifetime `'1` represents this closure's body
error[E0277]: the trait bound `ServerLogic: Clone` is not satisfied in `[closure@src/main.rs:45:51: 45:58]`
--> src/main.rs:45:51
|
45 | .route("/get", axum::routing::get(move || self.hello()));
| ------------------ -------^^^^^^^^^^^^^
| | |
| | within `[closure@src/main.rs:45:51: 45:58]`, the trait `Clone` is not implemented for `ServerLogic`
| | within this `[closure@src/main.rs:45:51: 45:58]`
| required by a bound introduced by this call
|
= help: the trait `Handler<T, S, B2>` is implemented for `Layered<L, H, T, S, B, B2>`
But I finally came up with a version that works, which is reproduced below:
use serde::{Serialize, Deserialize};
use serde::de::StdError;
#[derive(Debug, Serialize, Deserialize)]
struct Person {
age: u32,
name: String
}
mod http {
use axum::{
routing::get,
http::StatusCode,
response::IntoResponse,
Json, Router,
};
use std::net::SocketAddr;
use crate::Person;
#[derive(Clone)]
pub struct ServerLogic {
port: u16,
}
impl ServerLogic {
pub fn new(port: u16) -> Self {
Self {
port
}
}
pub async fn serve(self) {
let port = self.port;
let app = Router::new()
.route("/get", axum::routing::get(move || self.hello()));
let addr = SocketAddr::from(([127, 0, 0, 1], port));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
pub async fn hello(self) -> impl axum::response::IntoResponse {
(axum::http::StatusCode::OK, axum::Json(Person { age: 42, name: "John".to_string() }))
}
}
}
#[tokio::main]
async fn main() -> () {
// build our application with a route
http::ServerLogic::new(3000).serve().await;
}
Now I am not sure if this is the most idiomatic or the most efficient way. My doubts are because:
- I had to change the struct methods from
&self
toself
meaning the ownership is being moved to the methods. I feel this might not be the way an experienced Rust developer will go about things - I had to have the
ServerLogic
struct implementClone
which I interpret to mean there would be duplication of data which perhaps could be avoided.
So my question is, are these doubts valid? If so, are they valid for the reasons I think they are? Or perhaps there are other inefficiency in the approach? And finally what would be the more idiomatic approach?