我用Rust写了一个 "程序",方便地从控制台读取整数。
fn read_i32() -> Result<i32, String> {
let mut input = String::new();
match std::io::stdin().read_line(&mut input) {
Ok(_) => match input.trim_end().parse::<i32>() {
Ok(integer) => Ok(integer),
Err(_) => Err(String::from("parsing failed"))
},
Err(_) => Err(String::from("reading failed"))
}
}
fn main() {
println!("{:?}", read_i32());
}
然而,我所使用的错误处理方法显然很差(来自C++,我习惯于使用异常),并且把 String
作为 Err
我的 Result
可能只是一个黑客。我想
'\n'
;read_i32()
.如何实现呢?Result<i32, ParseIntError>
还不够通用,因为在解析之前就可能出现问题。.map()
和其他功能魔法似乎并没有用,因为它是在 read_line()
连串 .
s (在我的例子中)。
Rust中的错误处理是目前仍在发展的东西。你可能会对最近的这篇文章感兴趣 https:/nick.groenen.mepostsrust-error-handling。 以获得一些通用的、最新的建议和讨论。
根据你想保留多少关于错误的结构化信息,有几种可能的方法。在光谱的一端,你可以使用 这个错误 来构建你自己的精确错误类型。
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
#[error("Parsing failed")]
ParseError { source: std::num::ParseIntError },
#[error("Reading failed")]
ReadError { source: std::io::Error },
}
fn read_i32() -> Result<i32, MyError> {
let mut input = String::new();
match std::io::stdin().read_line(&mut input) {
Ok(_) => match input.trim_end().parse::<i32>() {
Ok(integer) => Ok(integer),
Err(e) => Err(MyError::ParseError { source: e }),
},
Err(e) => Err(MyError::ReadError { source: e }),
}
}
fn main() {
println!("{:?}", read_i32());
}
另一种方法,同样使用 thiserror
是将源错误直接嵌入到你的错误类型中,用 #[from]
. 这让你可以使用的错误类型自动转换为你的错误类型。?
运营商。
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
#[error(transparent)]
IOError(#[from] std::io::Error),
#[error(transparent)]
ParseIntError(#[from] std::num::ParseIntError),
}
fn read_i32() -> Result<i32, MyError> {
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let x = input.trim_end().parse::<i32>()?;
Ok(x)
}
fn main() {
println!("{:?}", read_i32());
}
这使得它更容易产生 MyError
s. 这里的缺点是,通过这种方式,你放弃了一些为错误类型添加上下文信息的能力;例如,如果在你的函数中,有多处地方有一个 io::Error
可能会发生,那么使用第一种方法,你的错误类型可以包括多个变体,以精确地识别它发生的位置,以及其他你可能想要添加的上下文信息(如错误发生的文件中的行号);仅仅通过底层的 io::Error
.
另一方面,如果你知道使用了 read_i32
不需要任何关于错误类型的结构化信息,你只需要生成人类可读的错误信息,那么就不需要定义自定义的错误类型,你可以使用 反正 这样的箱子,比如说。
use anyhow::{Context, Result};
fn read_i32() -> Result<i32> {
let mut input = String::new();
std::io::stdin().read_line(&mut input).context("Read failed")?;
let x = input.trim_end().parse::<i32>().context("Parse failed")?;
Ok(x)
}
fn main() {
println!("{:?}", read_i32());
}
在另一端,如果你甚至不需要那些人类可读的信息("读取失败","解析失败"),那么你也可以把所有的错误转换成一个 Box<dyn Error>
:
use std::error::Error;
fn read_i32() -> Result<i32, Box<dyn Error>> {
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let x = input.trim_end().parse::<i32>()?;
Ok(x)
}
fn main() {
println!("{:?}", read_i32());
}
随着你的程序的增长,这可能会使错误更难解释,因为这样一来,它们就不会提供很多上下文。现在有一点不方便的是,从Rust错误中获得回溯并不容易(至少在稳定的Rust中不容易)。这是目前正在研究的问题 (https:/github.comrust-langrustissues53487。).