问题
Why can't I push
to this vector during inspect
and do contains
on it during skip_while
?
I've implemented my own iterator for my own struct Chain
like this:
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)
}
}
Now what I'd like to do it take
while the iterator is producing unique values. So I'm inspect
-ing the chain and pushing to a vector and then checking it in a take_while
scope:
let mut v = Vec::with_capacity(terms);
Chain::new(i)
.inspect(|&x| {
v.push(x)
})
.skip_while(|&x| {
return v.contains(&x);
})
However, the Rust compile spits out this error:
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)
})
Obviously I don't understand the concept of "borrowing". What am I doing wrong?
回答1:
The problem here is that you're attempting to create both a mutable and an immutable reference to the same variable, which is a violation of Rust borrowing rules. And rustc actually does say this to you very clearly.
let mut v = Vec::with_capacity(terms);
Chain::new(i)
.inspect(|&x| {
v.push(x)
})
.skip_while(|&x| {
return v.contains(&x);
})
Here you're trying to use v
in two closures, first in inspect()
argument, second in skip_while()
argument. Non-move
closures capture their environment by reference, so the environment of the first closure contains &mut v
, and that of the second closure contains &v
. Closures are created in the same expression, so even if it was guaranteed that inspect()
ran and dropped the borrow before skip_while()
(which I is not the actual case, because these are iterator adapters and they won't be run at all until the iterator is consumed), due to lexical borrowing rules this is prohibited.
Unfortunately, this is one of those examples when the borrow checker is overly strict. What you can do is to use RefCell, which allows mutation through a shared reference but introduces some run-time cost:
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))
I think it may be possible to avoid runtime penalty of RefCell
and use UnsafeCell instead, because when the iterator is consumed, these closures will only run one after another, not at the same time, so there should never be a mutable and an immutable references outstanding at the same time. It could look like this:
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) })
But I may be wrong, and anyway, the overhead of RefCell
is not that high unless this code is running in a really tight loop, so you should only use UnsafeCell
as a last resort, only when nothing else works, and exercise extreme caution when working with it.
来源:https://stackoverflow.com/questions/36511683/rust-inspect-iterator-cannot-borrow-as-immutable-because-it-is-also-borrowe