Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fold with closure that returns a Result

I'm using the regex crate to find some text with this regex:

lazy_static! {
    static ref FIND_STEPS_RE: Regex =
        Regex::new(r"my regex").unwrap();
}

I want to find all possible captures and iterate over them:

FIND_STEPS_RE.captures_iter(script_slice)

Each captured element consists of 2 values: an operation and a number. For example, the output could be:

[("+", "10"), ("-", "20"), ("*", "2")]

I want to iterate over it, parse the numbers and apply the operation.

I tried:

let e = FIND_STEPS_RE.captures_iter(script_slice)
    .fold(0, |sum, value| apply_decoding_step)?;

where apply_decoding_step is:

fn apply_decoding_step(sum: i32, capture: regex::Captures<>) -> Result<i32> {
    let number = parse_number(&capture[2])?;

    match  &capture[1] {
        "+" => Ok(s + number),
        "-" => Ok(s - number),
        "*" => Ok(s * number),
        "/" => Ok(s / number),
        _ => bail!("Unknown step operator"),
    }
}

But I got this error:

error[E0271]: type mismatch resolving `<fn(i32, regex::Captures<'_>) -> std::result::Result<i32, Error> {apply_decoding_step} as std::ops::FnOnce<(i32, regex::Captures<'_>)>>::Output == i32`
   --> src/main.rs:122:10
    |
122 |         .fold(seed, apply_decoding_step);
    |          ^^^^ expected enum `std::result::Result`, found i32
    |
    = note: expected type `std::result::Result<i32, Error>`
               found type `i32`

I assume this is because I'm trying to fold a Result into a i32, but since I need to parse the second capture value and also need that otherwise case in my match, how can I fix that?

like image 661
Sassa Avatar asked Nov 15 '17 21:11

Sassa


2 Answers

As jupp0r states, the initial value of Iterator::fold must be of the same type as the return value of the closure.

Rust 1.26

You can use Iterator::try_fold instead. This will exit iteration on the first failure:

let result = x.iter().try_fold(0, |acc, &i| apply_decoding_step(acc, i));

Complete example:

fn main() {
    let x = [("+", "10"), ("-", "20"), ("*", "2")];

    let result = x.iter().try_fold(0, |acc, &i| apply_decoding_step(acc, i));

    println!("{:?}", result);
}

fn apply_decoding_step(sum: i32, capture: (&str, &str)) -> Result<i32, ()> {
    let number: i32 = capture.1.parse().expect("nope");

    match capture.0 {
        "+" => Ok(sum + number),
        "-" => Ok(sum - number),
        "*" => Ok(sum * number),
        "/" => Ok(sum / number),
        _ => Err(()),
    }
}

Rust 1.0

I'd recommend using Result::and_then to skip nothing in the fold when an error has occurred:

let result = x.iter().fold(Ok(0), |acc, &i| {
    acc.and_then(|acc| apply_decoding_step(acc, i))
});

The problem here is that the fold body is executed for every element in the iterator, even once an error occurs.

Here's an enterprise-grade solution where the main benefit is that iteration will end as soon as the first Err is encountered, instead of spinning through the rest of the list. Secondary benefits include the ability to write very fine-grained tests for each piece (parsing from a string, arithmetic operations, accumulation, etc.):

fn main() {
    let x = [("+", "10"), ("-", "20"), ("*", "2")];

    let result: Result<Accumulator, ()> = x
        .iter()
        .map(|&(op, val)| {
            let op = op.parse::<Op>()?;
            let val = val.parse::<i32>().map_err(|_| ())?;
            Ok((op, val))
        })
        .collect();

    println!("{:?}", result);
}

use std::iter::FromIterator;
use std::str::FromStr;

#[derive(Debug)]
enum Op {
    Add,
    Sub,
    Mul,
    Div,
}

impl Op {
    fn apply(&self, a: i32, b: i32) -> i32 {
        use Op::*;

        match *self {
            Add => a + b,
            Sub => a - b,
            Mul => a * b,
            Div => a / b,
        }
    }
}

impl FromStr for Op {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        use Op::*;

        match s {
            "+" => Ok(Add),
            "-" => Ok(Sub),
            "*" => Ok(Mul),
            "/" => Ok(Div),
            _ => Err(()),
        }
    }
}

#[derive(Debug)]
struct Accumulator(i32);

impl<'a> FromIterator<(Op, i32)> for Accumulator {
    fn from_iter<I>(iter: I) -> Self
    where
        I: IntoIterator<Item = (Op, i32)>,
    {
        Accumulator(
            iter.into_iter()
                .fold(0, |acc, (op, val)| op.apply(acc, val)),
        )
    }
}
like image 169
Shepmaster Avatar answered Sep 28 '22 17:09

Shepmaster


Take a closer look at the type signature for fold:

fn fold<B, F>(self, init: B, f: F) -> B
 where
    F: FnMut(B, Self::Item) -> B,
{ ... }

init has to have the same type as the return value of f. This is also what the compiler tells you in the error message. You could do

fn apply_decoding_step(sum: Result<i32>, capture: regex::Captures<>) -> Result<i32> {
    match sum {
        Err(_) => sum,
        Ok(s) => {      
            let number = parse_number(&capture[2])?;
            match  &capture[1] {
                "+" => Ok(s + number),
                "-" => Ok(s - number),
                "*" => Ok(s * number),
                "/" => Ok(s / number),
                _ => bail!("Unknown step operator"),
           }
       }
    }
}

And then call it with an Ok seed:

.fold(Ok(seed), apply_decoding_step);

Now, if any failure occurs, your fold returns an Err.

like image 25
jupp0r Avatar answered Sep 28 '22 19:09

jupp0r