Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly use `peek()` in Rust?

I am trying to do something simple. In a slice of u8, I want to find occurrence of two characters "\r\n". However, I cannot convert that slice into String using from_utf8 because parts of slice after "\r\n" may not be utf-8 and as far as possible I don't want to use from_utf8_unchecked. So I tried something like following.

fn find_crlf(text: &[u8]) -> Option<usize> {
    let mut textiter = text.iter().peekable();

    for (idx, &elem) in textiter.enumerate() {
        if Some(&elem) == Some(&b'\r') {
            if textiter.peek() == Some(&&b'\n') {
                return Some(idx);
            }
        }
    }
    None
}

And I get following compilation errors, understandably. However I am not too sure how to go about doing this. If it were a str, it was simply .find("\r\n").

Compilation Error ->

error[E0382]: borrow of moved value: `textiter`
 --> src/lib.rs:6:16
  |
2 |     let mut textiter = text.iter().peekable();
  |         ------------ move occurs because `textiter` has type `std::iter::Peekable<std::slice::Iter<'_, u8>>`, which does not implement the `Copy` trait
3 | 
4 |     for (idx, &elem) in textiter.enumerate() {
  |                         -------- value moved here
5 |         if Some(&elem) == Some(&b'\r') {
6 |             if textiter.peek() == Some(&&b'\n') {
  |                ^^^^^^^^ value borrowed here after move

May be I am missing something really simple, but stuck on this for quite some time now.

like image 483
gabhijit Avatar asked Jun 04 '20 04:06

gabhijit


1 Answers

Usually, the best way to write this sort of code is to not use Peekable at all. It's a tricky API to use because you often want to call peek when you are in the middle of iterating, which usually means you have already borrowed the iterator mutably so you can't borrow it again.

But, since you asked about Peekable specifically, you could rewrite your code to explicitly call next in a loop, which is often the only way to use peek:

fn find_crlf(text: &[u8]) -> Option<usize> {
    let mut textiter = text.iter().enumerate().peekable();
    while let Some((idx, &elem)) = textiter.next() {
        if Some(&elem) == Some(&b'\r') {
            if let Some((_, &b'\n')) = textiter.peek() {
                return Some(idx);
            }
        }
    }
    None
}

Generally, a better method for look-ahead is to use slice::windows or tuple_windows from itertools.

Given that your input is a slice, you can use slice::windows:

fn find_crlf(text: &[u8]) -> Option<usize> {
    for (idx, window) in text.windows(2).enumerate() {
        if window[0] == b'\r' && window[1] == b'\n' {
            return Some(idx);
        }
    }
    None
}

In general though, I prefer the syntax of the itertools method, because you can pattern match on the tuple which feels cleaner than indexing into a slice:

use itertools::Itertools; // 0.9.0

fn find_crlf(text: &[u8]) -> Option<usize> {
    for (idx, (&elem, &next)) in text.iter().tuple_windows().enumerate() {
        if elem == b'\r' && next == b'\n' {
            return Some(idx);
        }
    }
    None
}

Or, even better:

use itertools::Itertools; // 0.9.0

fn find_crlf(text: &[u8]) -> Option<usize> {
    text.iter()
        .tuple_windows()
        .position(|(elem, next)| elem == &b'\r' && next == &b'\n')
}
like image 105
Peter Hall Avatar answered Nov 13 '22 09:11

Peter Hall