Return 错误并在 clap cli 中正常退出

Return an error and exit gracefully in clap cli

我想实现一个简单的 clap cli,它在 git 存储库上运行,但这对问题并不重要;我相信这将有助于澄清。如果不是 运行 从存储库的根目录,我试图确定最惯用的退出错误的方法。这里有三个选项;我不确定哪个好。

执行这些步骤的最佳方法是什么:

  1. 检查我是否从 repo root
  2. 运行
  3. 如果是继续,如果不是退出
  4. 如果没有给出命令,生成帮助
  5. 如果给出命令,运行命令

理想情况下,我能够输出错误和用法。此外,子命令中还会出现其他错误,我不确定在这些情况下如何优雅地退出。

考虑以下 cli 定义:

use clap::ErrorKind::Io;
use clap::{Parser, Subcommand};
use git2::Repository;
use std::process;

#[derive(Debug, Parser)]
#[clap(author, version, about, long_about = None)]
struct Cli {
    #[clap(subcommand)]
    command: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
    /// Do a thing.
    Do,
}

目前我看到的三个主要选项是:

选项 1

fn main() -> Result<(), String> {
    let repo = match Repository::open(".") {
        Ok(repo) => repo,
        Err(_) => return Err("must be run from root of repository".to_owned()),
    };
    let args = Cli::parse();
    match args.command {
        Commands::Do => {
            println!("{:?}: Doing a thing with the repository.", repo.workdir());
        }
    }
    Ok(())
}

选项 2

fn main() {
    let repo = match Repository::open(".") {
        Ok(repo) => repo,
        Err(_) => {
            eprintln!("{}", "must be run from root of repository".to_owned());
            process::exit(1);
        }
    };
    let args = Cli::parse();
    match args.command {
        Commands::Do => {
            println!("{:?}: Doing a thing with the repository.", repo.workdir());
        }
    }
}

选项 3

fn main() -> clap::Result<(), clap::Error> {
    let repo = match Repository::open(".") {
        Ok(repo) => repo,
        Err(_) => return Err(clap::Error::raw(Io, "not in repo")),
    };
    let args = Cli::parse();
    match args.command {
        Commands::Do => {
            println!("{:?}: Doing a thing with the repository.", repo.workdir());
        }
    }
    Ok(())
}

这些中的任何一个或所有这些都是可怕的、有用的或可改进的吗?


我看到了寻求主观信息的结束投票,但我所追求的可能比看起来更加二元化。我当然会尊重社区的意愿,但我想知道是否有任何或所有这些严重超出规范的问题出于某种原因。

我个人非常喜欢 anyhow, eyre or miette.

等错误包装器库

一些备注:

  • 考虑使用 .map_err() 而不是 match 将错误转换为不同的错误,它更紧凑且更易于阅读
  • 考虑使用 ? 运算符而不是 return 向上传播错误。 ? 运算符是“如果正常则解包,如果错误则 return 错误”的简短版本,它使代码更易于阅读。

这是一个使用 anyhow 的例子:

fn main() -> anyhow::Result<()> {
    let repo = Repository::open(".").map_err(|_| anyhow!("must be run from root of repository"))?;
    let args = Cli::parse();
    match args.command {
        Commands::Do => {
            println!("{:?}: Doing a thing with the repository.", repo.workdir());
        }
    }
    Ok(())
}```