9

I would like to write some code in a "functional programming" style.

However, I start with an Iterator of Results and I only want to apply the function to the Ok items. Furthermore, I want to stop the iteration on the first error (however, I'd be open to different behavior).

So far, I am using a nested map() pattern: <iter>.map(|l| l.map(replace)). I think this is extremely ugly.

Using the nightly "result_flattening", I can flatten each nested Result<Result<T, E>, E> into a Result<T, E>. Using eyre::Context I convert the different Error types into an eyre::Report error type. All of this feels quite clumsy.

What is an elegant way to write this in Rust?

Minimal Working Example

#![feature(result_flattening)] use std::io::BufRead; use eyre::Context; fn main() { let data = std::io::Cursor::new(b"FFBFFFBLLL\nBFBFBBFRLR\nFFFBFFBLLL"); let seats: Result<Vec<_>, _> = data .lines() .map(|l| l.map(replace).context("force eyre")) .map(|l| l.map(|s| u32::from_str_radix(&s, 2).context("force eyre"))) .map(|r| r.flatten()) .collect(); println!("{:#?}", seats); } fn replace(line: String) -> String { line.replace('F', "0") .replace('B', "1") .replace('L', "0") .replace('R', "1") } 

Further References:

1 Answer 1

10

Since you discard the error type anyway, you can avoid eyre entirely and use .ok to convert the Result into an Option, then just work with Option's and_then to avoid flattening every time:

let seats: Option<Vec<_>> = data .lines() .map(|l| l.ok()) .map(|l| l.map(replace)) .map(|l| l.and_then(|s| u32::from_str_radix(&s, 2).ok())) // if you want to keep chaining .map(|l| l.and_then(|s| some_result_function(&s).ok())) .collect(); 

If you want to just skip over the errors, a much more elegant solution exists with filter_map:

let seats: Vec<_> = data .lines() .filter_map(|l| l.ok()) .map(replace) .filter_map(|l| u32::from_str_radix(&l, 2).ok()) .collect(); 

If you want to maintain errors, then box the errors into a Box<dyn Error> to account for different types:

use std::error::Error; // later in the code let seats: Result<Vec<_>, Box<dyn Error>> = data .lines() .map(|x| x.map_err(|e| Box::new(e) as _)) .map(|l| l.map(replace)) .map(|l| l.and_then(|s| u32::from_str_radix(&s, 2).map_err(|e| Box::new(e) as _))) .collect(); 

If you don't like the repeated .map_err(|e| Box::new(e) as _), then make a trait for it:

use std::error::Error; trait BoxErr { type Boxed; fn box_err(self) -> Self::Boxed; } impl<T, E: Error + 'static> BoxErr for Result<T, E> { type Boxed = Result<T, Box<dyn Error>>; fn box_err(self) -> Self::Boxed { self.map_err(|x| Box::new(x) as Box<dyn Error>) } } // later in the code let seats: Result<Vec<_>, Box<dyn Error>> = data .lines() .map(|x| x.box_err()) .map(|l| l.map(replace)) .map(|l| l.and_then(|s| u32::from_str_radix(&s, 2).box_err())) .collect(); 
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer. Is there a way a to do it as elegantly as you did but also to detect errors?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.