【问题标题】:std::error::FromError idiomatic usagestd::error::FromError 惯用用法
【发布时间】:2015-05-23 06:03:48
【问题描述】:

我正在尝试在我的项目中尽可能广泛地使用 std::error::FromError 特征,以利用 try! 宏。但是,我对不同模组之间的这些错误转换有点迷茫。

例如,我有 mod(或 crate)a,它使用自己的 Error 类型进行了一些错误处理,并为 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(())
    }
}

我也有类似情况的mod b,但是它实现了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")))
    }
}

现在我在我当前的 mod super 中,它有自己的 Error 类型,我的目标是编写这样的程序:

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

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

所以我肯定需要为我的Error 类型实现a::Errorb::Error 的转换:

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)
    }
}

好的,直到那个时候它都可以工作。现在我需要写这样的东西:

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

这里出现了一个问题,因为没有从num::ParseIntErrorError 的转换。所以看来我必须再次实施它。但我为什么要这样做?已经实现了从num::ParseIntErrorb::Error 的转换,还有从b::ErrorError 的转换。所以毫无疑问,rust 有一种干净的方法可以在没有我明确帮助的情况下将一种类型转换为另一种类型。

所以,我删除了我的 impl FromError&lt;b::Error&gt; 块并尝试了这个毯子 impl:

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)
    }
}

它甚至成功了!但是,我没有成功地用a::Error 重复这个技巧,因为 rustc 开始抱怨实现冲突:

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 }

我什至可以理解问题的根源(a::Errorb::Error 都可以实现一种类型FromError&lt;E&gt;),但我不知道如何解决它。

理论上,也许这是一种错误的方式,我的问题还有另一种解决方案吗?或者我仍然需要在每个新模块中手动重复所有错误转换?

【问题讨论】:

    标签: error-handling rust


    【解决方案1】:

    没有从num::ParseIntErrorError 的转换

    从概念上讲,您似乎做错了事。当一个库生成io::Error 时,就像您的第一个示例一样,那么应该由该库决定如何处理该错误。但是,根据您的问题,听起来您正在生成io::Errors 其他地方,然后希望将它们视为第一个库。

    这看起来很奇怪。我不希望将库 B 生成的错误交给库 A 并说“将这个错误包装起来,就好像你做了它一样”。也许您正在做的事情应该是相应库的一部分?然后它可以像往常一样处理错误。也许您可以只接受一个闭包并酌情调用错误转换。

    所以毫无疑问,Rust 有一种干净的方法可以在没有我明确帮助的情况下将一种类型转换为另一种类型

    (强调我的)。这对我来说似乎真的很可怕。隐式转换应该允许多少步?如果有多个路径,或者即使有循环怎么办?将这些作为明确的步骤对我来说似乎是合理的。

    我什至可以理解问题的根源 [...],但我不知道如何解决它。

    我认为无法解决此问题。如果您可以通过多种不同的方式为同一类型实现一个特征,那么根本无法在它们之间进行选择,因此代码是模棱两可的并被编译器拒绝。

    【讨论】:

    • “当一个库......那么它应该由那个库来决定如何处理它” - 是的,但也许将它传递给库客户端是一个好主意。例如,数据库库不能对io::Error 做任何事情,以防它无法打开文件,但库用户可以,所以它只是包装io::Error 并作为结果返回。但是这个 db 库用户可以是另一个库:例如,使用 db 作为后端的缓存。而这个缓存库本身可能会出现io::Errors 类型的错误,那么如果它要以相同的方式处理它们,为什么不将其返回给客户端呢?
    • ...或者总是将这些错误包装到在FromError impls 中传播enum 树以确切知道问题发生在哪里是个好主意?就像手动构建异常堆栈跟踪一样。
    • 对于您的数据库示例,我希望看到Error:UnableToOpenStorage(io::Error)。但是我不希望缓存库执行一些 IO 操作,然后要求数据库从结果中构造一个错误。但是您的缓存层可以重新包装(或可选地解开然后包装)错误。
    猜你喜欢
    • 2011-09-07
    • 1970-01-01
    • 2022-06-16
    • 2019-12-27
    • 2010-09-16
    • 2015-01-31
    • 1970-01-01
    • 1970-01-01
    • 2018-04-07
    相关资源
    最近更新 更多