Search code examples
genericsrustiteratortraitsrust-result

Extending all Iterators<Item = Result<Type, E>> to transform type


I'm trying to implement an extension trait for all Iterator<Item = Result<Type, E>> where E is generic, to generate another Iterator over Result<OtherType, E>, where the errors from the original are forwarded.

The problem is, that the transformation Type -> OtherType may fail (the function is f(t: Type) -> Result<OtherType, ConcreteError>.

Therefor, the iteration might return E (generic) from the underlying iterator or a concrete error type, which is of course not possible.

How to implement this?

A minimal example:

pub struct A;
pub struct B;
pub struct CongreteError;

fn transform(a: A) -> Result<B, CongreteError> {
    Ok(B {})
}

pub struct ExtensionIter<E>(Box<Iterator<Item = Result<A, E>>>);

impl<E> Iterator for ExtensionIter<E> {
    type Item = Result<B, E>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.0.next() {
            Some(Ok(a)) => Some(transform(a)),
            Some(Err(e)) => Some(Err(e)),
            None => None,
        }
    }
}

pub trait Extension<E> {
    fn extend(self) -> ExtensionIter<E>;
}

impl<E, I> Extension<E> for I
where
    I: Iterator<Item = Result<A, E>>,
{
    fn extend(self) -> ExtensionIter<E> {
        ExtensionIter(Box::new(self))
    }
}

fn main() {
    let v: Vec<A> = vec![];
    for element in v.iter().extend() {
        match element {
            Ok(b) => {}
            Err(e) => {}
        }
    }
}

playground

Errors:

error[E0308]: mismatched types
  --> src/main.rs:16:33
   |
16 |             Some(Ok(a)) => Some(transform(a)),
   |                                 ^^^^^^^^^^^^ expected type parameter, found struct `CongreteError`
   |
   = note: expected type `std::result::Result<_, E>`
              found type `std::result::Result<_, CongreteError>`
   = help: here are some functions which might fulfill your needs:
           - .map_err(...)
           - .or(...)
           - .or_else(...)

error[E0310]: the parameter type `I` may not live long enough
  --> src/main.rs:32:23
   |
27 | impl<E, I> Extension<E> for I
   |         - help: consider adding an explicit lifetime bound `I: 'static`...
...
32 |         ExtensionIter(Box::new(self))
   |                       ^^^^^^^^^^^^^^
   |
note: ...so that the type `I` will meet its required lifetime bounds
  --> src/main.rs:32:23
   |
32 |         ExtensionIter(Box::new(self))
   |                       ^^^^^^^^^^^^^^

error[E0599]: no method named `extend` found for type `std::slice::Iter<'_, A>` in the current scope
  --> src/main.rs:38:29
   |
38 |     for element in v.iter().extend() {
   |                             ^^^^^^
   |
   = note: the method `extend` exists but the following trait bounds were not satisfied:
           `std::slice::Iter<'_, A> : Extension<_>`
           `&std::slice::Iter<'_, A> : Extension<_>`
           `&mut std::slice::Iter<'_, A> : Extension<_>`
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `extend`, perhaps you need to implement it:
           candidate #1: `Extension`

Solution

  • I highly recommend reading The Rust Programming Language. It is packed full of information that new Rust programmers should know.

    might return E [...] or a concrete error type

    One of Rust's exciting features are enums. An enum allows you to create a type that can be one of multiple other types. In this case, we can define an enum to be either the underlying error or our own:

    pub enum ExtensionError<E> {
        Original(E),
        Concrete(ConcreteError),
    }
    

    Then it's just a matter of mapping from one type to another:

    pub struct A;
    pub struct B;
    pub struct ConcreteError;
    
    fn transform(_: A) -> Result<B, ConcreteError> {
        Ok(B {})
    }
    
    pub struct ExtensionIter<I>(I);
    pub enum ExtensionError<E> {
        Original(E),
        Concrete(ConcreteError),
    }
    
    impl<I, E> Iterator for ExtensionIter<I>
    where
        I: Iterator<Item = Result<A, E>>,
    {
        type Item = Result<B, ExtensionError<E>>;
    
        fn next(&mut self) -> Option<Self::Item> {
            match self.0.next() {
                Some(Ok(a)) => Some(transform(a).map_err(ExtensionError::Concrete)),
                Some(Err(e)) => Some(Err(ExtensionError::Original(e))),
                None => None,
            }
        }
    }
    
    pub trait Extension: Iterator {
        fn extend(self) -> ExtensionIter<Self>
        where
            Self: Sized,
        {
            ExtensionIter(self)
        }
    }
    
    impl<I: Iterator> Extension for I {}
    
    fn main() {
        let v: Vec<Result<A, ()>> = vec![];
        for element in v.into_iter().extend() {
            match element {
                Ok(_) => {}
                Err(_) => {}
            }
        }
    }