How to convert &str to Error in fn map_err

I often use two ways as following to return error

#[derive(Debug)]
struct UserErr(String);
impl UserErr {
  pub fn new(msg: &str)-> Self {
     Self(msg.to_owned())
  }
}
impl std::fmt::Display for UserErr {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>)-> Result<(), std::fmt::Error> {
     write!(f, "{}", self.0)
  }
}
impl std::error::Error for UserErr {}

fn foo()-> Result<std::fs::File, Box<dyn std::error::Error>> {

  // the first method:
  let _f = std::fs::File::open("").map_err(|e| UserErr::new(&format!("User error, and original error: {}", e.to_string())))?;
 
 // the second method
 let f = match std::fs::File::open("") {
     Ok(f)=> f,
     Err(e)=> Err(format!("User error, and original error: {}", e.to_string()))?,
  };

  Ok(f)
}

Here is playground

With the first method, I have to define a struct UserErr; And with the second method, it will unwrap f and wrap f again.
I wonder,

  1. in the second method, it looks like String format!("User error, and original error: {}", e.to_string()) was auto casted some kind of Error, so which type of Error (a struct that impl trait std::error::Error) this string auto casted ?
  2. How can I use map_err to map error to a customed string without define a new type(struct UserErr in this example)?

Any help would be appreciated, thanks.

For your first question, well, it works because of the following impl in the standard library:

#[stable(feature = "rust1", since = "1.0.0")]
impl From<String> for Box<dyn Error + Send + Sync> {
    /// Converts a [`String`] into a box of dyn [`Error`] + [`Send`] + [`Sync`].
    ///
    /// # Examples
    ///
    /// ```
    /// use std::error::Error;
    /// use std::mem;
    ///
    /// let a_string_error = "a string error".to_string();
    /// let a_boxed_error = Box::<dyn Error + Send + Sync>::from(a_string_error);
    /// assert!(
    ///     mem::size_of::<Box<dyn Error + Send + Sync>>() == mem::size_of_val(&a_boxed_error))
    /// ```
    #[inline]
    fn from(err: String) -> Box<dyn Error + Send + Sync> {
        struct StringError(String);

        impl Error for StringError {
            #[allow(deprecated)]
            fn description(&self) -> &str {
                &self.0
            }
        }

        impl Display for StringError {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                Display::fmt(&self.0, f)
            }
        }

        // Purposefully skip printing "StringError(..)"
        impl Debug for StringError {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                Debug::fmt(&self.0, f)
            }
        }

        Box::new(StringError(err))
    }
}

If you want to access this via map_err, then you can try calling .into() on your string. This will work if the compiler can infer the target error type.

2 Likes

Cool, it works! Thanks.

In this case, the compiler cannot infer the target error type. Is there a good way to implement it without defining a new error?
The code snippet is as following,

let hmap = HashMap::from([("hello", "world")]);
let _ = hmap.get("he").ok_or(format!("error").into())?;

Here is playground

The compiler complains,

26 |     let _ = hmap.get("he").ok_or(format!("error").into())?;
   |                            ^^^^^ ----------------------- this method call resolves to `T`
   |                            |
   |                            cannot infer type for type parameter `E` declared on the associated function `ok_or`

error[E0283]: type annotations needed

Expect further answer. Thanks

The question mark operator inserts a call to into of itself. Since you now have a chain of two into calls, it can't figure out what the intermediate type should be.

Remove the into call and let the question mark do it for you.

3 Likes

It works :+1: thanks again.

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.