Rust中map闭包迭代求和时Result类型适配错误求助
问题描述
我正在通过Advent of Code(AoC)学习Rust,参考资深开发者的写法实现解决方案。以下代码除.sum()部分外均可正常编译,但编译器提示在sum操作前Iterator::Item变为Result<usize, _>。
我不清楚如何避免get_priority返回Result类型,同时疑惑sum既然适用于迭代器,为何无法识别当前的Result类型迭代器?
代码示例
use std::fs; use std::error::Error; // 假设ByteSet是你引入的第三方库类型 use some_crate::ByteSet; fn main() { let data = fs::read_to_string("data/data.txt").expect("File should exist"); let lines = data.lines(); // 这是一个&str类型的行迭代器 let result: usize = lines.map(|rucksack| { let (cmprt1, cmprt2) = rucksack.split_at(rucksack.len() / 2); let cmprt1: ByteSet = cmprt1.into(); let cmprt2: ByteSet = cmprt2.into(); let dup = cmprt1.intersection(cmprt2).first().ok_or("Error finding intersection")?; Ok(get_priorty(dup)?) }).sum(); println!("{}", result) } fn get_priorty(letter: u8) -> Result<usize, Box<dyn Error>>{ match letter { b'a'..=b'z' => Ok(1 - (letter + b'a') as usize), b'A'..=b'Z' => Ok(27 - (letter + b'A') as usize), _ => Err("Unexpected character in rucksack".into()) } }
错误信息
10 | let result: usize = lines.map(|rucksack| { | _______________________________^ 11 | | let (cmprt1, cmprt2) = rucksack.split_at(rucksack.len() / 2); 12 | | let cmprt1: ByteSet = cmprt1.into(); 13 | | let cmprt2: ByteSet = cmprt2.into(); 14 | | let dup = cmprt1.intersection(cmprt2).first().ok_or("Error finding intersection")?; 15 | | Ok(get_priorty(dup)?) 16 | | }).sum(); | |______^ `Iterator::Item` changed to `Result<usize, _>` here note: required by a bound in `std::iter::Iterator::sum`
解决方案
1. 为什么sum无法处理Result迭代器?
Iterator::sum()默认只支持实现了Sum trait的类型。你现在的迭代器输出的是Result<usize, _>,但你试图直接把它sum成usize,类型不匹配,所以编译器报错。如果要sum Result迭代器,需要把结果类型声明为Result<usize, _>,或者提前把Result转换成具体数值。
2. 处理Result迭代器的两种方式
方式一:提前unwrap错误(适用于确定输入合法的场景)
如果能保证所有输入都符合要求(比如AoC的测试用例),可以直接用expect或unwrap把Result转成usize,让迭代器输出纯数值:
let result: usize = lines.map(|rucksack| { let (cmprt1, cmprt2) = rucksack.split_at(rucksack.len() / 2); let cmprt1: ByteSet = cmprt1.into(); let cmprt2: ByteSet = cmprt2.into(); let dup = cmprt1.intersection(cmprt2).first().expect("找不到交集"); get_priorty(dup).expect("背包中存在无效字符") }).sum();
方式二:保留错误处理(优雅处理异常)
如果想在出错时得到明确的错误信息,把结果类型改成Result<usize, Box<dyn Error>>,让sum返回Result,之后再处理错误:
let result: Result<usize, Box<dyn Error>> = lines.map(|rucksack| { let (cmprt1, cmprt2) = rucksack.split_at(rucksack.len() / 2); let cmprt1: ByteSet = cmprt1.into(); let cmprt2: ByteSet = cmprt2.into(); let dup = cmprt1.intersection(cmprt2).first().ok_or("找不到交集")?; get_priorty(dup) }).sum(); println!("{}", result.expect("计算优先级总和失败"));
注意这里去掉了Ok(...)包裹,因为get_priorty已经返回Result,闭包直接返回它即可,这样迭代器的Item就是Result<usize, _>,标准库为Result<T, E>实现了Sum(当T实现Sum时)。
3. 避免get_priorty返回Result的方法
如果能确保输入字符一定是a-z或A-Z,可以把函数改成直接返回usize,用panic!()处理异常情况:
fn get_priorty(letter: u8) -> usize { match letter { b'a'..=b'z' => 1 + (letter - b'a') as usize, // 修正公式:a对应1,b对应2...z对应26 b'A'..=b'Z' => 27 + (letter - b'A') as usize, // A对应27,B对应28...Z对应52 _ => panic!("背包中出现意外字符: {}", letter as char), } }
重要提示:你原来的优先级计算公式是错误的,1 - (letter + b'a')会得到负数,完全不符合题目要求。正确的计算应该是用当前字符减去基准字符(b'a'或b'A'),再加对应的起始值(1或27)。
内容的提问来源于stack exchange,提问作者thefrollickingnerd

