Convert any error into ()?

I am trying to wrap my head around error handling.

I have the following code:

async fn read_file_content(path: &str) -> Result<String, Error> {
    let raw = tokio::fs::read(path).await?;
    let content = String::from_utf8(raw)?;
    Ok(content)
}

struct Error {}

impl From<std::string::FromUtf8Error> for Error {
    fn from(_: std::string::FromUtf8Error) -> Error {
        Error {}
    }
}

impl From<std::io::Error> for Error {
    fn from(_: std::io::Error) -> Error {
        Error {}
    }
}

The idea being that I do not care to know what failed in read_file_content, I will only handle the case where I get an error, no matter what it is.

My hope was to return Result<String, ()>, and use the ? operator to neatly propagate the error up and hope that it would be correctly converted. It is not converted, hence why you can see the empty struct with the implementations for each error that I am expecting.

I tried writing:

impl<T> From<T> for Error {
    fn from(_: T) -> Error {
        Error {}
    }
}
conflicting implementations of trait `std::convert::From<Error>` for type `Error`:

note: conflicting implementation in crate `core`:
      - impl<T> std::convert::From<T> for T; // rustc(E0119)

Another attempt:

impl<T> From<T> for () {
    fn from(_: T) -> () {
        ()
    }
}
conflicting implementations of trait `std::convert::From<()>` for type `()`:

note: conflicting implementation in crate `core`:
      - impl<T> std::convert::From<T> for T; // rustc(E0119)

It looks like a simple enough use case, and I am kind of surprised to be stuck on this? Am I doing something wrong here?

If you have no need for an error, you could just return an Option.

async fn read_file_content(path: &str) -> Option<String> {
    let raw = tokio::fs::read(path).await.ok()?;
    let content = String::from_utf8(raw).ok()?;
    Some(content)
}

But that might suck in regards to semantics.


you can always convert it by hand:

async fn read_file_content(path: &str) -> Result<String, ()> {
    let raw = tokio::fs::read(path).await.map_err(|_| ())?;
    let content = String::from_utf8(raw).map_err(|_| ())?;
    Ok(content)
}
1 Like

Since From already has one implementation that covers every single type, it's not possible to provide a completely generic implementation without conflicting with that existing one. However, you can restrict your implementation to types that implement the error::Error trait:

struct Error;

impl<T: std::error::Error> From<T> for Error {
    fn from(_: T) -> Self {
        Error
    }
}

Or you could use the type Box<dyn Error>, which already has this generic From implementation, plus a few other useful ones:

type Error = Box<dyn std::error::Error + Send + Sync>;
2 Likes

To be fair using Option is the simpliest way to write and express what I wanted, thanks!

I toyed with:

async fn read_file_content(path: &str) -> Result<String, ()> {
    let raw = tokio::fs::read(path).await.or(Err(()))?;
    let content = String::from_utf8(raw).or(Err(()))?;
    Ok(content)
}

But it is plenty ugly... Really wished something more clean was possible in regards to Result.

I was using Result<String, Box<std::string::Error>> as a return before, but since I really didn't handle this error value I hoped I could get away from moving something onto the heap that I won't even look at...

Thanks for the answers!

Consider also anyhow.

2 Likes

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.