采用这种样式Result
是否可以?
fn a() -> Result<u32, &'static str>
然后,错误特征的目的是什么? https://doc.rust-lang.org/std/error/trait.Error.html
隐式错误结果是更好的做法吗?
impl Error for MyError {..... }
fn a() -> Result<u32, MyError>
答案 0 :(得分:4)
对于简单的用例,像Result<u32, &'static str>
或Result<u32, String>
这样的不透明错误类型就足够了,但是对于更复杂的库,它很有用,甚至鼓励您创建自己的错误类型,例如{{1 }}或struct MyError
,可帮助您更好地定义意图。您可能还需要阅读enum AnotherLibError
书的Error Handling
一章。
Rust by Example
特性是Error
的一部分,可帮助开发人员以通用且集中的方式定义自己的错误类型,以描述发生的情况和可能的根本原因(回溯)。目前受到一定限制,但有计划帮助improve its usability。
使用std
时,您是在告诉编译器您不关心要返回的类型,只要它实现了impl Error
特性。当错误类型太复杂或要归纳返回类型时,此方法很有用。例如:
Error
方法fn example() -> Result<Duration, impl Error> {
let sys_time = SystemTime::now();
sleep(Duration::from_secs(1));
let new_sys_time = SystemTime::now();
sys_time.duration_since(new_sys_time)
}
返回一个duration_since
类型,但是在上面的方法签名中,您可以看到对于Result的Err
部分,它正在返回实现{{ 1}}特征。
总结所有内容,如果您阅读Rust书籍并知道自己在做什么,则可以选择最适合您需求的方法。否则,最好为错误定义自己的类型,或使用Result<Duration, SystemTimeError>
或Error
条板箱之类的第三方实用程序。
答案 1 :(得分:3)
简而言之:不,这不好。字符串为错误会丢弃有关详细信息和原因的信息,使该错误对调用方无用,因为它无法检查错误并可能从错误中恢复。
如果只需要在Error参数中填充一些内容,请创建一个单位结构。它不是很有用,但也不像字符串那么易变。并且您可以轻松地区分foo::SomeError
与bar::SomeError
。
#[derive(Debug)]
pub struct SomeError; // No fields.
如果可以枚举错误变量,请使用enum
。
有时将其他错误“包括”到其中也很有用。
#[derive(Debug)]
pub enum PasswordError {
Empty,
ToShort,
NoDigits,
NoLetters,
NoSpecials
}
#[derive(Debug)]
pub enum ConfigLoadError {
InvalidValues,
DeserializationError(serde::de::Error),
IoError(std::io::Error),
}
没有人阻止您使用struct
。
当您有意向调用者隐藏一些信息时,它们特别有用(与enum
的变体始终具有公共可见性相比)。例如。呼叫者与错误消息无关,但可以使用kind
来处理它:
pub enum RegistrationErrorKind {
InvalidName { wrong_char_idx: usize },
NonUniqueName,
WeakPassword,
DatabaseError(db::Error),
}
#[derive(Debug)]
pub struct RegistrationError {
message: String, // Private field
pub kind: RegistrationErrorKind, // Public field
}
impl错误-存在类型-在这里没有意义。如果这是您的意图,则不能在错误位置返回其他错误类型。而且不透明的错误像字符串一样用处不大。
std::error::Error
特性可确保您的SomeError
类型具有std::fmt::{Display, Debug}
的实现(用于向用户和开发人员显示错误,并提供source
这样的有用方法(此返回此错误的原因); is
,downcast
,downcast_ref
,downcast_mut
。最后4个用于错误类型擦除。
错误类型清除
错误类型擦除有其折衷,但也值得一提。
在编写一些高级应用程序代码时,它也特别有用。但是对于库,在决定使用此方法之前,您应该三思而后行,因为这会使您的库无法使用'no_std'。
假设您具有一些具有非平凡逻辑的函数,这些函数可以返回某些错误类型的值,而不仅仅是错误类型。在这种情况下,您可以使用(但不要滥用)错误类型擦除:
use std::error::Error;
use std::fmt;
use std::fs;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::net::Ipv4Addr
use std::path::Path;
// Error for case where file contains '127.0.0.1'
#[derive(Debug)]
pub struct AddressIsLocalhostError;
// Display implementation is required for std::error::Error.
impl fmt::Display for AddressIsLocalhostError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Address is localhost")
}
}
impl Error for AddresIsLocalhostError {} // Defaults are okay here.
// Now we have a function that takes a path and returns
// non-localhost Ipv4Addr on success.
// On fail it can return either of IoError, AddrParseError or AddressIsLocalhostError.
fn non_localhost_ipv4_from_file(path: &Path) -> Result<Ipv4Addr, Box<dyn Error + 'static>> {
// Opening and reading file may cause IoError.
// ? operator will automatically convert it to Box<dyn Error + 'static>.
// (via From trait implementation)
// This way concrete type of error is "erased": we don't know what's
// in a box, in fact it's kind of black box now, but we still can call
// methods that Error trait provides.
let content = fs::read_to_string(path)?;
// Parsing Ipv4Addr from string [slice]
// may cause another error: AddressParseError.
// And ? will convert it to to the same type: Box<dyn Error + 'static>
let addr: Ipv4Addr = content.parse()?;
if addr == Ipv4Add::new(127, 0, 0, 1) {
// Here we perform manual conversion
// from AddressIsLocalhostError
// to Box<dyn Error + 'static> and return error.
return Err(AddressIsLocalhostError.into());
}
// Everyhing is okay, returning addr.
Ok(Ipv4Addr)
}
fn main() {
// Let's try to use our function.
let maybe_address = non_localhost_ipv4_from_file(
"sure_it_contains_localhost.conf"
);
// Let's see what kind of magic Error trait provides!
match maybe_address {
// Print address on success.
Ok(addr) => println!("File was containing address: {}", addr),
Err(err) => {
// We sure can just print this error with.
// println!("{}", err.as_ref());
// Because Error implementation implies Display implementation.
// But let's imagine we want to inspect error.
// Here deref coercion implicitly converts
// `&Box<dyn Error>` to `&dyn Error`.
// And downcast_ref tries to convert this &dyn Error
// back to &IoError, returning either
// Some(&IoError) or none
if Some(err) = err.downcast_ref::<IoError>() {
println!("Unfortunately, IO error occured: {}", err)
}
// There's also downcast_mut, which does the same, but gives us
// mutable reference.
if Some(mut err) = err.downcast_mut::<AddressParseError>() {
// Here we can mutate err. But we'll only print it.
println!(
"Unfortunately, what file was cantaining, \
was not in fact an ipv4 address: {}",
err
);
}
// Finally there's 'is' and 'downcast'.
// 'is' comapres "erased" type with some concrete type.
if err.is::<AddressIsLocalhostError>() {
// 'downcast' tries to convert Box<dyn Error + 'static>
// to box with value of some concrete type.
// Here - to Box<AddressIsLocalhostError>.
let err: Box<AddressIsLocalhostError> =
Error::downcast(err).unwrap();
}
}
};
}
总结一下:错误(应该说-必须)除了可以显示错误之外,还应该为调用者提供有用的信息,因此错误不应该是字符串。错误必须至少实现Error,才能在所有包装箱中保留更少的一致错误处理经验。其余的一切都取决于情况。
Caio alredy提到了The Rust Book。
但是这些链接也可能有用: