Skip to main content

Error Handling

impl PostgresUsers {
pub async fn new(connection_string: String) -> Result<Self, ApplicationError> {
let database_pool = PgPool::connect(&connection_string)
.await
.map_err(|e| ApplicationError::DatabaseError(e.to_string()))?;

Ok(Self {
db: database_pool,
})
}
}

This code introduces two important Rust error handling patterns:

The ? operator

This automatically propagates the error back up the stack. For this to work, the current function needs to return a Result and the error you are propagating needs to be the same as the Error of the current function. In the above example, the new() function uses a custom ApplicationError enum.

That means if you want to propagate an error back up the stack, the function call you add the ? to also needs to return an ApplicationError.

The code sample above is trying to retrieve an environment variable, and the env::var function definitely doesn't return an ApplicationError. That's where map_err comes in.

The map_err function`

The map_err function allows you to convert an error from one type to another. In the above example, you're taking the error returned by the env::var function, and creating a new ApplicationError::DatabaseError passing in the actual error as a string. Because you now have an ApplicationError, you can then propagate that back up the stack using the ? syntax.

You might be thinking, where has that ApplicationError type come from though.

Error Handling with thiserror

For structured error handling, you'll use the thiserror crate to define application-specific errors:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum ApplicationError {
#[error("user already exists")]
UserAlreadyExists,
#[error("user does not exist")]
UserDoesNotExist,
#[error("the provider password is incorrect")]
IncorrectPassword,
#[error("error interacting with database {0}")]
DatabaseError(String),
#[error("unexpected application error {0}")]
ApplicationError(String),
}