swizard
swizard

Reputation: 2701

std::error::FromError idiomatic usage

I'm trying to involve std::error::FromError trait as widely as possible in my projects to take advantage of try! macro. However, I'm a little lost with these errors conversions between different mods.

For example, I have mod (or crate) a, which has some error handling using it's own Error type, and implements errors conversion for io::Error:

mod a {
    use std::io;
    use std::io::Write;
    use std::error::FromError;

    #[derive(Debug)]
    pub struct Error(pub String);

    impl FromError<io::Error> for Error {
        fn from_error(err: io::Error) -> Error {
            Error(format!("{}", err))
        }
    }

    pub fn func() -> Result<(), Error> {
        try!(writeln!(&mut io::stdout(), "Hello, world!"));
        Ok(())
    }
}

I also have mod b in the same situation, but it implements error conversion for num::ParseIntError:

mod b {
    use std::str::FromStr;
    use std::error::FromError;
    use std::num::ParseIntError;

    #[derive(Debug)]
    pub struct Error(pub String);

    impl FromError<ParseIntError> for Error {
        fn from_error(err: ParseIntError) -> Error {
            Error(format!("{}", err))
        }
    }

    pub fn func() -> Result<usize, Error> {
        Ok(try!(FromStr::from_str("14")))
    }
}

Now I'm in my current mod super, which has it's own Error type, and my goal is to write a procedure like this:

#[derive(Debug)]
struct Error(String);

fn func() -> Result<(), Error> {
    println!("a::func() -> {:?}", try!(a::func()));
    println!("b::func() -> {:?}", try!(b::func()));
    Ok(())
}

So I definitely need to implement conversions from a::Error and b::Error for my Error type:

impl FromError<a::Error> for Error {
    fn from_error(a::Error(contents): a::Error) -> Error {
        Error(contents)
    }
}

impl FromError<b::Error> for Error {
    fn from_error(b::Error(contents): b::Error) -> Error {
        Error(contents)
    }
}

Ok, it works up until that time. And now I need to write something like this:

fn another_func() -> Result<(), Error> {
    let _ = try!(<usize as std::str::FromStr>::from_str("14"));
    Ok(())
}

And here a problem raises, because there is no conversion from num::ParseIntError to Error. So it seems that I have to implement it again. But why should I? There is a conversion implemented already from num::ParseIntError to b::Error, and there is also a conversion from b::Error to Error. So definitely there is a clean way for rust to convert one type to another without my explicit help.

So, I removed my impl FromError<b::Error> block and tried this blanket impl instead:

impl<E> FromError<E> for Error where b::Error: FromError<E> {
    fn from_error(err: E) -> Error {
        let b::Error(contents) = <b::Error as FromError<E>>::from_error(err);
        Error(contents)
    }
}

And it's even worked! However, I didn't succeed to repeat this trick with a::Error, because rustc started to complain about conflicting implementations:

experiment.rs:57:1: 62:2 error: conflicting implementations for trait `core::error::FromError` [E0119]
experiment.rs:57 impl<E> FromError<E> for Error where a::Error: FromError<E> {
experiment.rs:58     fn from_error(err: E) -> Error {
experiment.rs:59         let a::Error(contents) = <a::Error as FromError<E>>::from_error(err);
experiment.rs:60         Error(contents)
experiment.rs:61     }
experiment.rs:62 }
experiment.rs:64:1: 69:2 note: note conflicting implementation here
experiment.rs:64 impl<E> FromError<E> for Error where b::Error: FromError<E> {
experiment.rs:65     fn from_error(err: E) -> Error {
experiment.rs:66         let b::Error(contents) = <b::Error as FromError<E>>::from_error(err);
experiment.rs:67         Error(contents)
experiment.rs:68     }
experiment.rs:69 }

I can even understand the origin of problem (one type FromError<E> can be implemented both for a::Error and b::Error), but I can't get how to fix it.

Theoretically, maybe this is a wrong way and there is another solution for my problem? Or I still have to repeat manually all errors conversions in every new module?

Upvotes: 2

Views: 1647

Answers (1)

Shepmaster
Shepmaster

Reputation: 430871

there is no conversion from num::ParseIntError to Error

It does seem like you doing the wrong thing, conceptually. When a library generates an io::Error, like your first example, then it should be up to that library to decide how to handle that error. However, from your question, it sounds like you are generating io::Errors somewhere else and then wanting to treat them as the first library would.

This seems very strange. I wouldn't expect to hand an error generated by library B to library A and say "wrap this error as if you made it". Maybe the thing you are doing should be a part of the appropriate library? Then it can handle the errors as it normally would. Perhaps you could just accept a closure and call the error-conversion as appropriate.

So definitely there is a clean way for Rust to convert one type to another without my explicit help.

(Emphasis mine). That seems really scary to me. How many steps should be allowed in an implicit conversion? What if there are multiple paths, or even if there are cycles? Having those as explicit steps seems reasonable to me.

I can even understand the origin of problem [...], but I can't get how to fix it.

I don't think it is possible to fix this. If you could implement a trait for the same type in multiple different ways, there's simply no way to pick between them, and so the code is ambiguous and rejected by the compiler.

Upvotes: 2

Related Questions