【问题标题】:Rust Inspect Iterator: cannot borrow `*` as immutable because it is also borrowed as mutableRust Inspect Iterator:不能将 `*` 作为不可变借用,因为它也作为可变借用
【发布时间】:2016-07-30 10:19:51
【问题描述】:

为什么我不能在inspect 期间对这个向量进行push 并在skip_while 期间对它执行contains

我已经为自己的结构 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-ing 链并推送到一个向量,然后在 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)
    })

显然我不理解“借用”的概念。我做错了什么?

【问题讨论】:

  • 这个问题可能与您尝试关闭另一个范围内某处的变量有关 (v)。这在 Rust 中非常重要。所以我们需要查看 v 的来源、它的声明方式以及在当前范围内如何引用它。
  • @SimonWhitehead 已编辑以包含 v。一定是之前漏掉了。

标签: iterator rust lazy-evaluation borrowing


【解决方案1】:

这里的问题是你试图同时创建对同一个变量的可变引用和不可变引用,这违反了 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 闭包通过引用捕获它们的环境,因此第一个闭包的环境包含&amp;mut v,第二个闭包的环境包含&amp;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))

认为也许可以避免RefCell 的运行时损失,而改用UnsafeCell,因为当迭代器被消耗时,这些闭包只会一个接一个地运行,而不是在同时,所以不应该同时存在一个可变引用和一个不可变引用。它可能看起来像这样:

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 作为最后的手段,只有在没有其他方法的情况下,使用它时要格外小心。

【讨论】:

  • 这是一个很好的解释,但是,您的两个解决方案都因某种原因破坏了迭代器。我想不通。就好像skip_while 没有像往常一样停止迭代,而是永远搜索。要么是这样,要么是慢了 100 倍以上(我等了大约 1 分钟,但它从未完成)。
  • 我能够通过将所有 push/contains 逻辑移动到迭代器 next() 函数中来实现解决方案:github.com/JacksonGariety/euler.rs/blob/master/src/…
  • 这非常有效,但是,如果可能的话,我想将逻辑移动到skip_while。当我使用您的两种实现时,skip_while 要么跳过所有迭代,要么迭代器永远运行。
  • @JacksonGariety,我刚刚使用这种方法编写了一个示例程序,并理解它无法工作。当您向迭代器链询问下一个元素时,它会贯穿整个链。所以假设你在上面的链上用inspect()skip_while() 调用next()(我认为它实际上应该是带有反转条件的take_while(),否则它没有意义)。从原始迭代器中取出一个元素并将其写入集合。然后在集合中查询该元素。自然,它已经存在,所以迭代立即停止。
  • @JacksonGariety 因此,您将集合内化到迭代器中的方法是 的方法。虽然我会使用HashSet 而不是Vec,因为contains()HashSet 上比在Vec 上效率更高。
猜你喜欢
  • 2015-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-09
  • 2017-05-02
  • 2018-05-17
相关资源
最近更新 更多