2016-04-02 16 views
3

Какой самый идиоматический способ работы с Итератором результатов? У меня есть код:Ржавчина: фильтрация и отображение итератора результатов

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| ...).

Есть ли другой подход, который я могу использовать для получения этого уровня лаконичности, или мне просто нужно его выкрутить?

+0

http://stackoverflow.com/questions/26368288/dealing-with-result-within-iter? – ArtemGr

ответ

4

Вы можете реализовать эти итераторы самостоятельно. Посмотрите, как filter и map реализованы в stdlib.

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>>() 

playpen


Устаревшие:

Использование take_while итератора:

[Ok(1),Ok(2),Err(3),Ok(4)] 
    .iter().cloned() 
    .take_while(|x| x.is_ok()) 
    .collect::<Vec<Result<u32,u32>>>() 

//[Ok(1), Ok(2)] 

Также вы можете использовать FromIterator признак реализованный для Result:

[Ok(1),Ok(2),Err(3),Ok(4)] 
    .iter().cloned() 
    .collect::<Result<Vec<u32>,u32>>() 

//Err(3) 

[Ok(1),Ok(2),Ok(3),Ok(4)] 
    .iter().cloned() 
    .collect::<Result<Vec<u32>,u32>>() 

//Ok([1, 2, 3, 4]) 
+0

Необходим ли '.cloned'? Это кажется отвлекающим здесь. –

+0

Извините, я должен был быть более ясным в своем вопросе, что знаю «собирать»: <Результат , _ >> '. См. Править. –

+0

@Matthieu M. Странно, но 'in_iter()' дает ссылку вместо значения здесь. – aSpex

2

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 
}) 

Оператор ? может быть менее читаемым в некоторых случаях, так что вы, возможно, не хотите его использовать.

Edit: Если вы можете изменить функцию check вернуть Some(x) вместо истинно, и None вместо ложного, вы можете использовать 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 здесь, похоже, не помогает.

8

Есть много способов, которыми Вы могли бы это значит.

Если вы просто хотите паниковать, используйте .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()); 

Обратите внимание, что эти операции могут быть объединены с более ранними операциями во многих случаях.

+0

Извините, я должно быть яснее в моем вопросе, что я знаю «.collect :: <Результат , _ >>' и его семантика верна для моей ситуации. См. Править. –

+0

Упрощенный для рекомендации метода 'filter_map'. Не знал об этом, это просто! –