How do I call Peekable::next based on the result o

2019-01-28 12:05发布

use std::iter::Peekable;

pub trait AdvanceWhile<I: Iterator> {
    fn advance_while<P>(&mut self, predicate: P)
    where
        P: Fn(&I::Item) -> bool;
}

impl<I: Iterator> AdvanceWhile<I> for Peekable<I> {
    fn advance_while<P>(&mut self, predicate: P)
    where
        P: Fn(&I::Item) -> bool,
    {
        while let Some(val) = self.peek() {
            if predicate(val) {
                self.next();
            } else {
                break;
            }
        }
    }
}

Playground

Error:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:16:17
   |
14 |         while let Some(val) = self.peek() {
   |                               ---- first mutable borrow occurs here
15 |             if predicate(val) {
16 |                 self.next();
   |                 ^^^^ second mutable borrow occurs here
...
20 |         }
   |         - first borrow ends here

标签: rust
5条回答
爷、活的狠高调
2楼-- · 2019-01-28 12:25

As Lukas Kalbertodt already said, this is a limitation of the borrow checker. Here I would like to show a more readable version:

fn advance_while<P>(&mut self, predicate: P)
    where P: Fn(&I::Item) -> bool 
{
    while let Some(true) = self.peek().map(&predicate) {
        self.next();
    }
}
查看更多
放我归山
3楼-- · 2019-01-28 12:25

You could rewrite the function into this:

impl<I: Iterator> AdvanceWhile<I> for Peekable<I> {
    fn advance_while<P>(&mut self, predicate: P)
        where P: Fn(&I::Item) -> bool 
    {
        loop {
            {
                let peek = match self.peek() {
                    Some(p) => p,
                    None => break,
                };
                if !predicate(peek) {
                    break;
                }
            }
            self.next();
        }
    }
}
查看更多
走好不送
4楼-- · 2019-01-28 12:32

The problem is that your original code could do this:

while let Some(val) = self.peek() {
    if predicate(val) {
        self.next();
        println!("{:?}", val);
    } else {
        break;
    }
}

Accessing val after you called next() would allow you to access memory that was no longer valid, causing memory unsafety.

As others have pointed out, your code doesn't actually do this, but current Rust uses lexical lifetimes, an overly-conservative approximation of how long a reference needs to be valid for. Future Rust should help constrain the logic without introducing unsafety.


If your value implements Copy, you can make use of that:

fn advance_while<P>(&mut self, predicate: P)
where
    P: Fn(&I::Item) -> bool,
    I::Item: Copy,
{
    while let Some(&val) = self.peek() {
        if predicate(&val) {
            self.next();
        } else {
            break;
        }
    }
}

This causes the value to be copied from the reference returned by peek. Since nothing is borrowed, the fact that you are going to mutate the iterator does not cause a problem.

If your type implements Clone, you could clone the value, again disassociating it:

fn advance_while<P>(&mut self, predicate: P)
where
    P: Fn(&I::Item) -> bool,
    I::Item: Clone,
{
    while let Some(val) = self.peek().cloned() {
        if predicate(&val) {
            self.next();
        } else {
            break;
        }
    }
}

If your type is neither Copy nor Clone, you need to introduce a temporary variable for the boolean result. This clearly informs the compiler that the borrow returned by peek isn't needed beyond the equality check statement:

fn advance_while<P>(&mut self, predicate: P)
where
    P: Fn(&I::Item) -> bool,
{
    while self.peek().map_or(false, &predicate) {
        self.next();
    }
}

See also:

查看更多
来,给爷笑一个
5楼-- · 2019-01-28 12:39

Okay so this is not the best way of doing it but it might come in handy for more complicated cases...

fn advance_while<P>(&mut self, predicate: P)
    where P: Fn(&I::Item) -> bool 
{
    while {
        if let Some(val) = self.peek() {
            predicate(val)
        } else {
            false
        }
    }
    {
        self.next();
    }
}

Playpen

查看更多
SAY GOODBYE
6楼-- · 2019-01-28 12:40

Typical case of lexical borrowing: the compiler is not yet able to understand that this is code is safe. So for the time being (until the so called non-lexical borrowing is implemented), try to rewrite your code. Like this, for example:

fn advance_while<P>(&mut self, predicate: P)
    where P: Fn(&I::Item) -> bool 
{
    loop {
        if let Some(val) = self.peek() {
            if !predicate(val) {
                break;
            }
        } else {
            break;
        }
        self.next();
    }
}
查看更多
登录 后发表回答