如何在Rust中包含文件路径以显示IO错误?

6
在这个极简程序中,我希望file_size函数在Err中包含路径/not/there,以便在main函数中显示。
use std::fs::metadata;
use std::io;
use std::path::Path;
use std::path::PathBuf;

fn file_size(path: &Path) -> io::Result<u64> {
    Ok(metadata(path)?.len())
}

fn main() {
    if let Err(err) = file_size(&PathBuf::from("/not/there")) {
        eprintln!("{}", err);
    }
}

并不是所有的IO错误都与路径相关(甚至可能根本与文件系统无关!) - Shepmaster
2个回答

5

为了包装这些额外的数据,你必须定义自己的错误类型。

个人而言,我喜欢使用custom_error crate来处理多种类型的情况,因为它特别方便。在你的情况下,代码可能如下所示:

use custom_error::custom_error;
use std::fs::metadata;
use std::io;
use std::path::{Path, PathBuf};
use std::result::Result;

custom_error! {ProgramError
    Io {
        source: io::Error,
        path: PathBuf
    } = @{format!("{path}: {source}", source=source, path=path.display())},
}

fn file_size(path: &Path) -> Result<u64, ProgramError> {
    metadata(path)
        .map(|md| md.len())
        .map_err(|e| ProgramError::Io {
            source: e,
            path: path.to_path_buf(),
        })
}

fn main() {
    if let Err(err) = file_size(&PathBuf::from("/not/there")) {
        eprintln!("{}", err);
    }
}

输出:

/not/there: No such file or directory (os error 2)

看起来很不错 - 我只需要将“IO错误”更改为“{path}:{source}”,以便在使用eprintln!("{}", err);时获得更用户友好的消息。 我必须说,我希望有些更简单/ less verbose 的东西,特别是将IO错误转换为自定义错误。 - Gaëtan Lehmann
这是更简洁的版本。尝试不使用像custom_error或failure这样的crate。请注意,当您不添加数据而只是包装源错误时,custom_error使处理多个错误类型变得更容易:https://docs.rs/custom_error/latest/custom_error/macro.custom_error.html#automatic-conversion-from-other-error-types - Denys Séguret
我认为我会将路径存储为 PathBuf,而不是急切地执行有损转换。 - Shepmaster
1
在错误中使用 PathBuf 会更加麻烦,因为 custom_error! 宏生成的一些代码尝试在路径上使用 to_string() 方法,但该方法在此处不可用。 - Gaëtan Lehmann
1
更新后的代码在此Gist中:https://gist.github.com/glehmann/72e0acd5c176e6a3d532433b0d671e61 - Gaëtan Lehmann
显示剩余7条评论

3

虽然Denys Séguret的回答是正确的,但我喜欢使用我的crate SNAFU,因为它提供了一个context的概念。这使得附加路径(或其他任何东西!)变得非常容易:

use snafu::{ResultExt, Snafu}; // 0.2.3
use std::{
    fs, io,
    path::{Path, PathBuf},
};

#[derive(Debug, Snafu)]
enum ProgramError {
    #[snafu(display("Could not get metadata for {}: {}", path.display(), source))]
    Metadata { source: io::Error, path: PathBuf },
}

fn file_size(path: impl AsRef<Path>) -> Result<u64, ProgramError> {
    let path = path.as_ref();
    let md = fs::metadata(&path).context(Metadata { path })?;
    Ok(md.len())
}

fn main() {
    if let Err(err) = file_size("/not/there") {
        eprintln!("{}", err);
    }
}

这也是一个不错的方法,它更接近我提出的 with_path 方法。我会尝试一下! - Gaëtan Lehmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接