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.

Monomorphization

The compiler will generate a different version of the code for each type that we use for the generic parameter. This is called monomorphization.

You can learn more about it here and here

Check that everything works as expected and commit your changes:

git add .
git commit -m "refactor film endpoints to use generics"