Search code examples
rust

How can I typecheck custom Error type in rust?


I have declared a custom error type that contains a string and recently added a "kind" attribute. The error is returned as "boxed". I am struggling to understand how a caller can determine if the returned error is of my custom type (ArchiverError) and if so inspect the "kind" of error.

#[derive(Debug)]
struct ArchiverError {
    details: String,
    kind: ErrorKind
}

impl ArchiverError {
    fn new(foo: ErrorKind, msg: &str) -> ArchiverError {
        ArchiverError{details: msg.to_string(), kind: foo}
    }
    fn kind(&self) -> ErrorKind {
        self.kind
    }
}

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

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

type Result2<T> = std::result::Result<T, Box<dyn std::error::Error>>;

I return the error like this:

    pub fn extract_packet(buffer: &Vec<u8>, offset: &mut usize) -> Result2<RdtpPacketV1> {

    //let frame_delimeter = buffer_to_u32(&buffer[*offset]);
    let mut bytes: &[u8] = vec_to_bytes(buffer, *offset, 4);
    if bytes.len() == 0 {
        return Err(Box::new(ArchiverError::new(ErrorKind::Other,"Error: Unable to extract packet. Buffer Underrun (Frame Delimiter)")));
    }

And when I try to evaluate the returned error as an ArchiverError, it doesn't match:

                    match extract_packet(&byte_array, &mut offset) {
                    Ok(packet) => {
                        let packetStartTime = Timestamp::new(packet.get_timestamp().unwrap()).to_utc_datetime();
                        let packetEndTime = Timestamp::new(packet.get_end_timestamp().unwrap()).to_utc_datetime();

                        if (packetEndTime >= start && packetStartTime < end) {
                           // log(session_id, format!("Packet included. Start: {} End: {}, Window Start: {}, Window End: {}",
                               // packetStartTime, packetEndTime, start, end ));
                            packets.push(packet);
                        }
                    }
                    Err(error) => {
                        match error.downcast_ref::<ArchiverError>() {
                            Some(ae) => {
                                match ae.kind() {
                                    // Other is generic unrecoverable error.
                                    // InvalidData indicates recoverable - continue trying
                                    ErrorKind::Other => {
                                        // Break out of the inner while loop and continue to the next file
                                        break;
                                    },
                                    ErrorKind::InvalidData => {
                                        // Allow it to continue
                                    },
                                    other_error => {
                                        break;
                                    }
                                }
                            },
                            None => break,
                        }

                    }
                }

Solution

  • Reviewing your case and the minimal reproducible I can tell you that the error is in your Result2, because you are expecting a dynamic type when you should have it strictly typed, it is fixed in the following way

    type Result2<T> = std::result::Result<T, ArchiverError>;
    

    and it is used in the following way:

    match extract_packet(&Vec::new(), &mut 0) {
        Ok(_packet) => {}
        Err(e) => {
            println!("found archive error: {:?}", e.kind);
        },
    }
    // or
    match extract_packet(&Vec::new(), &mut 0) {
        Ok(_packet) => {}
        // we use the destructuring
        Err(ArchiverError { kind, .. }) => {
            println!("found archive error: {kind:?}");
        },
    }
    

    more details about pattern matching

    attached link to the modified playground

    Now, surely you want to make your errors more dynamic and you can have variants between the types of errors, maybe in that case you should explore thiserror, basically you could have an enum as error and with the pattern matching go comparing the types of errors, that is the way in which you usually work the errors, you could have an enum as error and with the pattern matching go comparing the types of errors, that is the way in which you usually work the errors, you could have an enum as error

    use thiserror::Error;
    
    #[derive(Error, Debug)]
    pub enum DataStoreError {
        #[error("data store disconnected")]
        Disconnect(#[from] io::Error),
        #[error("the data for key `{0}` is not available")]
        Redaction(String),
        #[error("invalid header (expected {expected:?}, found {found:?})")]
        InvalidHeader {
            expected: String,
            found: String,
        },
        #[error("unknown data store error")]
        Unknown,
    }
    

    And use:

    match something() {
      Err(DataStoreError::Unknown) => println!("Unknown Error"),
      Err(DataStoreError::Redaction(details)) => println!("Redaction Error: {details}"),
      Err(e) => println!("{e:?}"),
      _ => {}
    }