How to handle structs with the same interface from diffrent libraries?

I've created two libraries for text parsing and they have in common the ErrorType type with the message attribute, now after importing them in main() function I'd like to render the errors from both parsers, they are actually the same but they have different types followed by module name, can I solve it in some decent way? Do I need some third library just for this one error struct and import it as dependency in both parsers or can I use some generic type in main() for both ones?

Very simplified code:

mod text_parser_1 {
  #[derive(Debug)]
  pub struct ErrorType {
    pub message: String,
  }
}
mod text_parser_2 {
  #[derive(Debug)]
  pub struct ErrorType {
    pub message: String,
  }
}
        
fn main() {
    let mut errors: Vec<text_parser_1::ErrorType> = vec![];

    let test_events = vec!["error_type_1", "error_type_2"];

    let result = test_events.iter().map(|event| match event {
        &"error_type_1" => errors.push(text_parser_1::ErrorType { message: String::from("some error 1") }),
        &"error_type_2" => errors.push(text_parser_2::ErrorType { message: String::from("some error 2") }),
    });

    rerender_errors(errors);
}

fn rerender_errors(errors: Vec<text_parser_1::ErrorType>) {
    println!("All Errors {:?}", errors);
}

Error message:

error[E0308]: mismatched types
  --> src/main.rs:21:40
   |
21 |         &"error_type_2" => errors.push(text_parser_2::ErrorType { message: String::from("some error 2") }),
   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `text_parser_1::ErrorType`, found struct `text_parser_2::ErrorType`

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=958e8ccfaee185c73e1fe053237666e5

1 Like

Either use a trait object or an enum.

Edit: For trait objects, there is std::error::Error and anyhow::Error.

1 Like

And for enums, you can check thiserror.

1 Like

How can I extract message from this heterogeneous SuperError enum, why All Errors shows empty list? BTW I seems it forces me to use fmt::Display, can I somehow reduce my code?

use thiserror::Error;
use std::fmt;

mod text_parser_1 {
  #[derive(Debug)]
  pub struct ErrorType {
    pub message: String,
  }
}
mod text_parser_2 {
  #[derive(Debug)]
  pub struct ErrorType {
    pub message: String,
  }
}

#[derive(Error, Debug)]
pub enum SuperError {
    ErrorType1(text_parser_1::ErrorType),
    ErrorType2(text_parser_2::ErrorType),
}

impl fmt::Display for SuperError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
          SuperError::ErrorType1(text_parser_1::ErrorType { ref message }) => write!(f, "SuperError is here! {}", message),
          SuperError::ErrorType2(text_parser_2::ErrorType { ref message }) => message.fmt(f),
        }
    }
}

fn main() {
    let mut errors: Vec<SuperError> = vec![];

    let test_events = vec!["error_type_1", "error_type_2"];

    let result = test_events.iter().map(|&event| match event {
        "error_type_1" => {
            errors.push(SuperError::ErrorType1(text_parser_1::ErrorType { message: String::from("some error 1")}));
            "error_type_1"
        },
        "error_type_2" => {
            errors.push(SuperError::ErrorType2(text_parser_2::ErrorType { message: String::from("some error 2")}));
            "error_type_2"
        },
        _ => "any",
    });

    println!("result {:?}", result); // to ensure both errors were matched

    rerender_errors(errors);
}

fn rerender_errors(errors: Vec<SuperError>) {
    println!("All Errors {:?}", errors);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2912a2e57e59854a24f7658766054fc8

result is a std::iter::Map, and the calls to errors.push haven't run yet because they're trapped in the map's closure. You could get it working with:

    let result = result.collect::<Vec<_>>();  // <-- runs closure
    println!("result {:?}", result);
    rerender_errors(errors);
1 Like

Apologies for so many questions but I've encountered new problems, I found that in higher level I have to match both types everywhere, would it be possible to put parser errors in some generic error where I'd be able to get message directly like error.message? Why my fmt::Display for SuperError seems unused by e.g. println!, why is it required by compiler?

use thiserror::Error;
use std::fmt;

mod text_parser_1 {
  #[derive(Debug)]
  pub struct ErrorType {
    pub message: String,
  }
}
mod text_parser_2 {
  #[derive(Debug)]
  pub struct ErrorType {
    pub message: String,
  }
}

#[derive(Error, Debug)]
pub enum SuperError {
    ErrorType1(text_parser_1::ErrorType),
    ErrorType2(text_parser_2::ErrorType),
}

impl fmt::Display for SuperError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
          SuperError::ErrorType1(text_parser_1::ErrorType { ref message }) => write!(f, "Why I'm not visible? {}", message), //why this formatter seems unused?
          SuperError::ErrorType2(text_parser_2::ErrorType { ref message }) => message.fmt(f),
        }
    }
}

fn main() {
    let mut errors: Vec<SuperError> = vec![];

    let test_events = vec!["error_type_1", "error_type_2"];

    let result: Vec<_> = test_events.iter().map(|&event| match event {
        "error_type_1" => {
            errors.push(SuperError::ErrorType1(text_parser_1::ErrorType { message: String::from("some error 1")}));
            "error_type_1"
        },
        "error_type_2" => {
            errors.push(SuperError::ErrorType2(text_parser_2::ErrorType { message: String::from("some error 2")}));
            "error_type_2"
        },
        _ => "any",
    }).collect();

    println!("result {:?}", result);

    rerender_errors(errors);
}

fn rerender_errors(errors: Vec<SuperError>) {
    for msg in &errors {
        //println!("error {:?}", msg.message); // would it be possible to get message directly form generic error?
        match msg {
            SuperError::ErrorType1(text_parser_1::ErrorType { ref message }) => println!("error 1 {:?}", message),
            SuperError::ErrorType2(text_parser_2::ErrorType { ref message }) => println!("error 2 {:?}", message),
        }
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0b05bc4368b93cb8d5c46082be9ea19c

For enums that have common fields you might want to implement a "getter" function for each one of the fields:

fn message(&self) -> &str {
    match self {
        SuperError::ErrorType1(text_parser_1::ErrorType { message }) => message,
        SuperError::ErrorType2(text_parser_2::ErrorType { message }) => message,
    }
}
1 Like

For some of these enums you can reduce the syntactical boilerplate through a macro:

macro_rules! enum_dispatch {(
    let SuperError::_ $pat:tt = $scrutinee:expr;
    $($body:tt)*
) => (
    match $scrutinee {
        | SuperError::ErrorType1 $pat => { $($body)* },
        | SuperError::ErrorType2 $pat => { $($body)* },
    }
)}

e.g.

fn message (self: &'_ Self)
  -> &'_ str
{
    enum_dispatch! {
        let SuperError::_(inner) = self;
        &inner.message
    }
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.