How to reduce boilerplate nested Result in Rust - rust

I have code using a nested Result like this:
fn ip4(s: &str) -> Result<(u8, u8, u8, u8), num::ParseIntError> {
let t: Vec<_> = s.split('.').collect();
match t[0].parse::<u8>() {
Ok(a1) => {
match t[1].parse::<u8>() {
Ok(a2) => {
match t[2].parse::<u8>() {
Ok(a3) => {
match t[3].parse::<u8>() {
Ok(a4) => {
Ok((a1, a2, a3, a4))
}
Err(er) => Err(er)
}
},
Err(er) => Err(er)
}
}
Err(er) => Err(er)
}
}
Err(er) => Err(er),
}
}
Is there any function or composing way to reduce this? Something like Haskell or Scala programmers do:
fn ip4(s: &str) -> Result<(u8, u8, u8, u8), num::ParseIntError> {
let t: Vec<_> = s.split('.').collect();
Result
.lift((,,,))
.ap(() -> t[0].parse::<u8>())
.ap(() -> t[1].parse::<u8>())
.ap(() -> t[2].parse::<u8>())
.ap(() -> t[3].parse::<u8>()) // maybe more concise in Haskell or Scala but I think it's enough :)
}

The answer to your direct question is the questionmark operator which would allow you to replace your whole match block with
Ok((
t[0].parse::<u8>()?,
t[1].parse::<u8>()?,
t[2].parse::<u8>()?,
t[3].parse::<u8>()?,
))
where essentially ? will return the error immediately if one is encountered.
That said, Rust already provides APIs for parsing IP addresses. Even if you wanted to maintain your tuple approach (though why would you), you could implement your function as
fn ip4(s: &str) -> Result<(u8, u8, u8, u8), net::AddrParseError> {
let addr: net::Ipv4Addr = s.parse()?;
let octets = addr.octets();
Ok((octets[0], octets[1], octets[2], octets[3]))
}
or just pass around the Ipv4Addr value directly.

Though, I do not see anything bad in #loganfsmyth's answer, I want to add another solution.
Your problem is a very simple and general problem of all programming languages which can be solved very easily if you would have enough time or practice in optimizing solutions. There is some divide and conquer recursive technique which is usually used to solve such problems. For a start, imagine a more simple thing: parsing a single octet from a string. This is a simple parse which you already know. Then mentally try to expand this problem to a larger one - parsing all octets which is a simple repeating process of the smallest problem we have solved earlier (parsing a single octet). This leads us to an iterative/recursive process: do something until. Keeping this in mind I have rewritten your function to a simple iterative process which uses tail-recursion which will not cause a stack overflow as a usual recursion due to it's form:
use std::num;
#[derive(Debug, Copy, Clone)]
struct IpAddressOctets(pub u8, pub u8, pub u8, pub u8);
type Result = std::result::Result<IpAddressOctets, num::ParseIntError>;
fn ipv4(s: &str) -> Result {
let octets_str_array: Vec<_> = s.split('.').collect();
// If it does not contain 4 octets then there is a error.
if octets_str_array.len() != 4 {
return Ok(IpAddressOctets(0, 0, 0, 0)) // or other error type
}
let octets = Vec::new();
fn iter_parse(octets_str_array: Vec<&str>, mut octets: Vec<u8>) -> Result {
if octets.len() == 4 {
return Ok(IpAddressOctets(octets[0], octets[1], octets[2], octets[3]))
}
let index = octets.len();
octets.push(octets_str_array[index].parse::<u8>()?);
iter_parse(octets_str_array, octets)
}
iter_parse(octets_str_array, octets)
}
fn main() {
println!("IP address octets parsed: {:#?}", ipv4("10.0.5.234"));
}
Keep in mind that Rust language is a bit more functional than you might think.
Also, I would recommend you to read this book which greatly explains the solution.

You can use early returns to prevent the nesting (but not the repetition).
Note the body of the Err arms of the matches:
fn ip4(s: &str) -> Result<(u8, u8, u8, u8), num::ParseIntError> {
let t: Vec<_> = s.split('.').collect();
let a1 = match t[0].parse::<u8>() {
Ok(x) => x,
Err(er) => return Err(er),
};
let a2 = match t[1].parse::<u8>() {
Ok(x) => x,
Err(er) => return Err(er),
};
let a3 = match t[2].parse::<u8>() {
Ok(x) => x,
Err(er) => return Err(er),
};
let a4 = match t[3].parse::<u8>() {
Ok(x) => x,
Err(er) => return Err(er),
};
(a1, a2, a3, a4)
}
But, as the others have said, Rust already has a built-in way to parse IP addresses.

Related

Matching multiple possible types?

I'm very, very new to Rust and struggling with it because of my strong weakly typed programming background.
The code below should write data being received from Python via PYO3 into a XLSX worksheet. I just don't know how to handle the last match, because "value" is of type PyAny (this is, its method extract can output multiple types such as String, f32, etc. and I want a specific behavior depending on the extracted type).
Maybe I could just chain matches for each potential extracted type (if first outputs Err, try the next), but I suspect there could be a better way. Maybe I'm just approaching the problem with a wrong design. Any insights will be welcome.
pub trait WriteValue {
fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError>;
}
impl WriteValue for String {
fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError> {
worksheet.write_string(row, col, &self, format)
}
}
impl WriteValue for f32 {
fn write_value(&self, worksheet: &mut Worksheet, row: u32, col: u16, format: Option<&Format>) -> Result<(), XlsxError> {
worksheet.write_number(row, col, f64::from(*self), format)
}
}
fn _write(path: &str, data: HashMap<u32, &PyList>, _highlight: Option<&PyDict>) -> Result<(), XlsxError> {
let workbook = Workbook::new(path);
let mut worksheet = workbook.add_worksheet(None)?;
let format_bold = workbook.add_format().set_bold();
for (row_index, values) in data {
let mut col_idx: u16 = 0;
for value in values {
col_idx += 1;
let row_format= match &row_index {
0 => Some(&format_bold),
_ => None
};
match value.extract::<String>() {
Ok(x) => x.write_value(&mut worksheet, row_index.clone(), &col_idx -1, row_format)?,
Err(_) => { }
}
}
}
workbook.close()
}
This is mostly a pyo3 API issue, and I don't think pyo3 has built-in "multiextract" though I'm not ultra familiar with it, so it may.
However, first since you don't care about the Err clause you could simplify your code by simply chaining if let statements, they're syntactic sugar but for unary or binary boolean conditions they're really convenient e.g.
if let Ok(x) = value.extract::<String>() {
x.write_value(...)
} else if let Ok(x) = value.extract::<f32>() {
// handle this case and possibly add a bunch more
} else {
// handle no case matching (optional if should be ignored)
}
Second, it looks like pyo3 lets you derive enums, since WriteValue is apparently an internal trait it would make sense to derive the corresponding enum:
#[derive(FromPyObject)]
enum Writables {
#[pyo3(transparent, annotation = "str")]
String(String),
#[pyo3(transparent, annotation = "float")]
Float(f32),
// put the other cases here
}
then you can extract to that and match all the variants at once (and handle the "unsupported types" separately).
In fact at this point the trait is probably unecessary, unless it's used for other stuff, you could just have your write_value method on the enum directly.
side-note: extracting a python float (which is a double) to an f32 then immediately widening it to an f64 in order to write it out seems... odd. Why not extract an f64 in the first place?
PyAny can be try to downcast to any other Python type. I am not proficient with PyO3, but the only approach I see here is to try to downcast to the types you support otherwise maybe launch an error:
fn _write(path: &str, data: HashMap<u32, &PyList>, _highlight: Option<&PyDict>) -> Result<(), XlsxError> {
let workbook = Workbook::new(path);
let mut worksheet = workbook.add_worksheet(None)?;
let format_bold = workbook.add_format().set_bold();
for (row_index, values) in data {
let mut col_idx: u16 = 0;
for value in values {
col_idx += 1;
let row_format= match &row_index {
0 => Some(&format_bold),
_ => None
};
if let Ok(string) = value.downcast::<PyString> {
// handle pystring object
string.write_value(&mut worksheet, row_index.clone(), &col_idx -1, row_format)?;
...
} else if let Ok(int) = value.downcast::<PyInt> {
// handle pyint object
...
} else {
// error, or not supported
}
}
}
workbook.close()
}

Initialize a Vec with not-None values only

If I have variables like this:
let a: u32 = ...;
let b: Option<u32> = ...;
let c: u32 = ...;
, what is the shortest way to make a vector of those values, so that b is only included if it's Some?
In other words, is there something simpler than this:
let v = match b {
None => vec![a, c],
Some(x) => vec![a, x, c],
};
P.S. I would prefer a solution where we don't need to use the variables more than once. Consider this example:
let some_person: String = ...;
let best_man: Option<String> = ...;
let a_third_person: &str = ...;
let another_opt: Option<String> = ...;
...
As can be seen, we might have to use longer variable names, more than one Option (None), expressions (like a_third_person.to_string()), etc.
Yours is fine, but here's a sophisticated one:
[Some(a), b, Some(c)].into_iter().flatten().collect::<Vec<_>>()
This works since Option impls IntoIterator.
If it depends on just one variable:
b.map(|b| vec![a, b, c]).unwrap_or_else(|| vec![a, c]);
Playground
After some thinking and investigating, I've come with the following crazy thing.
The end goal is to have a macro, optional_vec![], that you can pass it either T or Option<T> and it should behave like described in the question. However, I decided on a strong restriction: it should have the best performance possible. So, you write:
optional_vec![a, b, c]
And get at least the performance of hand-written match, if not more. This forbids the use of the simple [Some(a), b, Some(c)].into_iter().flatten().collect::<Vec<_>>(), suggested in my other answer (though even this solution needs some way to differentiate between Option<T> and just T, which, like we'll see, is not an easy problem at all).
I will first warn that I've not found a way to make my macro work with Option. That is, if you want to build a vector of Option<T> from Option<T> and Option<Option<T>>, it will not work.
When a design a complex macro, I like to think first how the expanded code will look like. And in this macro, we have several hard problems to solve.
First, the macro take plain expressions. But somehow, it needs to switch on their type being T or Option<T>. How should such thing be done?
The feature we use to do such things is specialization.
#![feature(specialization)]
pub trait Optional {
fn some_method(self);
}
impl<T> Optional for T {
default fn some_method(self) {
// Just T
}
}
impl<T> Optional for Option<T> {
fn some_method(self) {
// Option<T>
}
}
Like you probably noticed, now we have two problems: first, specialization is unstable, and I'd like to stay with stable. Second, what should be inside the trait? The second problem is easier to solve, so let's begin with it.
Turns out that the most performant way to do the pushing to the vector is to pre-allocate capacity (Vec::with_capacity), write to the vector by using pointers (don't push(), it optimizes badly!) then set the length (Vec::set_len()).
We can get a pointer to the internal buffer of the vector using Vec::as_mut_ptr(), and advance the pointer via <*mut T>::add(1).
So, we need two methods: one to hint us about the capacity (can be zero for None or one for Some() and non-Option elements), and a write_and_advance() method:
pub trait Optional {
type Item;
fn len(&self) -> usize;
unsafe fn write_and_advance(self, place: &mut *mut Self::Item);
}
impl<T> Optional for T {
default type Item = Self;
default fn len(&self) -> usize { 1 }
default unsafe fn write_and_advance(self, place: &mut *mut Self) {
place.write(self);
*place = place.add(1);
}
}
impl<T> Optional<T> for Option<T> {
type Item = T;
fn len(&self) -> usize { self.is_some() as usize }
unsafe fn write_and_advance(self, place: &mut *mut T) {
if let Some(value) = self {
place.write(value);
*place = place.add(1);
}
}
}
It doesn't even compile! For the why, see Mismatch between associated type and type parameter only when impl is marked `default`. Luckily for us, the trick we'll use to workaround specialization not being stable does work in this situation. But for now, let's assume it works. How will the code using this trait look like?
match (a, b, c) { // The match is here because it's the best binding for liftimes: see https://stackoverflow.com/a/54855986/7884305
(a, b, c) => {
let len = Optional::len(&a) + Optional::len(&b) + Optional::len(&c);
let mut result = ::std::vec::Vec::with_capacity(len);
let mut next_element = result.as_mut_ptr();
unsafe {
Optional::write_and_advance(a, &mut next_element);
Optional::write_and_advance(b, &mut next_element);
Optional::write_and_advance(c, &mut next_element);
result.set_len(len);
}
result
}
}
And it works! Except that it does not, because the specialization does not compile as I said, and we also want to not repeat all of this boilerplate but insert it into a macro.
So, how do we solve the problems with specialization: being unstable and not working?
dtonlay has a very cool trick he calls autoref specialization (BTW, all of this repo is a very recommended reading!). This is a trick that can be used to emulate specialization. It works only in macros, but we're in a macro so this is fine.
I will not elaborate about the trick here (I recommend to read his post; he also used this trick in the excellent and very widely used anyhow crate). In short, the idea is to trick the typechecker by implementing a trait for T under certain conditions (the specialized impl) and other trait for &T for the general case (this could be inherent impl if not coherence). Since Rust performs automatic referencing during method resolution, that is take reference to the receiver as needed, this will work - the typechecker will autoref if needed, and will stop in the first applicable impl - i.e. the specialized impl if it matches, or the general impl otherwise.
Here's an example:
use std::fmt;
pub trait Display {
fn foo(&self);
}
// Level 1
impl<T: fmt::Display> Display for T {
fn foo(&self) { println!("Display({}), {}", std::any::type_name::<T>(), self); }
}
pub trait Debug {
fn foo(&self);
}
// Level 2
impl<T: fmt::Debug> Debug for &T {
fn foo(&self) { println!("Debug({}), {:?}", std::any::type_name::<T>(), self); }
}
macro_rules! foo {
($e:expr) => ((&$e).foo());
}
Playground.
We can use this trick in our case:
#[doc(hidden)]
pub mod autoref_specialization {
#[derive(Copy, Clone)]
pub struct OptionTag;
pub trait OptionKind {
fn optional_kind(&self) -> OptionTag;
}
impl<T> OptionKind for Option<T> {
#[inline(always)]
fn optional_kind(&self) -> OptionTag { OptionTag }
}
impl OptionTag {
#[inline(always)]
pub fn len<T>(self, this: &Option<T>) -> usize { this.is_some() as usize }
#[inline(always)]
pub unsafe fn write_and_advance<T>(self, this: Option<T>, place: &mut *mut T) {
if let Some(value) = this {
place.write(value);
*place = place.add(1);
}
}
}
#[derive(Copy, Clone)]
pub struct DefaultTag;
pub trait DefaultKind {
fn optional_kind(&self) -> DefaultTag;
}
impl<T> DefaultKind for &'_ T {
#[inline(always)]
fn optional_kind(&self) -> DefaultTag { DefaultTag }
}
impl DefaultTag {
#[inline(always)]
pub fn len<T>(self, _this: &T) -> usize { 1 }
#[inline(always)]
pub unsafe fn write_and_advance<T>(self, this: T, place: &mut *mut T) {
place.write(this);
*place = place.add(1);
}
}
}
And the expanded code will look like:
use autoref_specialization::{DefaultKind as _, OptionKind as _};
match (a, b, c) {
(a, b, c) => {
let (a_tag, b_tag, c_tag) = (
(&a).optional_kind(),
(&b).optional_kind(),
(&c).optional_kind(),
);
let len = a_tag.len(&a) + b_tag.len(&b) + c_tag.len(&c);
let mut result = ::std::vec::Vec::with_capacity(len);
let mut next_element = result.as_mut_ptr();
unsafe {
a_tag.write_and_advance(a, &mut next_element);
b_tag.write_and_advance(b, &mut next_element);
c_tag.write_and_advance(c, &mut next_element);
result.set_len(len);
}
result
}
}
It may be tempting to try to convert this immediately into a macro, but we still have one unsolved problem: our macro need to generate identifiers. This may not be obvious, but what if we pass optional_vec![1, Some(2), 3]? We need to generate the bindings for the match (in our case, (a, b, c) => ...) and the tag names ((a_tag, b_tag, c_tag)).
Unfortunately, generating names is not something macro_rules! can do in today's Rust. Fortunately, there is an excellent crate paste (another one from dtonlay!) that is a small proc-macro that allows you to do that. It is even available on the playground!
However, we need a series of identifiers. That can be done with tt-munching, by repeatedly adding some letter (I used a), so you get a, aa, aaa, ... you get the idea.
#[doc(hidden)]
pub mod reexports {
pub use std::vec::Vec;
pub use paste::paste;
}
#[macro_export]
macro_rules! optional_vec {
// Empty case
{ #generate_idents
exprs = []
processed_exprs = [$($e:expr,)*]
match_bindings = [$($binding:ident)*]
tags = [$($tag:ident)*]
} => {{
use $crate::autoref_specialization::{DefaultKind as _, OptionKind as _};
match ($($e,)*) {
($($binding,)*) => {
let ($($tag,)*) = (
$((&$binding).optional_kind(),)*
);
let len = 0 $(+ $tag.len(&$binding))*;
let mut result = $crate::reexports::Vec::with_capacity(len);
let mut next_element = result.as_mut_ptr();
unsafe {
$($tag.write_and_advance($binding, &mut next_element);)*
result.set_len(len);
}
result
}
}
}};
{ #generate_idents
exprs = [$e:expr, $($rest:expr,)*]
processed_exprs = [$($processed_exprs:tt)*]
match_bindings = [$first_binding:ident $($bindings:ident)*]
tags = [$($tags:ident)*]
} => {
$crate::reexports::paste! {
$crate::optional_vec! { #generate_idents
exprs = [$($rest,)*]
processed_exprs = [$($processed_exprs)* $e,]
match_bindings = [
[< $first_binding a >]
$first_binding
$($bindings)*
]
tags = [
[< $first_binding a_tag >]
$($tags)*
]
}
}
};
// Entry
[$e:expr $(, $exprs:expr)* $(,)?] => {
$crate::optional_vec! { #generate_idents
exprs = [$($exprs,)+]
processed_exprs = [$e,]
match_bindings = [__optional_vec_a]
tags = [__optional_vec_a_tag]
}
};
}
Playground.
I can also personally recommend
let mut v = vec![a, c];
v.extend(b);
Short and clear.
Sometime the straight forward solution is the best:
fn jim_power(a: u32, b: Option<u32>, c: u32) -> Vec<u32> {
let mut acc = Vec::with_capacity(3);
acc.push(a);
if let Some(b) = b {
acc.push(b);
}
acc.push(c);
acc
}
fn ys_iii(
some_person: String,
best_man: Option<String>,
a_third_person: String,
another_opt: Option<String>,
) -> Vec<String> {
let mut acc = Vec::with_capacity(4);
acc.push(some_person);
best_man.map(|x| acc.push(x));
acc.push(a_third_person);
another_opt.map(|x| acc.push(x));
acc
}
If you don't care about the order of the values, another option is
Iterator::chain(
[a, c].into_iter(),
[b].into_iter().flatten()
).collect()
Playground

Rust error-handling (I've got a utf8error, I want to return io::Result)

I'm putting together a collection of functions for reading data from a file. All is well until I get to the read n bytes as a string function. Here's my current attempt:
pub fn read_string<R: Read>(input: &mut R, size: u32) -> io::Result<String> {
let mut buf = vec![0u8; size as usize];
input.read_exact(&mut buf)?;
match str::from_utf8(buf.as_slice()) {
Ok (v) => Ok(v),
Err (e) => ???,
}
}
I've tried a few different things for the Err arm of my match with little luck. I had hoped I could just return the Result from str::from_utf8 but that didn't work. How do I take the utf8error in e and convert it into an appropriate return type for my function?
OK, I've figured it out, I needed to wrap the Error::new in Err(). I also had a type mismatch on Ok(). This is the working version of the function:
pub fn read_string<R: Read>(input: &mut R, size: u32) -> io::Result<String> {
let mut buf = vec![0u8; size as usize];
input.read_exact(&mut buf)?;
match String::from_utf8(buf) {
Ok (v) => Ok(v),
Err (e) => Err(io::Error::new(ErrorKind::Other, e))
}
}
Edited to incorporate NieDzejkob's suggestion for those who might want to reuse this code.

Rust: How to short-circuit exit from a chain of iterator methods on first Err or None?

Consider a chain of iterator methods:
.iter().a().b().c()
where a produces values of type Option (or Result). Is there a way to have the whole chain return None (or (Err(_)) as soon as a yields a None (or Err(_))?
Detailed example
Given functions valid (identifying nonsensical input) and accept (an
arbitrary selection criterion):
type T = u8;
type ERR = u8;
fn valid(x: &T) -> Result<T, ERR> {
if *x < 10 { Ok(*x) } else { Err(*x) }
}
fn accept(x: &T) -> bool {
if *x > 9 { panic!("{} should have been rejected by validator") }
*x % 2 == 0
}
I would like to write a function
fn count_accepted(data: &[T]) -> Result<usize, ERR>
which
Returns Err(ERR) as soon as the first invalid element is encountered in the
input data
If all elements are valid, returns Ok(usize) containing the count of values
that satisfied the accept criterion
Here is a solution that uses a loop:
fn count_loop(data: &[T]) -> Result<usize, ERR> {
let mut count = 0;
for item in data {
valid(&item)?;
if accept(&item) { count += 1 }
}
Ok(count)
}
which seems to work as required, as witnessed by these tests:
macro_rules! testem {
($count:path) => {
#[test] fn empty() { assert_eq!($count(&[]) , Ok(0)) }
#[test] fn all_ok_and_accepted() { assert_eq!($count(&[2,6]) , Ok(2)) }
#[test] fn all_ok_some_rejected() { assert_eq!($count(&[2,3]) , Ok(1)) }
#[test] fn one_invalid() { assert_eq!($count(&[12]) , Err(12)) }
#[test] fn stop_on_first_invalid() { assert_eq!($count(&[2,13,6,12,5]), Err(13)) }
}
}
mod test_loop {testem!{super::count_loop}}
I would like to understand whether/how one could implement this behaviour using
iterators rather than a loop.
Consider a related, but simpler problem: if any of the data are not valid, bail
out immediately, otherwise collect all the data into a vector. In other words,
remove the accept condition from the previous problem.
This problem has quite a satisfactory solution, because the FromIterator
implementation of Result takes care of early termination:
fn related(data: &[T]) -> Result<Vec<T>, ERR> {
data.iter()
.map(valid)
.collect()
}
mod test_related {
#[test]
fn stop_on_first_invalid() { assert_eq!(super::related(&[2,13,6,12,5]), Err(13))}
}
Here is an extension of related which passes the same tests as count_loop:
fn count_via_vec(data: &[T]) -> Result<usize, ERR> {
Ok(data
.iter()
.map(valid)
.filter(|x| x.is_err() || accept(&x.unwrap()))
.collect::<Result<Vec<T>, ERR>>()?
.len())
}
mod test_vvec {testem!{super::count_via_vec}}
However: this solution has a number of drawbacks with respect to count_loop:
The filter condition is very noisy.
The filtering step still needs to be performed when the first invalid item has
been identified (unlike in the original loop implementation): the ? appears
2 lines later than it should ... if that were meaningful.
A vector is unnecessarily populated (unless Rust performs some cool
optimization that I'm, as yet, unaware of), so the space complexity rises from
O(1) to O(N).
The last point would normally be addressed by replacing
.collect::<Result<Vec<T>, ERR>>()?.len()) with .count(), but this has the
further detrimental effect of removing the recognition of invalid cases: they
are simply counted as successes, as witnessed by the test failed by this
implementation:
fn count_iterate(data: &[T]) -> Result<usize, ERR> {
Ok(data
.iter()
.map(valid)
.filter(|x| x.is_err() || accept(&x.unwrap()))
.count())
}
mod test_iter {testem!{super::count_iterate}}
Can you suggest some mechanism for early returning in chains of iterator methods
that can be used in cases such as this?
You are probably looking for std::iter::Sum which has an impl<T, U, E> Sum<Result<U, E>> for Result<T, E> where T: Sum<U>, while there are also impls for Sum for all basic integers.
So the following will Just Work:
fn valid(x: &u32) -> Result<u32, u32> {
if *x < 10 { Ok(1) } else { Err(*x) }
}
fn count(x: &[u32]) -> Result<u32, u32> {
x.iter()
.map(valid)
.sum()
}
fn main() {
println!("{:?}", count(&[13,1,2,3]));
}
As the docs on Sum say, this will short-circuit the iterator if an error is encountered. This will include short-circuiting chained iterators.

What's the most idiomatic way of working with an Iterator of Results? [duplicate]

This question already has answers here:
How do I stop iteration and return an error when Iterator::map returns a Result::Err?
(4 answers)
Closed 3 years ago.
I have code like this:
let things = vec![/* ...*/]; // e.g. Vec<String>
things
.map(|thing| {
let a = try!(do_stuff(thing));
Ok(other_stuff(a))
})
.filter(|thing_result| match *thing_result {
Err(e) => true,
Ok(a) => check(a),
})
.map(|thing_result| {
let a = try!(thing_result);
// do stuff
b
})
.collect::<Result<Vec<_>, _>>()
In terms of semantics, I want to stop processing after the first error.
The above code works, but it feels quite cumbersome. Is there a better way? I've looked through the docs for something like filter_if_ok, but I haven't found anything.
I am aware of collect::<Result<Vec<_>, _>>, and it works great. I'm specifically trying to eliminate the following boilerplate:
In the filter's closure, I have to use match on thing_result. I feel like this should just be a one-liner, e.g. .filter_if_ok(|thing| check(a)).
Every time I use map, I have to include an extra statement let a = try!(thing_result); in order to deal with the possibility of an Err. Again, I feel like this could be abstracted away into .map_if_ok(|thing| ...).
Is there another approach I can use to get this level of conciseness, or do I just need to tough it out?
There are lots of ways you could mean this.
If you just want to panic, use .map(|x| x.unwrap()).
If you want all results or a single error, collect into a Result<X<T>>:
let results: Result<Vec<i32>, _> = result_i32_iter.collect();
If you want everything except the errors, use .filter_map(|x| x.ok()) or .flat_map(|x| x).
If you want everything up to the first error, use .scan((), |_, x| x.ok()).
let results: Vec<i32> = result_i32_iter.scan((), |_, x| x.ok());
Note that these operations can be combined with earlier operations in many cases.
Since Rust 1.27, Iterator::try_for_each could be of interest:
An iterator method that applies a fallible function to each item in the iterator, stopping at the first error and returning that error.
This can also be thought of as the fallible form of for_each() or as the stateless version of try_fold().
You can implement these iterators yourself. See how filter and map are implemented in the standard library.
map_ok implementation:
#[derive(Clone)]
pub struct MapOkIterator<I, F> {
iter: I,
f: F,
}
impl<A, B, E, I, F> Iterator for MapOkIterator<I, F>
where
F: FnMut(A) -> B,
I: Iterator<Item = Result<A, E>>,
{
type Item = Result<B, E>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|x| x.map(&mut self.f))
}
}
pub trait MapOkTrait {
fn map_ok<F, A, B, E>(self, func: F) -> MapOkIterator<Self, F>
where
Self: Sized + Iterator<Item = Result<A, E>>,
F: FnMut(A) -> B,
{
MapOkIterator {
iter: self,
f: func,
}
}
}
impl<I, T, E> MapOkTrait for I
where
I: Sized + Iterator<Item = Result<T, E>>,
{
}
filter_ok is almost the same:
#[derive(Clone)]
pub struct FilterOkIterator<I, P> {
iter: I,
predicate: P,
}
impl<I, P, A, E> Iterator for FilterOkIterator<I, P>
where
P: FnMut(&A) -> bool,
I: Iterator<Item = Result<A, E>>,
{
type Item = Result<A, E>;
#[inline]
fn next(&mut self) -> Option<Result<A, E>> {
for x in self.iter.by_ref() {
match x {
Ok(xx) => if (self.predicate)(&xx) {
return Some(Ok(xx));
},
Err(_) => return Some(x),
}
}
None
}
}
pub trait FilterOkTrait {
fn filter_ok<P, A, E>(self, predicate: P) -> FilterOkIterator<Self, P>
where
Self: Sized + Iterator<Item = Result<A, E>>,
P: FnMut(&A) -> bool,
{
FilterOkIterator {
iter: self,
predicate: predicate,
}
}
}
impl<I, T, E> FilterOkTrait for I
where
I: Sized + Iterator<Item = Result<T, E>>,
{
}
Your code may look like this:
["1", "2", "3", "4"]
.iter()
.map(|x| x.parse::<u16>().map(|a| a + 10))
.filter_ok(|x| x % 2 == 0)
.map_ok(|x| x + 100)
.collect::<Result<Vec<_>, std::num::ParseIntError>>()
playground
filter_map can be used to reduce simple cases of mapping then filtering. In your example there is some logic to the filter so I don't think it simplifies things. I don't see any useful functions in the documentation for Result either unfortunately. I think your example is as idiomatic as it could get, but here are some small improvements:
let things = vec![...]; // e.g. Vec<String>
things.iter().map(|thing| {
// The ? operator can be used in place of try! in the nightly version of Rust
let a = do_stuff(thing)?;
Ok(other_stuff(a))
// The closure braces can be removed if the code is a single expression
}).filter(|thing_result| match *thing_result {
Err(e) => true,
Ok(a) => check(a),
}
).map(|thing_result| {
let a = thing_result?;
// do stuff
b
})
The ? operator can be less readable in some cases, so you might not want to use it.
If you are able to change the check function to return Some(x) instead of true, and None instead of false, you can use filter_map:
let bar = things.iter().filter_map(|thing| {
match do_stuff(thing) {
Err(e) => Some(Err(e)),
Ok(a) => {
let x = other_stuff(a);
if check_2(x) {
Some(Ok(x))
} else {
None
}
}
}
}).map(|thing_result| {
let a = try!(thing_result);
// do stuff
b
}).collect::<Result<Vec<_>, _>>();
You can get rid of the let a = try!(thing); by using a match in some cases as well. However, using filter_map here doesn't seem to help.

Resources