迭代结构的一个属性,同时迭代另一个属性

时间:2015-07-02 19:48:09

标签: iterator rust borrow-checker

我的结构有2 Vec s。我希望能够在修改另一个时迭代一个。这是一个示例程序:

use std::slice;

struct S {
    a: Vec<i32>,
    b: Vec<i32>
}

impl S {
    fn a_iter<'a>(&'a self) -> slice::Iter<i32>  {
        self.a.iter()
    }
    fn a_push(&mut self, val: i32) {
        self.a.push(val);
    }
    fn b_push(&mut self, val: i32) {
        self.b.push(val);
    }
}

fn main() {
    let mut s = S { a: Vec::new(), b: Vec::new() };
    s.a_push(1);
    s.a_push(2);
    s.a_push(3);

    for a_val in s.a_iter() {
        s.b_push(a_val*2);
    }
}

但是存在这个编译器错误:

$ rustc iterexample.rs 
iterexample.rs:28:9: 28:10 error: cannot borrow `s` as mutable because it is also borrowed as immutable
iterexample.rs:28         s.b_push(a_val*2);
                           ^
note: in expansion of for loop expansion
 iterexample.rs:26:5: 29:6 note: expansion site
iterexample.rs:26:18: 26:19 note: previous borrow of `s` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `s` until the borrow ends
iterexample.rs:26     for a_val in s.a_iter() {
                                   ^
note: in expansion of for loop expansion
iterexample.rs:26:5: 29:6 note: expansion site
iterexample.rs:29:6: 29:6 note: previous borrow ends here
iterexample.rs:26     for a_val in s.a_iter() {
iterexample.rs:27         println!("Looking at {}", a_val);
iterexample.rs:28         s.b_push(a_val*2);
iterexample.rs:29     }
                      ^
note: in expansion of for loop expansion
iterexample.rs:26:5: 29:6 note: expansion site
error: aborting due to previous error

我理解编译器在抱怨什么。我在for循环中借用了self,因为我仍在循环它。

虽然应该有一种方法可以做到这一点。我只修改s.b,而不是修改我正在循环的东西(s.a)。反正有没有编写我的程序来说明这种分离,并允许这种程序编译?

这是一个较大程序的简化示例,因此我需要保持一般结构相同(一个具有某些东西的结构,其中一个将被迭代,另一个将被更新)。

4 个答案:

答案 0 :(得分:4)

如果您使用s.a.it而不是s.a_iter(),则可以删除错误。 您当前的解决方案无法正常工作,因为s.a_iter()返回的迭代器保留了s的引用,该引用具有与s本身相同的生命周期,因此在该引用存活之前,您不能在s内借用可变的东西。具体来说,这是因为:

  

编译时,编译器在函数调用边界处停止   通用参数

(你的生命周期)

这里有一个很好的答案,其中包含一个非常类似问题的完整解释: cannot borrow `self.x` as immutable because `*self` is also borrowed as mutable

修改

一种可能的解决方案是将操作置于S内,而不是从S中带出迭代器。您可以在S中定义一个这样的方法:

fn foreach_in_a_push_to_b<F>(&mut self, func: F) where F : Fn(&i32) -> i32 {
    for a_val in self.a.iter() {
        self.b.push(func(a_val));
    }
}

然后

s.foreach_in_a_push_to_b(|&x| x * 2);

答案 1 :(得分:2)

根本问题在于借阅检查器没有足够的信息来证明您的代码是安全的;它停在功能边界。您可以通过编写一个分割引用的方法来解决这个问题,以便编译器 获得所需的信息:

struct S {
    a: Vec<i32>,
    b: Vec<i32>
}

impl S {
    fn a_push(&mut self, val: i32) {
        self.a.push(val);
    }
    fn split_a_mut_b<'a>(&'a mut self) -> (&'a Vec<i32>, &'a mut Vec<i32>) {
        (&self.a, &mut self.b)
    }
}

fn main() {
    let mut s = S { a: Vec::new(), b: Vec::new() };
    s.a_push(1);
    s.a_push(2);
    s.a_push(3);

    let (a, b) = s.split_a_mut_b();

    for a_val in a.iter() {
        b.push(a_val*2);
    }
}

这里的关键是在split_a_mut_b内,编译器可以证明两个借位不重叠。您可以使用的另一种模式,即允许您保留更多原始API,是暂时将值反汇编为可变和不可变的部分:

use std::slice;

#[derive(Debug)]
struct S {
    a: Vec<i32>,
    b: Vec<i32>
}

impl S {
    fn a_iter(&self) -> slice::Iter<i32>  {
        self.a.iter()
    }
    fn a_push(&mut self, val: i32) {
        self.a.push(val);
    }
    fn b_push(&mut self, val: i32) {
        self.b.push(val);
    }
    fn split_a_mut_b<F, R>(&mut self, f: F) -> R
    where F: FnOnce(&Self, &mut Self) -> R {
        use std::mem::swap;

        // Break off the mutable part(s) (or the immutable parts if there
        // are less of those).
        let mut temp = S { a: vec![], b: vec![] };
        swap(&mut self.b, &mut temp.b);

        // Call the closure.
        let result = f(self, &mut temp);

        // Glue the value back together.
        swap(&mut self.b, &mut temp.b);

        result
    }
}

fn main() {
    let mut s = S { a: Vec::new(), b: Vec::new() };
    s.a_push(1);
    s.a_push(2);
    s.a_push(3);

    s.split_a_mut_b(|imm, muta| {
        for a_val in imm.a_iter() {
            muta.b_push(a_val*2);
        }
    });

    println!("{:?}", s);
}

非常低效;这个方法引入绝对没有堆活动;我们只是在改变指针。

答案 2 :(得分:0)

使用原始指针,您可以将结构别名为第二个变量--Rust会将它们视为两个不同的变量,让您在不抱怨的情况下借用不可变部分。

let s_alias = &s as *const S;
let a_iter = unsafe { (*s_alias).a_iter() };

for a_val in a_iter {
    s.b_push(a_val*2);
}

Playground

我欢迎对此提出第二意见,但我不知道它是如何导致任何内存安全问题的,至少在这个例子中。

答案 3 :(得分:0)

我认为我已经使用宏“解决了”这个问题。如果我使用以下代码,它可以工作:

use std::slice;

struct S {
    a: Vec<i32>,
    b: Vec<i32>
}

impl S {
    fn a_push(&mut self, val: i32) {
        self.a.push(val);
    }
}

macro_rules! a_iter {
    ($x: expr) => {
        { $x.a.iter() }
    }
}

macro_rules! b_push {
    ($x: expr, $val: expr) => {
        $x.b.push($val);
    }
}

fn main() {
    let mut s = S { a: Vec::new(), b: Vec::new() };
    s.a_push(1);
    s.a_push(2);
    s.a_push(3);

    let iter = a_iter!(s);

    for a_val in iter {
        println!("Looking at {}", a_val);
        b_push!(s, a_val*2);
    }
}

在此,我已将a_iterb_push代码移至宏中。编译代码时,宏将被扩展,就好像我们没有使用抽象函数一样。但是,为了编写代码,该功能被抽象掉了。

我不确定它们是好还是坏。