[HowTo] 在 error 中添加位置信息
在 rust 里可以使用 ?
提早返回, 比如下面下面这段代码, 如果 name 中 age 有任意一个为空, 就提前返回.
fn print_info(name: Option<String>, age: Option<u8>) -> Result<(), ()> { let name = name.ok_or(())?; let age = age.ok_or(())?; println!("name: {} age: {}", name, age); Ok(()) }
但当我们要确定 print_info
是从哪里返回时, 只能阅读代码, 并确认 name 和 age 哪个为空, 如果函数行数很多或逻辑复杂, 甚至无法确定 name 和 age 值时, 这种方法不是很有用.
一种解决办法是在错误类型里添加位置信息, 在每次调用 ?
时获取调用者, 即外层函数调用?
那一行的位置信息.
rust 有 line!
, column!
和 file!
宏可以获取宏调用时的位置信息, 但如果我们这样写永远只能取到 From
trait 实现那行的信息
#[derive(Debug, Clone, Copy)] pub struct LocatedError<T> { pub file: &'static str, pub line: u32, pub column: u32, pub error: T, } impl<T> From<T> for LocatedError<T> { fn from(value: T) -> Self { Self { file: file!(), line: line!(), column: column!(), error: value, } } } fn print_info_located(name: Option<String>, age: Option<u8>) -> Result<(), LocatedError<()>> { let name = name.ok_or(())?; let age = age.ok_or(())?; println!("name: {} age: {}", name, age); Ok(()) } dbg!(print_info_located(None, None)).ok(); dbg!(print_info_located(Some("PrivateRookie".into()), None)).ok(); dbg!(print_info_located(Some("PrivateRookie".into()), Some(15))).ok();
注意看第一和第二个调用case的返回的错误中位置信息并没有改变
[examples/r.rs:3] print_info_located(None, None) = Err( LocatedError { file: "examples/r.rs", line: 57, column: 21, error: (), }, ) [examples/r.rs:4] print_info_located(Some("PrivateRookie".into()), None) = Err( LocatedError { file: "examples/r.rs", line: 57, column: 21, error: (), }, ) name: PrivateRookie age: 15 [examples/r.rs:5] print_info_located(Some("PrivateRookie".into()), Some(15)) = Ok( (),
显然这并不符合我们要求.
在 rust 文档里仔细翻找和搜索后, 找到stack overflow上的一个答案. 使用 track_caller
(很遗憾rust文档中没有这个宏的文档)和 std::panic::Location 可以实现上面的需求.
#[derive(Debug, Clone, Copy)] pub struct LocatedError<T> { pub location: Location<'static>, pub error: T, } impl<T> From<T> for LocatedError<T> { #[track_caller] // 注意这里添加了 track_caller 宏 fn from(value: T) -> Self { Self { location: *Location::caller(), error: value, } } } fn print_info(name: Option<String>, age: Option<u8>) -> Result<(), LocatedError<()>> { let name = name.ok_or(())?; let age = age.ok_or(())?; println!("name: {} age: {}", name, age); Ok(()) }
这里最大改变在于给 from
添加了 track_caller
, 使用之前的测试代码可以发现错误里携带的正是 print_info ?
对应的行和列.