how to get element without consuming iterator int rust(problem with rewriting strtol in rust)

2.6k Views Asked by At

I implement strtol in Rust like this:

fn strtol(chars: &mut Chars<'_>) -> i64 {
    let mut result: i64 = 0;
    loop {
        match chars.next() {
            Some(c) => {
                match c.to_digit(10) {
                    Some(i) => result = result * 10 + i64::from(i),
                    None => break,
                }
            },
            None => break,
        }
    }
    result
}

the problem is that after runing strtol, the iterator point to the second character after number digit, which should point at the first character after number digit. For example, if input "1234abc", after calling strtol, iterator point to b which should be a.

2

There are 2 best solutions below

1
Aplet123 On BEST ANSWER

Your code fails because you look at chars.next to see if it's a valid digit or not then stops if it isn't. This means that the first non-digit will be consumed, as you observed. To fix this, you can pass in a Peekable:

use std::iter::Peekable;

fn strtol<I: Iterator<Item = char>>(chars: &mut Peekable<I>) -> i64 {
    let mut result: i64 = 0;
    loop {
        // first peek the element here
        match chars.peek() {
            Some(c) => match c.to_digit(10) {
                Some(i) => result = result * 10 + i64::from(i),
                None => break,
            },
            None => break,
        }
        // at this point we know it's a digit so we consume it
        chars.next();
    }
    result
}

fn main() {
    let test = "1234abcd";
    let mut iter = test.chars().peekable();
    println!("{}", strtol(&mut iter)); // 1234
    println!("{}", iter.collect::<String>()); // abcd
}

Playground link

1
Masklinn On

You can't, really, because that's not how Iterator works.

You could require a Peekable, which would let you peek() the iterator without consuming the item, but that's about it I think: AFAIK while Rust has DoubleEndedIterator that lets you iterate from the back, it doesn't have bidirectional iterators (which let you move / adjust the iterator backwards), at least not that I know.

Though I'm not sure why you're taking a Chars input either, why not e.g. take an &str and have strtol return a tuple of the extracted number and the "rest" as a slice? That seems more Rust-ish.

Incidentally your loop could be simplified by using while let:


while let Some(c) = chars.next()
    match c.to_digit(10) {
        Some(i) => result = result * 10 + i64::from(i),
        None => break,
    }
}