Rewrite help: imperative -> functional style

EDIT: free free to change title if you find something more accurate // I'm sure a more descriptive title exists. I just can't come up with it.

  1. Consider this silly code:
fn convert(c: Cat) -> Result<Dog, ConversionError> {
...
}

fn foo(cats: Vec<Cat>) -> Result<Vec<Dog>, ConversionError> {
  let mut dogs: Vec<Dog> = vec![];
  for c in cats {
    let d = convert(c)?;
    dogs.push(d);
  }
  dogs;
}


  1. If convert was Cat -> Dog instead of Cat -> Result<Dog, ConversionError> then I could just use .iter().map() ... collect::<Vec<_>>()

However, given that the function can possibly fail, is there a functional way to do this, or is impure the way to go?

fn foo(cats: Vec<Cat>) -> Result<Vec<Dog>, ConversionError> {
  cats.into_iter().map(convert).collect()
}

This works because Result can be constructed from the iterator of results. Exact impl is impl<T, E, Collection> FromIterator<Result<T, E>> for Result<Collection, E> where Collection: FromIterator<T>, and its semantic is same as your code.

Edit: fix type signiture

4 Likes
  1. This is amazing.

  2. In my above impl, 'Err' short circuits execution. In your code, the map is lazy, and since the collect is doing some type of black magic, does it also ensure "short circuit" on Err ?

Yes, that short circuits on Err

1 Like

There isn't really any black magic involved. collect iterates, looks at every element, puts it into the collection that the inner FromIterator builds, or stops collecting when encountering an Err.

This also means that if the element 9999/10000 is the first Err, an almost-fully-populated Vec will be dropped. But I don't think that is avoidable :slight_smile:

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.