dtolnay / async-trait

Type erasure for async trait methods
Apache License 2.0
1.81k stars 84 forks source link

Lifetime issue when trait parameter is a reference #196

Closed peterstuart closed 2 years ago

peterstuart commented 2 years ago

In the following example, I'd like implementations of the Create trait to be able to have Store be either a reference or a mutable reference, so I don't want to bake that in to the definition of the trait.

use async_trait::async_trait;

#[async_trait]
pub trait Create<Input, Store, Error>
where
    Self: Sized,
    Store: Send + Sync,
{
    async fn create(input: Input, store: Store) -> Result<Self, Error>;
}

… but when I try to write an implementation using a reference:

#[cfg(test)]
mod tests {
    use super::Create;
    use async_trait::async_trait;
    use sqlx::{Error, FromRow, PgPool};

    #[derive(FromRow)]
    struct User {
        id: i32,
        first_name: String,
        last_name: String,
    }

    struct Input {
        first_name: String,
        last_name: String,
    }

    #[async_trait]
    impl Create<Input, &PgPool, Error> for User {
        async fn create(input: Input, store: &PgPool) -> Result<Self, Error> {
            let user = sqlx::query_as::<_, User>(
                "INSERT INTO users (first_name, last_name) VALUES (?, ?) RETURNING *",
            )
            .bind(input.first_name)
            .bind(input.last_name)
            .fetch_one(store)
            .await?;

            Ok(user)
        }
    }
}

… I get the following error:

error[E0195]: lifetime parameters or bounds on method `create` do not match the trait declaration
  --> src/create.rs:64:18
   |
41 |     async fn create(input: Input, store: Store) -> Result<Self, Error>;
   |              ---------------------------------- lifetimes in impl do not match this method in trait
...
64 |         async fn create(input: Input, store: &PgPool) -> Result<Self, Error> {
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetimes do not match method in trait

Is it possible to achieve what I'm trying to do?

dtolnay commented 2 years ago

You need to write this as:

#[async_trait]
pub trait Create<Input, Store, Error>
where
    Self: Sized,
    Store: Send + Sync,
{
    async fn create(input: Input, store: Store) -> Result<Self, Error>
    where
        Store: 'async_trait;
}

…

#[async_trait]
impl<'a> Create<Input, &'a PgPool, Error> for User {
    async fn create(input: Input, store: &'a PgPool) -> Result<Self, Error>
    where
        'a: 'async_trait,
    {
        …
    }
}
peterstuart commented 2 years ago

Perfect, thanks for the help!