DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Rust read_line 读入数字后,parse 报 InvalidDigit

read_line 保留换行且追加到已有缓冲区。按输入规则处理首尾空白,匹配解析错误,并在循环读取前清空字符串。

终端输入 50,显示出来也是数字,但下面的解析仍可能失败:

let mut input = String::new();
std::io::stdin().read_line(&mut input).expect("read failed");
let value = input.parse::<u8>().expect("invalid number");

read_line 会保留行末换行,而整数解析要求输入符合目标类型的数字格式。换成更大的整数类型无法去掉换行;expect 只是把解析错误变成 panic。

下面针对 Rust 标准输入逐行读取和内置整数解析,使用稳定标准库接口。其他来源的字符串也可能含空白,但需要先检查实际输入,不能把全部 InvalidDigit 都归因于换行。

用调试格式看清不可见字符

在受控样本里查看原字符串:

println!("raw={:?}", input);

按 Enter 提交的文本可能显示为 "50\n" 或 "50\r\n"。普通 {} 输出会直接换行,容易掩盖字符串中的行结束符。若包含敏感输入,不要把原文写入生产日志。

输入还可能是 "5x"、"5 0" 或含下划线的 "5_0"。这些情况并不是行末换行,不能靠去除首尾空白修复。

先处理允许忽略的空白,再匹配解析结果

下面是一个完整的读取示例,接受数字两端的空白,并将读取错误、输入结束和数字格式错误分开处理:

use std::io;

fn main() {
    let mut input = String::new();
    loop {
        input.clear();
        match io::stdin().read_line(&mut input) {
            Ok(0) => break,
            Ok(_) => {}
            Err(err) => {
                eprintln!("read failed: {}", err);
                break;
            }
        }

        match input.trim().parse::<u8>() {
            Ok(value) => {
                println!("value={}", value);
                break;
            }
            Err(err) => {
                eprintln!("invalid u8: {}", err);
            }
        }
    }
}

trim 返回去掉首尾 Unicode 空白的字符串切片,包含常见换行和回车,不修改原 String。这意味着代码也接受 " 50 ";如果协议不允许两端空格,应按协议只处理行结束符,不能直接放宽到所有空白。

目标类型仍然是 u8,范围为 0 到 255。256 超出范围,空行去空白后也不是数字。对这类用户输入保留 Err 分支,比直接 unwrap 或 expect 更适合继续输入的程序。不要用默认值 0 掩盖失败,除非业务明确把无效输入定义为零。

循环读取前要清空缓冲区

read_line 会把新内容追加到传入的字符串。把 String::new() 放在循环外复用是可以的,但必须在下一次读取前调用 clear(),否则第二次输入可能接在上一行之后,形成多个数字和换行混合的文本。

另一种方式是在每轮循环内部创建新字符串。两种写法都能避免累积,应保留符合当前程序结构的一种,不需要同时使用。

在本机受控输入中依次检查有效数字、两端空白、字母、超出范围的数以及输入结束。预期有效数字被接受,格式或范围错误得到错误提示且可重试,输入结束退出循环。多次失败后再输入有效值仍应成功,以确认缓冲区没有累积旧内容。

如果这次修改改变了既有协议对空白的处理,回退时恢复原校验规则,保留错误分支和输入结束处理;不要通过忽略解析错误维持程序继续运行。