在迭代另一个属性时修改结构的一个属性
Modifying one attribute of a struct while iterating over another attribute
我有一个包含 2 个 Vec
的结构。我希望能够在修改另一个的同时迭代一个。这是一个示例程序:
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
)。有没有办法编写我的程序来演示这种分离,并允许这种程序编译?
这是一个较大程序的简化示例,因此我需要保持一般结构相同(一个包含一些东西的结构,其中一个将被迭代,另一个将被更新)。
如果使用 s.a.it
而不是 s.a_iter()
,则可以消除错误。
您当前的解决方案不起作用,因为从 s.a_iter()
返回的迭代器保留了 s
的引用,它与 s
本身具有相同的生命周期,因此在该引用存在之前,您不能借用s
中的可变内容。发生这种情况的具体原因是:
the compiler stops at the function call boundary when evaluating
generic parameters
(你的生命周期)
这里有一个很好的答案,其中包含对一个非常相似的问题的完整解释:
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);
使用原始指针,您可以将结构别名化为第二个变量——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);
}
我欢迎对此提出第二个意见,但我看不出它如何导致任何内存安全问题,至少在这个例子中是这样。
根本问题是借用检查器没有足够的信息来证明您的代码是安全的;它在功能边界处停止。您可以通过编写一个拆分引用的方法来解决这个问题,这样编译器 确实 拥有它需要的信息:
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);
}
这不是非常低效;此方法引入绝对没有堆activity;我们只是在调整指针。
我想我在使用宏时遇到了 "solved" 这个问题。如果我使用以下代码,它会起作用:
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_iter
和b_push
代码移动到一个宏中。当代码被编译时,宏将被扩展,就好像我们没有使用抽象函数一样。然而,为了编写代码,该功能被抽象掉了。
我不确定他们是好主意还是坏主意。
我有一个包含 2 个 Vec
的结构。我希望能够在修改另一个的同时迭代一个。这是一个示例程序:
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
)。有没有办法编写我的程序来演示这种分离,并允许这种程序编译?
这是一个较大程序的简化示例,因此我需要保持一般结构相同(一个包含一些东西的结构,其中一个将被迭代,另一个将被更新)。
如果使用 s.a.it
而不是 s.a_iter()
,则可以消除错误。
您当前的解决方案不起作用,因为从 s.a_iter()
返回的迭代器保留了 s
的引用,它与 s
本身具有相同的生命周期,因此在该引用存在之前,您不能借用s
中的可变内容。发生这种情况的具体原因是:
the compiler stops at the function call boundary when evaluating generic parameters
(你的生命周期)
这里有一个很好的答案,其中包含对一个非常相似的问题的完整解释: 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);
使用原始指针,您可以将结构别名化为第二个变量——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);
}
我欢迎对此提出第二个意见,但我看不出它如何导致任何内存安全问题,至少在这个例子中是这样。
根本问题是借用检查器没有足够的信息来证明您的代码是安全的;它在功能边界处停止。您可以通过编写一个拆分引用的方法来解决这个问题,这样编译器 确实 拥有它需要的信息:
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);
}
这不是非常低效;此方法引入绝对没有堆activity;我们只是在调整指针。
我想我在使用宏时遇到了 "solved" 这个问题。如果我使用以下代码,它会起作用:
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_iter
和b_push
代码移动到一个宏中。当代码被编译时,宏将被扩展,就好像我们没有使用抽象函数一样。然而,为了编写代码,该功能被抽象掉了。
我不确定他们是好主意还是坏主意。