DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS

Clap 默认枚举值报 invalid value,检查 Display 与 value_enum

Clap 4.6.7 的 default_value_t 在缺少 value_enum 标记时使用 Display 输出;若该文本不是枚举允许的名称,连没有传参数的启动也会失败。显式标记可让默认值按 ValueEnum 转换。

程序没有传入任何选项,Clap 却返回 invalid value,列出的错误值恰好来自枚举的 Display 输出。例如枚举允许命令行输入 foo,展示时写成 bar;default_value_t = Mode::Foo 仍可能被当成无效输入。

这条路径可见于正式 Clap 4.6.7:字段类型派生了 ValueEnum,但字段上没有显式的 #[arg(value_enum)]。枚举值在 Rust 中合法,不代表它转换出的默认文本也符合命令行解析规则。

默认值也会经过字符串解析

default_value_t 并非直接把一个 Rust 值塞进解析结果。没有 value_enum 标记时,派生代码先对默认值调用 ToString::to_string,再把字符串交给参数的解析器。自定义 Display 因此会参与默认值生成。

另一方面,ValueEnum 的取值名称来自枚举定义,例如 Mode::Foo 对应 foo。如果 Display 写出 bar,解析器就会拒绝这个默认值。错误不一定来自用户输入;没有给出该参数时,默认值同样会触发检查。

在字段上显式声明 value_enum

需要保留独立的展示文本时,可以让命令行字段明确使用枚举取值名称。下面是启用 Clap derive feature 的完整示例:

use clap::{Parser, ValueEnum};
use std::fmt;

#[derive(Clone, Debug, ValueEnum)]
enum Mode {
    Foo,
}

impl fmt::Display for Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("bar")
    }
}

#[derive(Debug, Parser)]
struct Cli {
    #[arg(long, value_enum, default_value_t = Mode::Foo)]
    mode: Mode,
}

fn main() -> Result<(), clap::Error> {
    let cli = Cli::try_parse_from(["demo"])?;
    println!("{}", cli.mode);
    Ok(())
}

显式标记后,4.6.7 的派生代码通过 ValueEnum::to_possible_value 取得默认值名称;本例默认文本是 foo,展示时仍可输出 bar。派生 ValueEnum 不会自动替应用生成 Display,也没有必要为迁就默认参数而更改所有业务展示。

如果字段本来不使用 ValueEnum,而是自定义解析器,便应让默认值的 Display 输出满足那个解析器的契约。另一种明确的写法是给参数指定合法的字符串默认值,但这要求同步维护字符串与枚举名称,改名时也要更新,不能把拼错的文本藏进默认配置。

标为跳过的枚举变体不能用作这里的默认值:它没有可用的 possible value,不能指望标记替它生成命令行名称。

同时检查省略参数和显式参数

回查不传 --mode、传入 --mode foo、传入无效名称,以及 --help 显示的默认值和候选值。前两种应得到同一个枚举变体;无效名称仍应被拒绝。若枚举还有 rename_all 或单个变体重命名,也要按重命名后的名称验证。

不要用 ignore_errors 让默认值解析失败后继续启动。那会改变整个命令行的错误处理,可能让别的必填项或拼写错误也被忽略。这里需要对齐的是同一字段的默认文本与解析规则。