Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Generic async trait that returns the implemented Struct

I’m getting blocked on what I think it’s a simple problem. I’m still learning Rust, and I want to do the following:

I want to create an async trait (using async-trait) that will instantiate a DB connection instance and it will return the struct that is implementing that trait.

mongo.rs

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

#[async_trait]
pub trait DB {
    async fn init<T, E>() -> Result<T, E>;
}

Then: favorites.rs (See the implementation of the DB trait down below)

use async_trait::async_trait;
use mongodb::Collection;
use rocket::form::FromForm;
use rocket::serde::ser::StdError;
use serde::{Deserialize, Serialize};
use std::error::Error;
use uuid::Uuid;

pub struct FavoritesDB {
    collection: Collection<Favorite>,
}

#[derive(Debug)]
pub enum FavoritesError {
    UnknownError(Box<dyn Error>),
}

// Conflicts with the one down below
// impl From<Box<dyn Error>> for FavoritesError {
//     fn from(err: Box<dyn Error>) -> FavoritesError {
//         FavoritesError::UnknownError(err)
//     }
// }

impl From<Box<dyn StdError>> for FavoritesError {
    fn from(err: Box<dyn StdError>) -> FavoritesError {
        FavoritesError::UnknownError(err)
    }
}


#[async_trait]
impl mongo::DB for FavoritesDB {
    async fn init<FavoritesDB, FavoritesError>() -> Result<FavoritesDB, FavoritesError> {
        let main_db = mongo::init::<Favorite>("Favorites").await?;
        let db = FavoritesDB {
            collection: main_db.collection,
        };
        Ok(db)
    }
}

There are a list of problems with this:

1)

error[E0574]: expected struct, variant or union type, found type parameter `FavoritesDB`
  --> src\db\favorites.rs:41:18
   |
41 |         let db = FavoritesDB {
   |                  ^^^^^^^^^^^ not a struct, variant or union type
   |
help: consider importing this struct instead
  1. I’ve tried implementing From<Box<dyn tdError>> manually but it conflicts with what I have.
error[E0277]: `?` couldn't convert the error to `FavoritesError`
   --> src\db\favorites.rs:40:65
    |
40  |         let main_db = mongo::init::<Favorite>("Favorites").await?;
    |                                                                 ^ the trait `From<Box<dyn StdError>>` is not implemented for `FavoritesError`
    |
    = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
    = note: required because of the requirements on the impl of `FromResidual<Result<Infallible, Box<dyn StdError>>>` for `Result<FavoritesDB, FavoritesError>`
note: required by `from_residual`
   --> C:\Users\asili\.rustup\toolchains\nightly-2021-11-15-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\ops\try_trait.rs:339:5
    |
339 |     fn from_residual(residual: R) -> Self;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: consider further restricting this bound
    |
39  |     async fn init<FavoritesDB, FavoritesError + std::convert::From<std::boxed::Box<dyn std::error::Error>>>() -> Result<FavoritesDB, FavoritesError> {
    |                                               ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Some errors have detailed explanations: E0277, E0282, E0574.
For more information about an error, try `rustc --explain E0277`.

Just for more context, here’s the DB struct and impl (Currently connecting to a local MongoDB) included in mongo.rs

pub struct Database<T> {
    client: mongodb::Database,
    pub collection: Collection<T>,
}

impl<T> Database<T> {
    pub async fn init() -> Result<mongodb::Database, Box<dyn Error>> {
        let mut client_options = ClientOptions::parse("mongodb://localhost:27017").await?;
        client_options.app_name = Some("My App".to_string());
        // Get a handle to the deployment.
        let client = Client::with_options(client_options)?;
        let db = client.database("rust-svelte");
        return Ok(db);
    }
}

pub async fn init<T>(collection: &str) -> Result<Database<T>, Box<dyn Error>> {
    let client = Database::<T>::init().await?;
    let collection = client.collection::<T>(collection);
    let db = Database { client, collection };
    Ok(db)
}

I’ve been searching for a few days over SO and the Rust community and my Google-Rust-Fu isn’t good enough to spot what’s the problem. Any ideas?

>Solution :

You’ve declared init to take 2 generic parameters: T and E.

This means that the code that calls init has to provide the concrete types to fill in those parameters. For example, if someone was using your library, it would be totally feasible for them to write init::<i64, ()>(), and your code should deal with that.

Because of that, when you define your impl DB for FavouritesDB, you write this:

async fn init<FavoritesDB, FavoritesError>() -> Result<FavoritesDB, FavoritesError>

This is no different to writing:

async fn init<T, E>() -> Result<T, E>

you’ve just given the type parameters different names that happen to match a struct that you’re probably trying to use.

A better pattern might be an associated type. Instead of the caller deciding what the concrete types are (as is the case with generics), with associated types, the implementation of the trait on the type sets the type.

This is common with things like Iterator. Iterator has no generic parameters, but a single associated type Item. This is because it wouldn’t make sense to be able to impl Iterator<String> for MyStruct and impl Iterator<i64> for MyStruct at the same time. Instead, we want to implement Iterator for a type once, and that implementation carries with it the definition of the types it expects.

So something like this (I’ve omitted the async-ness for brevity since it doesn’t seem to be a factor here):

trait DB {
  type InitOk;
  type InitErr;

  fn init() -> Result<Self::InitOk, Self::InitErr>;
}

impl Db for FavouritesDB {
  type InitOk = FavouritesDB;
  type InitErr = FavouritesError;
  fn init() -> Result<Self::InitOk, Self::InitErr> {
    // now you can reference FavouritesDB the struct, rather than the generic parameter
  }
}

I’d also add you may want to not have the InitOk type, and just return Self, but that’s up to you if you think you might want a struct to be able to create a different type.

For part 2, Rust assumes nothing (other than Sized) about generic parameters. If you want Rust to force a generic to have some property, you have to add a bound.

The compiler is telling you here that it can’t use the ? operator to convert automatically, because it doesn’t know that your error type has a From<Box<dyn Error>> implementation.

If you know that every error type is going to implement that, you can add it as a bound on the associated type, like this:

trait DB {
  type InitOk;
  type InitErr: From<Box<dyn Error>>;

  // ...
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading