Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternatives for using the question mark operator inside a map function closure

Tags:

rust

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.

like image 565
Rafael Avatar asked Jul 02 '20 01:07

Rafael


2 Answers

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)
}
like image 188
Michael Anderson Avatar answered Sep 22 '22 17:09

Michael Anderson


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
}
like image 20
Chris Pearce Avatar answered Sep 26 '22 17:09

Chris Pearce