Rust thiserror
This article delves into a Rust program that showcases custom error handling, parsing, and validation.
By leveraging the thiserror
crate, we create descriptive error types to manage various parsing scenarios robustly.
We’ll explore how the program defines an Animal
struct, parses and validates integer strings, and utilizes generics and traits for flexible error reporting.
Whether you’re new to Rust or looking to deepen your understanding of error handling and type safety, this step-by-step guide will help you grasp these essential concepts and apply them effectively in your Rust projects.
Webdock – Fast Cloud VPS Linux Hosting
Importing Dependencies
use std::fmt;
use std::num::{ParseIntError, IntErrorKind};
use thiserror::Error;
std::fmt
: Used for formatting and printing.std::num::{ParseIntError, IntErrorKind}
: These are error types related to parsing integers.thiserror::Error
: A crate used for deriving error types in a more convenient way.
Defining Custom Errors
#[derive(Debug, Error)]
pub enum MyError {
#[error("Failed to parse integer: {0}")]
ParseError(#[from] ParseIntError),
#[error("Value cannot be negative")]
NegativeValue,
#[error("Value exceeds maximum allowed for u8")]
Overflow,
}
#[derive(Debug, Error)]
: This macro derives theDebug
andError
traits for theMyError
enum.pub enum MyError
: Defines a public enumMyError
which represents different kinds of errors.ParseError(#[from] ParseIntError)
: Wraps aParseIntError
with a custom error message.NegativeValue
: Represents an error when a value is negative.Overflow
: Represents an error when a value exceeds the maximum allowed foru8
.
Defining a Struct
#[derive(Debug)]
struct Animal {
name: String,
age: u8,
}
#[derive(Debug)]
: Derives theDebug
trait for theAnimal
struct, allowing it to be formatted using{:?}
.struct Animal
: Defines a structAnimal
with two fields:name
: AString
representing the animal’s name.age
: Au8
representing the animal’s age.
Display Function
fn display<T: fmt::Debug>(value: Result<T, MyError>) {
match value {
Ok(val) => println!("Here's your value: {:?}", val),
Err(e) => println!("An error occurred: {:?}", e),
}
}
fn display<T: fmt::Debug>
: A generic function that takes aResult<T, MyError>
.match value
: Matches on the result:Ok(val)
: If the result isOk
, prints the value.Err(e)
: If the result isErr
, prints the error.
Parsing and Validating Function
fn parse_and_validate(value_str: &str) -> Result<u8, MyError> {
let parsed: i64 = value_str.parse().map_err(MyError::from)?;
if parsed < 0 {
Err(MyError::NegativeValue)
} else if parsed > u8::MAX as i64 {
Err(MyError::Overflow)
} else {
Ok(parsed as u8)
}
}
fn parse_and_validate(value_str: &str) -> Result<u8, MyError>
: Takes a string slice and returns aResult<u8, MyError>
.let parsed: i64 = value_str.parse().map_err(MyError::from)?
: Tries to parse the string toi64
and maps anyParseIntError
toMyError::ParseError
.if parsed < 0
: Checks if the parsed value is negative.Err(MyError::NegativeValue)
: Returns aNegativeValue
error.
else if parsed > u8::MAX as i64
: Checks if the parsed value exceeds the maximum value foru8
.Err(MyError::Overflow)
: Returns anOverflow
error.
else
: If the value is valid, converts it tou8
and returnsOk(parsed as u8)
.
Main Function
fn main() {
let cat = Animal {
name: "Whiskers".to_string(),
age: 3,
};
let number_str = "-30";
let number = parse_and_validate(number_str);
display(Ok(cat));
display(number);
}
fn main()
: The main entry point of the program.let cat = Animal { ... }
: Creates an instance ofAnimal
.let number_str = "-30";
: A string representing a number.let number = parse_and_validate(number_str);
: Tries to parse and validate the string.display(Ok(cat));
: Displays theAnimal
instance.display(number);
: Displays the result ofparse_and_validate
.
Conclusion
This code demonstrates:
- Custom error handling using the
thiserror
crate. - Parsing and validating user input.
- Using generics and traits to handle different types and errors gracefully.
- Struct creation and usage in Rust.
By breaking down each part, we’ve shown how Rust’s powerful type system and error handling mechanisms can be used to write robust and readable code.