Какой самый идиоматический способ работы с Итератором результатов?
У меня такой код:
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<_>, _>>()
В терминах семантики я хочу прекратить обработку после первой ошибки.
Вышеприведенный код работает, но он выглядит довольно громоздким. Есть ли способ лучше? Я просмотрел документы для чего-то вроде filter_if_ok
, но я ничего не нашел.
Я знаю collect::<Result<Vec<_>, _>>
, и он отлично работает. Я специально пытаюсь устранить следующий шаблон:
- В закрытии фильтра я должен использовать
match
на thing_result
. Я чувствую, что это должно быть просто однострочным, например .filter_if_ok(|thing| check(a))
. - Каждый раз, когда я использую
map
, я должен включить дополнительный оператор let a = try!(thing_result);
чтобы справиться с возможностью Err
. Опять же, я чувствую, что это можно .map_if_ok(|thing|...)
на .map_if_ok(|thing|...)
.
Есть ли другой подход, который я могу использовать, чтобы получить этот уровень лаконичности, или мне просто нужно его выкрутить?
Ответы
Ответ 1
Вы можете реализовать эти итераторы самостоятельно. Посмотрите, как filter
и map
реализованы в стандартной библиотеке.
реализация map_ok
:
#[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
почти то же самое:
#[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>>,
{
}
Ваш код может выглядеть так:
["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>>()
детская площадка
Ответ 2
Есть много способов, которыми вы могли бы это понимать.
Если вы просто хотите паниковать, используйте .map(|x| x.unwrap())
.
Если вы хотите получить все результаты или одну ошибку, collect
в Result<X<T>>
:
let results: Result<Vec<i32>, _> = result_i32_iter.collect();
Если вы хотите все, кроме ошибок, используйте .filter_map(|x| x.ok())
или .flat_map(|x| x)
.
Если вы хотите, чтобы все было до первой ошибки, используйте .scan((), |_, x| x.ok())
.
let results: Vec<i32> = result_i32_iter.scan((), |_, x| x.ok());
Обратите внимание, что во многих случаях эти операции можно комбинировать с более ранними операциями.
Ответ 3
Начиная с Rust 1.27, может представлять интерес Iterator::try_for_each
:
Метод итератора, который применяет ошибочную функцию к каждому элементу итератора, останавливаясь на первой ошибке и возвращая эту ошибку.
Это также можно рассматривать как for_each()
форму for_each()
или версию try_fold()
без сохранения состояния.
Ответ 4
filter_map
может использоваться для уменьшения простых случаев сопоставления, а затем фильтрации. В вашем примере есть некоторая логика для фильтра, поэтому я не думаю, что это упрощает вещи. Я не вижу никаких полезных функций в документации для Result
к сожалению. Я думаю, что ваш пример такой же идиоматический, как он мог бы получить, но вот некоторые небольшие улучшения:
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
})
?
оператор может быть менее читабельным в некоторых случаях, поэтому вы, возможно, не захотите его использовать.
Если вы можете изменить функцию check
чтобы вернуть Some(x)
вместо true, а None
вместо false, вы можете использовать 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<_>, _>>();
Вы можете избавиться от let a = try!(thing);
используя совпадение в некоторых случаях. Однако использование filter_map
здесь, похоже, не помогает.