0
votes

Creating an authentication server.

I want a PGUserRepo struct, which implements a UserRepo trait async fn create.

The PGUserRepo should have a field that implements the Hasher trait.

However when I try to restrict the PGUserRepo field to be a dyn Hasher I get the error

"the trait std::marker::Send is not implemented for `(dyn domain::hasher::Hasher + 'static)".

If I make the PGUserRepo field the concrete implementing type, ArgonHasher, no error. But I don't want the PGUserRepo to care about what kind of Hasher was given to it, just that it implements the Hasher trait.

I have tried wrapping the hasher field in various combinations of Box, Arc, and Mutex, but I do not understand the root of why calling it an ArgonHasher is fine but saying it is some implementer of Hasher is not.

Code, collapsed to one file for convenience:


pub trait Hasher {
    fn hash(&self, password: String) -> Result<String, HasherError>;
}

pub struct ArgonHasher {}

impl Hasher for ArgonHasher {
    fn hash(&self, password: String) -> Result<String, HasherError> {
        let result = argon2id13::pwhash(
            &password.as_bytes(),
            argon2id13::OPSLIMIT_INTERACTIVE,
            argon2id13::MEMLIMIT_INTERACTIVE,
        );
        match result {
            Ok(hashed_password_result) => match std::str::from_utf8(&hashed_password_result.0) {
                Ok(hashed_password_utf8) => Ok(String::from(hashed_password_utf8)),
                Err(e) => Err(HasherError::from(e)),
            },
            Err(e) => Err(HasherError::from(e)),
        }
    }
}

impl From<()> for HasherError {
    fn from(_: ()) -> Self {
        HasherError::HasherError {
            message: String::from(""),
        }
    }
}

impl From<std::str::Utf8Error> for HasherError {
    fn from(cause: std::str::Utf8Error) -> Self {
        HasherError::HasherError {
            message: format!("{}", cause),
        }
    }
}

#[async_trait]
pub trait UserRepo {
    async fn create(&self, user: User) -> Result<User, UserRepoError>;
}

#[derive(Debug)]
pub enum UserRepoError {
    UserAlreadyExistsError { field: String, value: String },
    UserRepoError { message: String },
}

impl fmt::Display for UserRepoError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *&self {
            UserRepoError::UserAlreadyExistsError { field, value } => {
                f.write_str(&format!("User with {}={} already exists", field, value))
            }
            UserRepoError::UserRepoError { message } => f.write_str(&message),
        }
    }
}


pub struct PGUserRepo {
    conn_pool: PgPool,
    hasher: &'static dyn Hasher,
}

impl PGUserRepo {
    pub fn new(conn_pool: PgPool, hasher: &'static dyn Hasher) -> Self {
        PGUserRepo { conn_pool, hasher }
    }
}

#[async_trait]
impl UserRepo for PGUserRepo {
    async fn create(&self, user: User) -> Result<User, UserRepoError> {
    # Compiler error is on this function
    # hasher is not even used in this function yet, it's just a field on PGuserRepo
}

Bonus: I don't really need the Hasher trait hash to have a reference to self but without it I get "error[E0038]: the trait domain::hasher::Hasher cannot be made into an object".

1
See also Sharing a struct with trait objects as properties across threads That Q&A is for boxed traits but the concepts are the same.kmdreko

1 Answers

1
votes

The marker trait Send is used to indicate when a type is safe to be transferred between threads. It is implemented by default when the compiler deems it to be safe. However, you have a trait object hasher that has no constraint on if it can be shared between threads safely.

This comes up here because async code is usually handled via multiple threads and the async_trait enforces that.

The fix is to indicate that only Hashers that can be shared between threads are allowed. You can do this by using the Sync trait:

pub struct PGUserRepo {
    conn_pool: PgPool,
    hasher: &'static (dyn Hasher + Sync),
}

See Understanding the Send trait to learn more about the difference between Sync and Send and their purpose.