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,
}
}
}
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:?}"),
_ => {}
}