Static dispatch
You can check out this section of the Rust Book to understand about some of the trade-offs of using dynamic dispatch.
We're going to learn in this section how to use Generics to leverage static dispatch.
Refactor the film
endpoints
Let's change all the code to use generics
instead of trait objects
:
#![allow(unused)] fn main() { use actix_web::{ web::{self, ServiceConfig}, HttpResponse, }; use shared::models::{CreateFilm, Film}; use uuid::Uuid; use crate::film_repository::FilmRepository; pub fn service<R: FilmRepository>(cfg: &mut ServiceConfig) { cfg.service( web::scope("/v1/films") // get all films .route("", web::get().to(get_all::<R>)) // get by id .route("/{film_id}", web::get().to(get::<R>)) // post new film .route("", web::post().to(post::<R>)) // update film .route("", web::put().to(put::<R>)) // delete film .route("/{film_id}", web::delete().to(delete::<R>)), ); } async fn get_all<R: FilmRepository>(repo: web::Data<R>) -> HttpResponse { match repo.get_films().await { Ok(films) => HttpResponse::Ok().json(films), Err(e) => HttpResponse::NotFound().body(format!("Internal server error: {:?}", e)), } } async fn get<R: FilmRepository>(film_id: web::Path<Uuid>, repo: web::Data<R>) -> HttpResponse { match repo.get_film(&film_id).await { Ok(film) => HttpResponse::Ok().json(film), Err(_) => HttpResponse::NotFound().body("Not found"), } } async fn post<R: FilmRepository>( create_film: web::Json<CreateFilm>, repo: web::Data<R>, ) -> HttpResponse { match repo.create_film(&create_film).await { Ok(film) => HttpResponse::Ok().json(film), Err(e) => { HttpResponse::InternalServerError().body(format!("Internal server error: {:?}", e)) } } } async fn put<R: FilmRepository>(film: web::Json<Film>, repo: web::Data<R>) -> HttpResponse { match repo.update_film(&film).await { Ok(film) => HttpResponse::Ok().json(film), Err(e) => HttpResponse::NotFound().body(format!("Internal server error: {:?}", e)), } } async fn delete<R: FilmRepository>(film_id: web::Path<Uuid>, repo: web::Data<R>) -> HttpResponse { match repo.delete_film(&film_id).await { Ok(film) => HttpResponse::Ok().json(film), Err(e) => { HttpResponse::InternalServerError().body(format!("Internal server error: {:?}", e)) } } } }
Hinting the compiler
If you try to compile the code, you'll get an error:
error[E0282]: type annotations needed
--> api/shuttle/src/main.rs:22:24
|
22 | .configure(api_lib::films::service);
| ^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `R` declared on the function `service`
|
help: consider specifying the generic argument
|
22 | .configure(api_lib::films::service::<R>);
| +++++
For more information about this error, try `rustc --explain E0282`.
error: could not compile `api-shuttle` (bin "api-shuttle") due to previous error
Error: Build failed. Is the Shuttle runtime missing?
[Finished running. Exit status: 1]
But the compiler is giving us a hint on how to fix it. Let's do it.
Open the main.rs
file of our api-shuttle
crate and let's change a couple of things:
let film_repository = api_lib::film_repository::PostgresFilmRepository::new(pool);
- let film_repository: actix_web::web::Data<Box<dyn api_lib::film_repository::FilmRepository>> =
- actix_web::web::Data::new(Box::new(film_repository));
+ let film_repository = actix_web::web::Data::new(film_repository);
let config = move |cfg: &mut ServiceConfig| {
cfg.app_data(film_repository)
.configure(api_lib::health::service)
- .configure(api_lib::films::service);
+ .configure(api_lib::films::service::<api_lib::film_repository::PostgresFilmRepository>);
};
This should be enough to make the compiler happy. Now it knows what type to use for the R
generic parameter.
Check that everything works as expected and commit your changes:
git add .
git commit -m "refactor film endpoints to use generics"