Rust Inspect Iterator: cannot borrow `*` as immutable because it is also borrowed as mutable

試著忘記壹切 提交于 2021-01-28 18:27:13

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!