Brian Bruggeman
Brian Bruggeman

Reputation: 5324

How to implement Error with a custom struct

I was following along here.

It contains a simplified example which seemed relevant, but my implementation doesn't lend itself well. The error here is: returns a reference to data owned by the current function. The example suggests using a specific attribute of a struct, which is a String. This is fine because the compiler can know how long that struct will last. But what if we need to build the string at runtime? How do I make this work?

#[derive(Clone, Debug)]
pub enum ReadFailure {
    MissingFile(String),
    BadData(String),
    // ...
}

impl fmt::Display for ReadFailure {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ReadFailure::MissingFile(path) => write!(f, "WARNING - File not read (file does not exist): {}", path),
            ReadFailure::BadData(path) => write!(f, "WARNING - File cannot be read (file has bad data): {}", path),
            // ...
        }
    }
}

/// Implements an error for failure to read with a message
#[derive(Clone, Debug)]
pub struct ReadError {
    /// Failure condition
    error: ReadFailure,
}

impl fmt::Display for ReadError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.error)
    }
}

impl Error for ReadError {
    fn description(&self) -> &str {
        let s = format!("{}", self.error);
        &s
    }
}

Upvotes: 0

Views: 2722

Answers (1)

pretzelhammer
pretzelhammer

Reputation: 15115

But what if we need to build the string at runtime? How do I make this work?

Just build it at run-time when you create the ReadError, here's an example:

use std::fmt;
use std::error::Error;

#[derive(Clone, Debug)]
pub enum ReadFailure {
    MissingFile(String),
    BadData(String),
}

impl fmt::Display for ReadFailure {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ReadFailure::MissingFile(path) => write!(f, "WARNING - File not read (file does not exist): {}", path),
            ReadFailure::BadData(path) => write!(f, "WARNING - File cannot be read (file has bad data): {}", path),
        }
    }
}

#[derive(Clone, Debug)]
pub struct ReadError {
    error: ReadFailure,
    description: String, // NEW
}

// NEW
impl ReadError {
    fn new(error: ReadFailure) -> Self {
        ReadError {
            description: error.to_string(),
            error,
        }
    }
}

impl fmt::Display for ReadError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.error)
    }
}

impl Error for ReadError {
    fn description(&self) -> &str {
        &self.description
    }
}

fn main() {
    // example
    let error = ReadError::new(ReadFailure::MissingFile("./whatever.txt".to_string()));
    println!("{}", error.description());
}

playground

It's clear from description's function signature that it's only suppose to be a read-only method that returns a reference to some field within the error.

Anyway, as @justinas pointed out in the comments that method is deprecated and the docs recommend implementing the Display trait instead which you're already doing.

Upvotes: 1

Related Questions