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(),否则第二次输入可能接在上一行之后,形成多个数字和换行混合的文本。
另一种方式是在每轮循环内部创建新字符串。两种写法都能避免累积,应保留符合当前程序结构的一种,不需要同时使用。
在本机受控输入中依次检查有效数字、两端空白、字母、超出范围的数以及输入结束。预期有效数字被接受,格式或范围错误得到错误提示且可重试,输入结束退出循环。多次失败后再输入有效值仍应成功,以确认缓冲区没有累积旧内容。
如果这次修改改变了既有协议对空白的处理,回退时恢复原校验规则,保留错误分支和输入结束处理;不要通过忽略解析错误维持程序继续运行。


