带有回调的`zip`-like函数中的借用检查器问题

时间:2017-07-25 16:31:51

标签: rust borrow-checker

我试图实现一个同时遍历两个迭代器的函数,为每对调用一个函数。此回调可以通过返回(bool, bool)元组来控制每个步骤中哪些迭代器前进。由于迭代器在我的用例中引用缓冲区,因此它们无法实现stdlib中的Iterator特征,而是通过next_ref函数使用,该函数与{ {1}},但需要额外的生命周期参数。

Iterator::next
// 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)
    });
}

我明白为什么会抱怨,但无法找到解决办法。我很欣赏这个主题的任何帮助。

playground

中链接到此代码段

2 个答案:

答案 0 :(得分:5)

  

由于迭代器在我的用例中引用了一个缓冲区,它们不能从stdlib实现Iterator特征,而是通过next_ref函数使用,这是与Iterator::next相同,但需要额外的生命周期参数。

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

  

虽然标准Iterator特征的功能基于   next方法,StreamingIterator的功能基于。{   一对方法:advanceget。这基本上分裂了   next的逻辑为一半(事实上,StreamingIterator&#39; next方法   除了致电advance后跟get)之外什么都不做。

     

这是必要的,因为Rust的借词词汇处理(更多   特别是缺少单一进入,多次退出借入)。 如果   StreamingIterator被定义为Iterator只需要一个   next方法,filter等操作无法定义。

crate目前没有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)
    });
}

答案 1 :(得分:1)

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

  • next_ref的来电者接受对存储值的引用,该存储值与RefIter
  • 的生命周期相关联
  • RefIter的值必须是可变的,以便每次调用都可以递增

这完美地描述了可变别名(您正在尝试修改&#39;值&#39;同时保留对它的引用) - 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)
    });
}

RefIter版本向消费者发送Rc,而不是参考。这避免了可变别名的问题 - 通过放置来更新value 外部Rc中的新RefCell。这样做的一个副作用是消费者能够抓住一个“老年人”。即使在Rc已经提升之后,也会引用缓冲区(通过返回的RefIter)。