在类似 zip 的函数中通过回调借用检查器问题

Borrow checker issue in `zip`-like function with a callback

我正在尝试实现一个同时通过两个迭代器的函数,为每一对调用一个函数。此回调可以通过返回 (bool, bool) 元组来控制在每个步骤中推进哪个迭代器。由于迭代器在我的用例中引用了缓冲区,因此它们无法实现标准库中的 Iterator 特性,而是通过 next_ref 函数使用,这与 [=15] 相同=],但需要一个额外的生命周期参数。

// An iterator-like type, that returns references to itself
// in next_ref
struct RefIter {
    value: u64
}

impl RefIter {
    fn next_ref<'a>(&'a mut self) -> Option<&'a u64> {
        self.value += 1;
        Some(&self.value)
    }
}

// Iterate over two RefIter simultaneously and call a callback
// for each pair. The callback returns a tuple of bools
// that indicate which iterators should be advanced.
fn each_zipped<F>(mut iter1: RefIter, mut iter2: RefIter, callback: F)
    where F: Fn(&Option<&u64>, &Option<&u64>) -> (bool, bool)
{
    let mut current1 = iter1.next_ref();
    let mut current2 = iter2.next_ref();
    loop {
        let advance_flags = callback(&current1, &current2);
        match advance_flags {
            (true, true) => {
                current1 = iter1.next_ref();
                current2 = iter2.next_ref();
            },
            (true, false) => {
                current1 = iter1.next_ref();
            },
            (false, true) => {
                current2 = iter1.next_ref();
            },
            (false, false) => {
                return
            }
        }
    }
}

fn main() {
    let mut iter1 = RefIter { value: 3 };
    let mut iter2 = RefIter { value: 4 };
    each_zipped(iter1, iter2, |val1, val2| {
        let val1 = *val1.unwrap();
        let val2 = *val2.unwrap();
        println!("{}, {}", val1, val2);
        (val1 < 10, val2 < 10)
    });
}
error[E0499]: cannot borrow `iter1` as mutable more than once at a time
  --> src/main.rs:28:28
   |
22 |     let mut current1 = iter1.next_ref();
   |                        ----- first mutable borrow occurs here
...
28 |                 current1 = iter1.next_ref();
   |                            ^^^^^ second mutable borrow occurs here
...
42 | }
   | - first borrow ends here

error[E0499]: cannot borrow `iter2` as mutable more than once at a time
  --> src/main.rs:29:28
   |
23 |     let mut current2 = iter2.next_ref();
   |                        ----- first mutable borrow occurs here
...
29 |                 current2 = iter2.next_ref();
   |                            ^^^^^ second mutable borrow occurs here
...
42 | }
   | - first borrow ends here

error[E0499]: cannot borrow `iter1` as mutable more than once at a time
  --> src/main.rs:32:28
   |
22 |     let mut current1 = iter1.next_ref();
   |                        ----- first mutable borrow occurs here
...
32 |                 current1 = iter1.next_ref();
   |                            ^^^^^ second mutable borrow occurs here
...
42 | }
   | - first borrow ends here

error[E0499]: cannot borrow `iter1` as mutable more than once at a time
  --> src/main.rs:35:28
   |
22 |     let mut current1 = iter1.next_ref();
   |                        ----- first mutable borrow occurs here
...
35 |                 current2 = iter1.next_ref();
   |                            ^^^^^ second mutable borrow occurs here
...
42 | }
   | - first borrow ends here

我明白它为什么抱怨,但找不到解决方法。如果您对此问题有任何帮助,我将不胜感激。

Link 到 playground 中的这个片段。

此代码的问题在于 RefIter 以两种方式使用,这两种方式在根本上是相互矛盾的:

  • next_ref 的调用者收到对存储值的引用,该值与 RefIter
  • 的生命周期相关
  • RefIter 的值需要可变,以便在每次调用时递增

这完美地描述了可变别名(你试图修改 'value' 而持有对它的引用)——Rust 明确设计要防止的事情。

为了使 each_zipped 正常工作,您需要修改 RefIter 以避免分发对您希望改变的数据的引用。 我在下面使用 RefCellRc:

的组合实现了一种可能性
use std::cell::RefCell;
use std::rc::Rc;

// An iterator-like type, that returns references to itself
// in next_ref
struct RefIter {
    value: RefCell<Rc<u64>>
}


impl RefIter {
    fn next_ref(&self) -> Option<Rc<u64>> {
        let new_val = Rc::new(**self.value.borrow() + 1);
        *self.value.borrow_mut() = new_val;
        Some(Rc::clone(&*self.value.borrow()))
    }
}


// Iterate over two RefIter simultaniously and call a callback
// for each pair. The callback returns a tuple of bools
// that indicate which iterators should be advanced.
fn each_zipped<F>(iter1: RefIter, iter2: RefIter, callback: F)
    where F: Fn(&Option<Rc<u64>>, &Option<Rc<u64>>) -> (bool, bool)
{
    let mut current1 = iter1.next_ref();
    let mut current2 = iter2.next_ref();
    loop {
        let advance_flags = callback(&current1, &current2);
        match advance_flags {
            (true, true) => {
                current1 = iter1.next_ref();
                current2 = iter2.next_ref();
            },
            (true, false) => {
                current1 = iter1.next_ref();
            },
            (false, true) => {
                current2 = iter1.next_ref();
            },
            (false, false) => {
                return
            }
        }
    }
}

fn main() {
    let iter1 = RefIter { value: RefCell::new(Rc::new(3)) };
    let iter2 = RefIter { value: RefCell::new(Rc::new(4)) };
    each_zipped(iter1, iter2, |val1, val2| {
        // We can't use unwrap() directly, since we're only passed a reference to an Option
        let val1 = **val1.iter().next().unwrap();
        let val2 = **val2.iter().next().unwrap();
        println!("{}, {}", val1, val2);
        (val1 < 10, val2 < 10)
    });
}

此版本的 RefIterRc 分发给消费者,而不是参考资料。这避免了可变别名的问题 - 更新 value 是通过放置 一个新的Rc变成了外RefCell。这样做的一个副作用是消费者能够保留对缓冲区的 'old' 引用(通过返回的 Rc),即使在 RefIter 已经前进之后。

Since the iterators take a reference to a buffer in my use case, they can't implement the Iterator trait from the stdlib, but instead are used though a next_ref function, which is identical to Iterator::next, but takes an additional lifetime parameter.

您描述的是流式迭代器。为此有一个板条箱,恰当地称为 streaming_iterator。文档描述了你的问题(强调我的):

While the standard Iterator trait's functionality is based off of the next method, StreamingIterator's functionality is based off of a pair of methods: advance and get. This essentially splits the logic of next in half (in fact, StreamingIterator's next method does nothing but call advance followed by get).

This is required because of Rust's lexical handling of borrows (more specifically a lack of single entry, multiple exit borrows). If StreamingIterator was defined like Iterator with just a required next method, operations like filter would be impossible to define.

该板条箱目前没有 zip 功能,当然也没有您所描述的变体。但是,它很容易实现:

extern crate streaming_iterator;

use streaming_iterator::StreamingIterator;

fn each_zipped<A, B, F>(mut iter1: A, mut iter2: B, callback: F)
where
    A: StreamingIterator,
    B: StreamingIterator,
    F: for<'a> Fn(Option<&'a A::Item>, Option<&'a B::Item>) -> (bool, bool),
{
    iter1.advance();
    iter2.advance();

    loop {
        let advance_flags = callback(iter1.get(), iter2.get());
        match advance_flags {
            (true, true) => {
                iter1.advance();
                iter2.advance();
            }
            (true, false) => {
                iter1.advance();
            }
            (false, true) => {
                iter1.advance();
            }
            (false, false) => return,
        }
    }
}

struct RefIter {
    value: u64
}

impl StreamingIterator for RefIter {
    type Item = u64;

    fn advance(&mut self) {
        self.value += 1;
    }

    fn get(&self) -> Option<&Self::Item> {
        Some(&self.value)
    }
}

fn main() {
    let iter1 = RefIter { value: 3 };
    let iter2 = RefIter { value: 4 };
    each_zipped(iter1, iter2, |val1, val2| {
        let val1 = *val1.unwrap();
        let val2 = *val2.unwrap();
        println!("{}, {}", val1, val2);
        (val1 < 10, val2 < 10)
    });
}