genericsrusterror-handlingunion-types

How to return multiple generic error types in Rust?


I am trying to return errors of multiple types from a method, one error being generic.

I'd like not to use the Box<dyn Error> trick, mostly to learn how to achieve what I want to implement here.

The get function retrieves a value from the DB (which can fail with a rusqlite::Error), and then parses the value with from_str, which is defined on the generic return type of get.

enum GetError<T: std::str::FromStr> {
  Error1(T::Err),
  Error2(rusqlite::Error)
}

impl<T: std::str::FromStr> From<T::Err> for GetError<T> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  fn from(value: T) -> Self {
    GetError::Error1(value)
  }
}

impl<T: std::str::FromStr> From<rusqlite::Error> for GetError<T> {
  fn from(value: rusqlite::Error) -> Self {
    GetError::Error2(value)
  }
}

pub fn get<T>(&self, key: &str) -> Result<T, GetError<T>>
  where
    T: std::str::FromStr,
    {
      match self.connection.query_row("SELECT value FROM key_values WHERE key = ?1", params![key], |row| { row.get::<usize, String>(0) }) {
        Err(error) => Err(GetError::Error2(error)),
        Ok(value) => T::from_str(&value).or_else(|e| GetError::Error1(e))
    }
}

It's telling me that there is already an implementation of the trait From defined in std::convert.

error[E0119]: conflicting implementations of trait `std::convert::From<storage::permanent_key_value_store::GetError<_>>` for type `storage::permanent_key_value_store::GetError<_>`
  --> src/storage/permanent_key_value_store.rs:13:1
   |
13 | impl<T: std::str::FromStr> From<T::Err> for GetError<T> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `core`:
           - impl<T> std::convert::From<T> for T;

Is there a way to return a generic error GetError that could stand for a rusqlite::Error or any FromStr::Err?


Thank you so much!

Now the code boils down to (using ?):

pub enum GetError<T: std::str::FromStr> {
  Error1(T::Err),
  Error2(rusqlite::Error)
}

impl<T: std::str::FromStr> From<rusqlite::Error> for GetError<T> {
  fn from(value: rusqlite::Error) -> Self {
    GetError::Error2(value)
  }
}

pub fn get<T>(&self, key: &str) -> Result<T, GetError<T>>
  where
    T: std::str::FromStr,
    {
      let value = self.connection.query_row(
        "SELECT value FROM key_values WHERE key = ?1", 
        params![key], 
        |row| { row.get::<usize, String>(0) }
      )?;
      T::from_str(&value).map_err(GetError::Error1)
    }

Solution

  • The problem is that one can put whatever they want in their FromStr's associated type Err, for example:

    struct Foo;
    impl std::str::FromStr for Foo {
        type Err = GetError<Foo>;
        
        // ...
    }
    

    And then the From impl you wrote for GetError<Foo> will collide with the empty, provided-by-std impl<T> From<T> for T impl.

    You can avoid that by getting rid of this From impl and constructing the error manually:

    enum GetError<T: std::str::FromStr> {
        Error1(T::Err),
        Error2(rusqlite::Error),
    }
    
    impl<T: std::str::FromStr> From<rusqlite::Error> for GetError<T> {
        fn from(value: rusqlite::Error) -> Self {
            GetError::Error2(value)
        }
    }
    
    pub fn get<T>(&self, key: &str) -> Result<T, GetError<T>>
    where
        T: std::str::FromStr,
    {
        match self.connection.query_row(
            "SELECT value FROM key_values WHERE key = ?1",
            params![key],
            |row| row.get::<usize, String>(0),
        ) {
            Err(error) => Err(GetError::Error2(error)),
            Ok(value) => T::from_str(&value).map_err(|e| GetError::Error1(e)),
        }
    }