我正在编写一个将在无限循环中调用的函数,并且仅在从 Web 服务获取格式正确的数据时才执行某些操作。如果服务关闭,返回非 json,或者返回我们不理解的 json,该函数应该只记录错误并返回(在暂停后再次调用)。
我发现自己复制和粘贴这样的东西:
let v = match v {
Ok(data) => data,
Err(error) => {
println!("Error decoding json: {:?}", error);
return;
}
};
Run Code Online (Sandbox Code Playgroud)
错误匹配器的主体每次都会不同。有时它是恐慌的,有时它有不同的消息,有时error可以进一步分解 的元素以形成更好的消息,但结构的其余部分将是相同的。
这个有简写吗?我知道? 语法,但那是为了传播。当您需要稍微不同的处理以防出现上述情况中的错误时,我认为传播不会对这种情况有所帮助。这是因为处理方面的特殊差异属于这里,而不是堆栈。
我还没有用 Rust 写过很多代码,所以很可能我遗漏了一些明显的东西。
在 C# 中,上面的代码看起来像这样:
let v = match v {
Ok(data) => data,
Err(error) => {
println!("Error decoding json: {:?}", error);
return;
}
};
Run Code Online (Sandbox Code Playgroud)
或者
if (error != null)
{
Console.WriteLine($"Error decoding json: {error}");
return;
}
Run Code Online (Sandbox Code Playgroud)
两者都比 Rust 少得多。
如果我理解下面的评论,缩短的一种方法是这样的:
if let Err(error) = v {
println!("Error decoding json: {:?}", error);
return;
}
let v = v.unwrap();
Run Code Online (Sandbox Code Playgroud)
这看起来更紧凑,谢谢。这是惯用语吗?你会这样写吗?
作为自定义的替代方案,macro_rule您还可以使用?withOption<T>和 trait 扩展Result来打印错误并转换成功的值。
pub trait ResultOkPrintErrExt<T> {
fn ok_or_print_err(self, msg: &str) -> Option<T>;
}
impl<T, E> ResultOkPrintErrExt<T> for Result<T, E>
where
E: ::std::fmt::Debug,
{
fn ok_or_print_err(self, msg: &str) -> Option<T> {
match self {
Ok(v) => Some(v),
Err(e) => {
eprintln!("{}: {:?}", msg, e);
None
}
}
}
}
fn read_input() -> Result<u32, ()> {
// Ok(5)
Err(())
}
fn run() -> Option<()> {
let v: u32 = read_input().ok_or_print_err("invalid input")?;
println!("got input: {}", v);
Some(())
}
fn main() {
run();
}
Run Code Online (Sandbox Code Playgroud)
当您需要稍微不同的处理以防出现上述情况中的错误时,我认为传播不会对这种情况有所帮助。这是因为处理方面的特殊差异属于这里,而不是堆栈。
这是自定义错误类型可以帮助解决的问题。在这种情况下,您有一个共同的行为(“记录错误”),并且您希望针对不同的值以稍微不同的方式执行此操作。将“记录错误”部分移至调用方是有意义的(让我们调用函数try_poll):
loop {
if let Err(e) = try_poll() {
println!("{}", e);
}
sleep(100);
}
Run Code Online (Sandbox Code Playgroud)
并创建一个类实现Display,并From<E>为每个错误类型E:
enum PollError {
NetworkError(NetworkError),
JsonParseError(JsonParseError),
}
impl fmt::Display for PollError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PollError::NetworkError(ref e) => write!(f, "Error downloading file: {:?}", e),
PollError::JsonParseError(ref e) => write!(f, "Error parsing JSON: {:?}", e),
}
}
}
impl From<NetworkError> for PollError {
fn from(e: NetworkError) -> Self {
PollError::NetworkError(e)
}
}
impl From<JsonParseError> for PollError {
fn from(e: JsonParseError) -> Self {
PollError::JsonParseError(e)
}
}
Run Code Online (Sandbox Code Playgroud)
现在您可以使用?来传播错误,但调用者仍然不必关心具体是哪个错误。
fn try_poll() -> Result<(), PollError> {
let data = try_fetch_content()?;
let json = try_parse_json(data)?;
println!("Parsed {:?}", json);
Ok(())
}
Run Code Online (Sandbox Code Playgroud)
(操场)
From实现。关于这个的乏味部分是所有impl Froms,由于自定义错误类型,这些都是必需的。如果对错误所做的唯一处理是记录并忽略它,那么自定义错误类型并不是特别有用——唯一真正需要返回的是错误消息本身。
在这种情况下,使用try_pollreturnResult<(), String>和 useResult::map_err将每个单独的错误立即转换为错误消息,然后再?用于传播它:
fn try_poll() -> Result<(), String> {
let data = try_fetch_content()
.map_err(|e| format!("Error downloading file: {:?}", e))?;
let json = try_parse_json(data)
.map_err(|e| format!("Error parsing JSON: {:?}", e))?;
println!("Parsed {:?}", json);
Ok(())
}
Run Code Online (Sandbox Code Playgroud)
(操场)
The Rust Programming Language 的第一版有这样的说法String作为错误类型:
经验法则是定义您自己的错误类型,但
String错误类型会在紧要关头,特别是在您编写应用程序时。如果您正在编写库,则强烈建议您定义自己的错误类型,以免不必要地从调用者中删除选项。