🪴 notes

Search

Search IconIcon to open search

custom error types in rust

Published Dec 31, 2024 Last updated Jan 1, 2025 Edit Source

sources:

keep in mind the “orphan rule”: it is forbidden to implement a foreign trait on a foreign type. this is solved using new type idiom, defining a custom enum type to wrap the foreign error type. the advantage of doing this is that you can name the struct that makes sense for your project. instead of a general reqwest::Error it could be SendEmailError defined as

1
struct SendEmailError(reqwest::Error)

there’s more to it. read this how to code it guide about newtypes.

check the documentation about the Error types in the libraries you are using it should tell you how to instantiate one from std::error::Error

all good error types should implement std::error::Error trait making it consistent with all error types in the rust world. it is defined as:

1
2
3
4
5
6
pub trait Error: Debug + Display {
	/// The lower-level source of this error, if any.
	fn source(&self) -> Option<&(dyn Error + 'static)> {
		None
	}
}

the error type needs to implement Debug and Display traits. they are just different ways to print the context of an error. one is for developers (people who work on the internals) and consumers/users (other devs using the library or end users) respectively.

dyn Error is a “trait object”. a type where we only know it implements the Error trait.

“generic types are resolved at compile-time (static dispatch) and trait objects incur a runtime cost (dynamic dispatch).

  • zerotoprod

todo: define static dispatch or dispatch

the source method returns the underlying error type of your custom error. in the case of SendEmailError, reqwest::Error. implemented by hand like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

#[derive(Debug)] //Debug trait can implemented with this derive macro
struct SendEmailError(reqwest::Error)

impl std::error::Error for SendEmailError {
	fn source(&self) -> Option<&(dyn Error + 'static)> {
		Some(self.0) //might error, just read the compiler errors xd
	}
}
// or Error use default implementation like:
impl std::error:Error for SendEmailError {}

impl std::fmt::Display for SendEmailError {
	fn fmt(&self, f: std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "Failed to send email.")
	}
}

// Debug can also be implemented like Display for more control over the printed message
// make sure to only use one implementation, else you'll get a compiler error
impl std::fmt::Debug for SendEmailError {
	fn fmt(&self, f: std::fmt::Formatter<'_>) -> std::fmt::Result {
		
		writeln!(f, "{}\n", self)?;

		//print the error chain into the formatter
		let mut current = self.source();
		while let Some(cause) = current {
			writeln!(f, "Caused by:\n\t{}", cause)?;
			current = cause.source();
		}
		Ok(())
	}
}

with the Error trait implemented, it can be used like:

1
2
3
4
5
6
7
8
9
async fn send_email(email: &str, content: &str) -> Result<(), SendEmailError> {}


let response = send_email("email@gmail.com", "hello").await;

if response.is_err() {
	println!("{}", response); // Failed to send email.
	println!("{:?}", response); //  ... Caused by: [message from reqwest::Error] ... more
}