Fold with closure that returns a Result
Asked Answered
H

3

8

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?

Helminthiasis answered 15/11, 2017 at 21:39 Comment(0)
P
14

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)),
        )
    }
}
Parhe answered 15/11, 2017 at 22:38 Comment(0)
A
3

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.

Allative answered 15/11, 2017 at 21:49 Comment(1)
Wouldn't let sum = sum?; be shorter?Parhe
A
0

You could extend Iterator by a custom fold_result function like this (using the full path to Result because it seems you're importing a Result type generated by error_chain):

trait IterExtFoldResult: Iterator + Sized {
    #[inline]
    fn fold_result<B, F, E>(self, mut init: B, mut f: F) -> ::std::result::Result<B, E>
    where
        F: FnMut(B, Self::Item) -> ::std::result::Result<B, E>,
    {
        for i in self {
            init = f(init, i)?;
        }
        Ok(init)
    }
}
impl<I: Iterator> IterExtFoldResult for I {}

and use it like .fold_result(0, apply_decoding_step).

That way fold aborts when f returned an error; if you forward the error in f the compiler might or might not optimize to an early return.

Full example in playground

Aerometer answered 15/11, 2017 at 22:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.