Pioneer_11
Pioneer_11

Reputation: 1280

rust early return from within an iterator

I am trying to write a function that takes in an input string and does some operation on it only if the string is alphanumeric, currently my code is:

    fn update(&mut self, input_string: String) -> bool {
        let chars = {
            let mut chars = Vec::new();
            for c in input_string.chars() {
                if !c.is_alphanumeric() {
                    return false
                } else {
                    chars.push(c);
                }
            }
            chars
        };
        // do something with chars 
        todo!()
    }

This does work, but I would prefer to do this with iterators rather than loops as I generally find the code cleaner and easier to work with. However, I can't find a way to early return from within an iterator e.g.

    fn update(&mut self, input_string: String) -> bool {
        let chars = input_string.chars().into_iter().map(|c| {
            if c.is_alphanumeric() {
                c
            } else {
                return false
            }
        });

        // do something with chars 
        todo!()
    } 

doesn't compile. How can I do an early return from within an iterator

Upvotes: 0

Views: 81

Answers (3)

Nikolay
Nikolay

Reputation: 1959

If you want do the operation on chars, but stop it as soon as you get the first non-alphanumeric character consider using one of Iterator::try_* functions.

For example, Iterator::try_fold:

fn foo(input_string: &str) -> Option<Vec<char>> {
    input_string
        .chars()
        .try_fold(vec![], |mut chars, c| {
            if c.is_alphanumeric() {
                dbg!("alphanumeric", &c);
                chars.push(c);
                Some(chars)
            } else {
                dbg!("non-alphanumeric", &c);
                None
           }
        })
}

fn main() {
    dbg!(foo("Hello, world!"));
    dbg!(foo("Helloworld"));
}

Here we process the string char by char, but stop immediately on condition failure, returning None for non-alphanumeric strings and Some(...) for successful processing.

Upvotes: 0

prog-fh
prog-fh

Reputation: 16910

I'm not certain it is more readable than the explicit loop, but here is my attempt for an iterator-only version.

Some remarks:

  • since you plan to work on the vector of chars built by the function, I suppose you don't need to own the input string (except if you want to store it); a string-slice-reference (&str) is probably enough as the parameter,
  • we can allocate once for all instead of risking reallocations as the vector grows; the largest allocation occurs when all chars are alphanumeric and are made of a single byte in utf-8,
  • pushing the char before testing its class is not a problem since we will return false immediately without using chars,
  • if we absolutely want to make chars immutable, we can start the second part with let chars = chars.
fn with_iterator(input_string: &str) -> bool {
    let mut chars = Vec::with_capacity(input_string.len()); // worst case
    input_string.chars().all(|c| {
        chars.push(c);
        c.is_alphanumeric()
    }) && /* short-circuit */ {
        // do something with chars
        print!(" {:?}", chars);
        true
    }
}

fn explicit_loop(input_string: &str) -> bool {
    let chars = {
        let mut chars = Vec::new();
        for c in input_string.chars() {
            if !c.is_alphanumeric() {
                return false;
            } else {
                chars.push(c);
            }
        }
        chars
    };
    // do something with chars
    print!(" {:?}", chars);
    true
}

fn main() {
    for s in ["James", "Bond-007", "Bond007"] {
        println!("input_string {:?}", s);
        print!("• iter");
        println!(" {}", with_iterator(s));
        print!("• loop");
        println!(" {}", explicit_loop(s));
    }
}
/*
input_string "James"
• iter ['J', 'a', 'm', 'e', 's'] true
• loop ['J', 'a', 'm', 'e', 's'] true
input_string "Bond-007"
• iter false
• loop false
input_string "Bond007"
• iter ['B', 'o', 'n', 'd', '0', '0', '7'] true
• loop ['B', 'o', 'n', 'd', '0', '0', '7'] true
*/

Upvotes: 0

Babur Makhmudov
Babur Makhmudov

Reputation: 532

Iterator trait provides a lot of cool methods for tackling this issue, one of them is Iterator::all, and you can you use it this way:

fn update(input: String) -> bool {
    if !input.chars().all(char::is_alphanumeric) {
        return false;
    }
    // do something with original input, it's still intact
    todo!()
}
// or ...
fn updatecool(input: String) -> Option<()> {
    // one liner :)
    input.chars().all(char::is_alphanumeric).then_some(())?;
    // do something with original input, it's still intact
    todo!()
}

all method will short circuit on first failed test and return early

Upvotes: 0

Related Questions