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 the Debug and Error traits for the MyError enum.
  • pub enum MyError: Defines a public enum MyError which represents different kinds of errors.
    • ParseError(#[from] ParseIntError): Wraps a ParseIntError 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 for u8.

Defining a Struct

#[derive(Debug)]
struct Animal {
    name: String,
    age: u8,
}
  • #[derive(Debug)]: Derives the Debug trait for the Animal struct, allowing it to be formatted using {:?}.
  • struct Animal: Defines a struct Animal with two fields:
    • name: A String representing the animal’s name.
    • age: A u8 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 a Result<T, MyError>.
  • match value: Matches on the result:
    • Ok(val): If the result is Ok, prints the value.
    • Err(e): If the result is Err, 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 a Result<u8, MyError>.
  • let parsed: i64 = value_str.parse().map_err(MyError::from)?: Tries to parse the string to i64 and maps any ParseIntError to MyError::ParseError.
  • if parsed < 0: Checks if the parsed value is negative.
    • Err(MyError::NegativeValue): Returns a NegativeValue error.
  • else if parsed > u8::MAX as i64: Checks if the parsed value exceeds the maximum value for u8.
    • Err(MyError::Overflow): Returns an Overflow error.
  • else: If the value is valid, converts it to u8 and returns Ok(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 of Animal.
  • 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 the Animal instance.
  • display(number);: Displays the result of parse_and_validate.

Conclusion

This code demonstrates:

  1. Custom error handling using the thiserror crate.
  2. Parsing and validating user input.
  3. Using generics and traits to handle different types and errors gracefully.
  4. 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.

Webdock – Fast Cloud VPS Linux Hosting