Taking fallible iterators into account

A function like

fn sum(i: impl Iterator<Item = i32>) -> i32 {
    let mut acc = 0;
    for x in i {acc += x;}
    acc
}

can be used as:

let a = &[1, 2, 3, 4];
println!("{}", sum(a.iter().cloned()));

So far, so good. But now you want to have as well:

fn main() -> std::io::Result<()> {
    use std::{fs::File, io::BufReader, io::BufRead};

    let file = BufReader::new(File::open("/dev/stdin")?);
    let stream = file.lines()
        .map(|s| s.unwrap().parse::<i32>().unwrap());

    println!("{}", sum(stream));
    Ok(())
}

From this, however, I draw the conclusion that sum has a has the wrong signature. The proper one is:

fn sum<E>(a: impl Iterator<Item = Result<i32, E>>)
-> Result<i32, E>
{
    let mut acc = 0;
    for x in a {acc += x?;}
    Ok(acc)
}

type Error = Box<dyn std::error::Error>;

fn main() -> Result<(), Error> {
    use std::{fs::File, io::BufReader, io::BufRead};

    let file = BufReader::new(File::open("/dev/stdin")?);
    let stream = file.lines()
        .map(|s| Ok::<i32, Error>(s?.parse::<i32>()?));

    println!("{:?}", sum(stream)?);
    Ok(())
}

Consequently, we need to rephrase:

enum Zero {}

fn main() {
    let a = &[1, 2, 3, 4];
    let value = match sum(a.iter().cloned().map(Ok::<i32, Zero>)) {
        Ok(value) => value,
        _ => unreachable!()
    };
    println!("{}", value);
}

Is there any other conclusion to draw?

It's true that you need something like this to write a function that can be used with both fallible and infallible iterators, but it's not something I have actually ever been forced to do in my several years of using Rust. It seems that it is usually avoidable in real code.

Note that you don't need the unreachable!() call. You can do this:

let value = match sum(a.iter().cloned().map(Ok::<i32, Zero>)) {
    Ok(value) => value,
    Err(error) => match error {},
};
2 Likes

Note that .sum() from the standard library does indeed also support these two signatures.

fn sum(i: impl Iterator<Item = i32>) -> i32 {
    i.sum()
}

fn sum_fallible<E>(a: impl Iterator<Item = Result<i32, E>>) -> Result<i32, E> {
    a.sum()
}

If you’re implementing a version of an iterator-processing function that’s supposed to support Results, you could do as the standard library does and use an abstraction like itertools::process_results. The process_results function allows you to directly use something like your original sum function in a setting with Result-types:

fn sum(i: impl Iterator<Item = i32>) -> i32 { … }

type Error = Box<dyn std::error::Error>;

fn main() -> Result<(), Error> {
    use std::{fs::File, io::BufReader, io::BufRead};

    let file = BufReader::new(File::open("/dev/stdin")?);
    let stream = file.lines()
        .map(|s| Ok::<i32, Error>(s?.parse::<i32>()?));

    println!("{:?}", itertools::process_results(stream, |iter| sum(iter))?);
    Ok(())
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.