Conditional iterator .chain()

You know how it works: I have some code that does what it is supposed to do but I don't like looking at it... :slight_smile:

While processing a string I need to count a certain number of characters backward, and stop when some specific condition is reached. Then I process the string (except what I took from the end) but I may need the last character popped. .once() to the rescue but I can't use it in a single branch of an if because of the different type in the other branch. Right now I just "append" a fake character and then skip it while processing the string but... :nauseated_face:

Example code below and also on playground.

use std::iter::once;

fn main() {
    let s = "abcdexxx";
    
    // We have some logic here that both shortens `s` from the end and counts
    // the occurences of some characters. When we know we need to stop it is
    // too late and the first usefull character has already been popped. We
    // place it in `e`.
    let mut i = s.chars();
    
    let e = loop {
        // Logic is a bit more complex here, but this should suffice.
        if let Some(c) = i.next_back() {
            if c == 'a' {
                break None;
            }
            if c != 'x' {
                break Some(c);
            }
        }
        else {
            break None;
        }
    };
    
    let s_iter = if let Some(e) = e {
        s.chars().chain(once(e))
    }
    else {
        // Comment the .chain() to make the compiler angry.
        s.chars().chain(once('\0'))
    };
    
    for c in s_iter {
        if c == '\0' {
            continue;
        }
        // Process `c` here.
    }
}

Note that Option also implements IntoIterator, so you could get by with simply calling:

let s_iter = s.chars().chain(e);
8 Likes

you can erase the type of both the branches and use trait objects instead:

    let s_iter: &mut dyn Iterator<Item = char> = if let Some(e) = e {
        &mut s.chars().chain(once(e))
    }
    else {
        &mut s.chars()
    };

    for c in s_iter {
        todo!();
    }
3 Likes

@nerditation thank you, I usually use type erasure for this; good to know.

@tuffy did not know about Option implementing IntoIterator: it is so obvious now….

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.