15

In this function parse can return an error so I use .filter_map(Result::ok) to filter them out.

fn part1(input: &str) {
     let sum = input.lines()
        .map(|l| l.parse::<u32>())
        .filter_map(Result::ok)
        .map(|n| n as f32 / 3.0)
        .map(|f| f.round())
        .map(|f| f as u32 - 2)
        .sum::<u32>();
    // println!("{}", sum);
    println!("{:?}", sum);
}

However, I would like to return out of the part1 function when parse gives an error, kind of like using the question mark operator like this .map(|l| l.parse::<u32>()?). If this is done the compiler gives the error

error[E0277]: the `?` operator can only be used in a closure that returns `Result` 
or `Option` (or another type that implements `std::ops::Try`)                      
  --> src/main.rs:64:18                                                            
   |                                                                               
64 |         .map(|l| l.parse::<u32>()?)                                           
   |              ----^^^^^^^^^^^^^^^^^                                            
   |              |   |                                                            
   |              |   cannot use the `?` operator in a closure that returns `u32`  
   |              this function should return `Result` or `Option` to accept `?`

Is this because the question mark operator is used inside a closure so it returns out of the closure instead of the enclosing function? What are some idiomatic alternatives to using the question mark operator inside the closure so that I can return out of part1 if parse gives an error or unwrap the Ok if parse is successful? The result should be similar to .filter_map(Result::ok), except instead of filtering out the errors it will return out of the enclosing function when there is an error.

oberblastmeister
  • 864
  • 9
  • 13
  • 1
    As a sidenote: In contrast to Chris, IMHO having multiple `map` calls is better than having a big block. So keep that ;) – hellow Jul 02 '20 at 06:31
  • 1
    @hellow Removing the calls to map sadly gets rid of the pipeline feel of the calculation, which almost made it feel clearer. But I stand by my suggestion, since I prefer the calculation to not be tied up with iterator methods. I see your point though! – Chris Pearce Jul 02 '20 at 21:42
  • 1
    Rust by example gives some good patterns: https://doc.rust-lang.org/rust-by-example/error/iter_result.html – Tobu Feb 20 '21 at 22:55

2 Answers2

11

You can just keep passing the Result from parse further down the chain and allow the final sum to work - since Sum is implemented for Result. Then you can use ? on the final result of the chain.

An example would look like this:

fn part1(input: &str) -> Result<u32,std::num::ParseIntError>  {
     let sum = input.lines()
        .map(|l| l.parse::<u32>())
        .map(|n| n.map( |n| n as f32 / 3.0) )
        .map(|f| f.map( |f| f.round() ) )
        .map(|f| f.map( |f| f as u32 - 2) )
        .sum::<Result<u32,_>>()?;
    Ok(sum)
}

If you're using nightly rust you can get rid of the nested closures using a try block

#![feature(try_blocks)]

fn part1(input: &str) -> Result<u32, std::num::ParseIntError> {
    let sum = input.lines()
       .map( |l| try {
            let n = l.parse::<u32>()?;
            let f = n as f32 / 3.0;
            let f = f.round();
            f as u32 - 2
       })
       .sum::<Result<u32,_>>()?;
    Ok(sum)
}

If you are not using nightly you can extract the processing into a closure that returns a Result.

fn part1(input: &str) -> Result<u32, std::num::ParseIntError> {
    let process_line = |l:&str| -> Result<u32,std::num::ParseIntError> {
        let n = l.parse::<u32>()?;
        let f = n as f32 / 3.0;
        let f = f.round();
        Ok(f as u32 - 2)
    };
    let sum = input.lines().map(process_line).sum::<Result<u32,_>>()?;
    Ok(sum)
}

I'm also assuming that your real use case is somewhat more complicated than you've presented here. For something this simple I'd just use a for loop

fn part1(input: &str) -> Result<u32,std::num::ParseIntError> {
  let mut sum = 0;
  for line in input.lines() {
     let n = l.parse::<u32>()?;
     let f = n as f32 / 3.0;
     let f = f.round();
     sum += f as u32 - 2;
  }
  Ok(sum)
}
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • 1
    This might be an alternative for accumulating elements, `try_fold`: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b8e3c9cdc245b2f4ed0bc43d0cd36ff6 , for just traversing all the values `try_for_each` would work – Ömer Erden Jul 02 '20 at 05:39
10

The multiple calls to map might make some solutions feel cluttered.

Instead, all your math could be performed in a single call to map, that is then used with sum:

fn part1(input: &str) -> Result<(), std::num::ParseIntError> {
     let sum = input.lines()
        .map(|l| {
            let n = l.parse::<u32>()?;
            let mut f = n as f32 / 3.0;
            f = f.round();
            Ok(f as u32 - 2)
        })
        .sum::<Result<u32, _>>()?;

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

But you could then go further by removing the ? and using map on the Result. If you do this along with returning a value from your function, you don't even need the explicit type parameter to sum:

fn part1(input: &str) -> Result<u32, std::num::ParseIntError> {
     input.lines()
        .map(|l| {
            l.parse::<u32>().map(|n| {
                let mut f = n as f32 / 3.0;
                f = f.round();
                f as u32 - 2
            })
        })
        .sum()
}

You would then have to call println outside of the function.

If you don't like the nested closures, you can always extract the math to another function (with a better name):

fn part1(input: &str) -> Result<u32, std::num::ParseIntError> {
     input.lines()
        .map(|l| l.parse().map(math_part))
        .sum()
}

fn math_part(n: u32) -> u32 {
    let mut f = n as f32 / 3.0;
    f = f.round();
    f as u32 - 2
}
Chris Pearce
  • 666
  • 5
  • 16