1

为什么我不能push在这个向量上inspectcontains这个过程skip_while

我已经为自己的结构实现了自己的迭代器,Chain如下所示:

struct Chain {
    n: u32,
}

impl Chain {
    fn new(start: u32) -> Chain {
        Chain { n: start }
    }
}

impl Iterator for Chain {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        self.n = digit_factorial_sum(self.n);
        Some(self.n)
    }
}

take现在,当迭代器产生唯一值时,我想做什么。所以我正在inspect处理链并推送到一个向量,然后在一个take_while范围内检查它:

let mut v = Vec::with_capacity(terms);
Chain::new(i)
    .inspect(|&x| {
        v.push(x)
    })
    .skip_while(|&x| {
        return v.contains(&x);
    })

然而,Rust 编译吐出这个错误:

error: cannot borrow `v` as immutable because it is also borrowed as mutable [E0502]
...
borrow occurs due to use of `v` in closure
    return v.contains(&x);
           ^
previous borrow of `v` occurs here due to use in closure; the mutable borrow prevents subsequent moves, borrows, or modification of `v` until the borrow ends
    .inspect(|&x| {
        v.push(x)
    })

显然我不明白“借”的概念。我究竟做错了什么?

4

1 回答 1

3

这里的问题是您试图创建对同一变量的可变引用和不可变引用,这违反了 Rust 借用规则。rustc 实际上确实非常清楚地向您说明了这一点。

let mut v = Vec::with_capacity(terms);
Chain::new(i)
    .inspect(|&x| {
        v.push(x)
    })
    .skip_while(|&x| {
        return v.contains(&x);
    })

在这里,您尝试v在两个闭包中使用,第一个在inspect()参数中,第二个在skip_while()参数中。非move闭包通过引用捕获它们的环境,因此第一个闭包的环境包含&mut v,而第二个闭包的环境包含&v。闭包是在同一个表达式中创建的,所以即使保证inspect()之前运行并删除了借用skip_while()(我不是实际情况,因为这些是迭代器适配器,在迭代器被消耗之前它们根本不会运行) ,由于词汇借用规则,这是被禁止的。

不幸的是,这是借用检查器过于严格的例子之一。您可以做的是使用RefCell,它允许通过共享引用进行变异,但会引入一些运行时成本:

use std::cell::RefCell;

let mut v = RefCell::new(Vec::with_capacity(terms));
Chain::new(i)
    .inspect(|x| v.borrow_mut().push(*x))
    .skip_while(|x| v.borrow().contains(x))

认为可能可以避免运行时惩罚RefCellUnsafeCell改为使用,因为当迭代器被消耗时,这些闭包只会一个接一个地运行,而不是同时运行,所以永远不应该有一个可变引用和一个不可变引用在同时。它可能看起来像这样:

use std::cell::UnsafeCell;

let mut v = UnsafeCell::new(Vec::with_capacity(terms));
Chain::new(i)
    .inspect(|x| unsafe { (&mut *v.get()).push(*x) })
    .skip_while(|x| unsafe { (&*v.get()).contains(x) })

但我可能错了,无论如何,除非这段代码在一个非常紧密的循环RefCell中运行,否则开销并没有那么高,所以你应该只作为最后的手段,只有在没有其他方法的情况下才使用,并且在使用时要格外小心它。UnsafeCell

于 2016-04-09T07:27:16.663 回答