我正在尝试通过实现std::error::Error
创建自定义错误类型以在我的 Rust 项目中使用。我还创建了一个小快捷函数来创建std::io::Error
。不幸的是,我被困在一生中,所以我寻求一些帮助:
use std::error::Error;
use std::fmt;
use std::io;
#[derive(Debug)]
pub struct BadString<'a> {
desc: &'a str,
}
impl<'a> BadString<'a> {
pub fn new(desc: &str) -> BadString {
BadString{ desc: desc }
}
}
impl<'a> Error for BadString<'a> {
fn description(&self) -> &str { &self.desc }
}
impl<'a> fmt::Display for BadString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}
fn bad_str_err(desc: &str) -> io::Error {
let err = BadString::new(desc);
io::Error::new(io::ErrorKind::Other, err)
}
fn main() {
}
操场
这将报告错误:
<anon>:27:30: 27:34 error: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements
<anon>:27 let err = BadString::new(desc);
^~~~
<anon>:28:5: 28:46 note: first, the lifetime cannot outlive the call at 28:4...
<anon>:28 io::Error::new(io::ErrorKind::Other, err)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:28:42: 28:45 note: ...so that argument is valid for the call
<anon>:28 io::Error::new(io::ErrorKind::Other, err)
^~~
<anon>:27:30: 27:34 note: but, the lifetime must be valid for the expression at 27:29...
<anon>:27 let err = BadString::new(desc);
^~~~
<anon>:27:30: 27:34 note: ...so that auto-reference is valid at the time of borrow
<anon>:27 let err = BadString::new(desc);
^~~~
error: aborting due to previous error
playpen: application terminated with error code 101
我不确定如何解决这个问题,所以它会编译。
让我们看看io::Error::new
的签名:
fn new<E>(kind: ErrorKind, error: E) -> Error
where E: Into<Box<Error + Send + Sync>>
这说明error
可以是任何类型,只要该类型实现特征Into<Box<Error + Send + Sync>>
。该特征意味着可以将类型转换为盒装特征对象。特质对象本身必须实现特质Error
、Send
和Sync
。不明显的是,默认情况下,特征对象也具有'static
生命周期的绑定(这是有道理的,但它似乎确实会让人们感到困惑(。
让我们尝试自己进行转换:
fn bad_str_err(desc: &str) -> io::Error {
let err = BadString::new(desc);
let foo: Box<Error + Send + Sync + 'static> = err.into();
}
我们得到同样的错误——"由于需求冲突,无法推断出自动胁迫的适当生命周期"。所以我们的问题在于转换为这个特质对象的能力。
Send
和Sync
是两个关键特征,有助于指导编译器了解哪些类型可以安全地在线程之间发送/共享。为了在线程之间安全地共享某些内容,当另一个线程拥有它时,它不能"消失"。这是 Rust 在编译时帮助防止的一种错误。
在这种情况下,您绑定使用字符串切片(&str
(,但该切片不拥有底层内存,它只是引用它。一旦该内存超出范围,任何引用都需要变得无效。这是 Rust 在编译时阻止的另一件事。
在这种情况下,最简单的做法是不使用引用:
use std::error::Error;
use std::{fmt, io};
#[derive(Debug)]
pub struct BadString {
desc: String,
}
impl BadString {
pub fn new(desc: &str) -> BadString {
BadString { desc: desc.into() }
}
}
impl Error for BadString {
fn description(&self) -> &str { &self.desc }
}
impl fmt::Display for BadString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}
fn bad_str_err(desc: &str) -> io::Error {
let err = BadString::new(desc);
io::Error::new(io::ErrorKind::Other, err)
}
fn main() {}
String
拥有底层内存,因此可以安全地跨线程边界传输,无需担心意外释放任何其他对象。