Rust从结构体Vec提取嵌套Vec时如何解决借用检查报错
问题背景
我正在为一门玩具语言开发编译器,需要实现逐文件错误检查能力。我定义了MooFile结构体,其包含Vec<anyhow::Error>类型字段,遇到错误时就会存入该字段。现在我需要遍历所有文件,提取其中的错误并扁平化为单个向量作为编译器返回值,但实现过程中一直被借用检查器拦截,以下是多次尝试的实现代码及对应的编译报错:
第一次尝试:不可变迭代器下修改字段
fn compile(&mut self) -> Result<(), Vec<Error>> { ... // Check for errors let has_errors = self.files .iter() .map(|file| file.has_errors()) .reduce(|acc, value| acc | value) .unwrap_or(false); if has_errors { let mut errors = vec![]; self.files .iter() .for_each(|&mut file| errors.append(&mut file.errors)); Err(errors) } else { Ok(()) } }
编译报错:
error[E0596]: cannot borrow `file.errors` as mutable, as it is behind a `&` reference --> src\lib.rs:62:48 | 62 | .for_each(|file| errors.append(&mut file.errors)); | ---- ^^^^^^^^^^^^^^^^ `file` is a `&` reference, so the data it refers to cannot be borrowed as mutable | | | help: consider changing this to be a mutable reference: `&mut MooFile`
报错原因:iter()返回的是不可变引用&MooFile,没有修改内部字段、移走字段值的权限。
第二次尝试:调用into_iter()获取files所有权
fn compile(&mut self) -> Result<(), Vec<Error>> { ... // Check for errors let has_errors = self.files .iter() .map(|file| file.has_errors()) .reduce(|acc, value| acc | value) .unwrap_or(false); if has_errors { let mut errors = vec![]; self.files .into_iter() .for_each(|mut file| errors.append(&mut file.errors)); Err(errors) } else { Ok(()) } }
编译报错:
error[E0507]: cannot move out of `self.files` which is behind a mutable reference --> src\lib.rs:63:13 | 63 | self.files | ^^^^^^^^^^ move occurs because `self.files` has type `Vec<MooFile>`, which does not implement the `Copy` trait 64 | .into_iter() | ----------- `self.files` moved due to this method call
报错原因:compile方法仅持有&mut self可变引用,没有self的完整所有权,对self.files调用into_iter()会将整个files字段的所有权从结构体中移走,违反借用规则。
第三次尝试:直接映射errors字段
fn compile(&mut self) -> Result<(), Vec<Error>> { ... // Check for errors let errors: Vec<Error> = self.files .iter() .map(|file| file.errors) .flatten() .collect(); if errors.len() > 0 { Err(errors) } else { Ok(()) } }
编译报错:
error[E0507]: cannot move out of `file.errors` which is behind a shared reference --> src\lib.rs:54:25 | 54 | .map(|file| file.errors) | ^^^^^^^^^^^ move occurs because `file.errors` has type `Vec<anyhow::Error>`, which does not implement the `Copy` trait For more information about this error, try `rustc --explain E0507`. error: could not compile `moo2` due to previous error
报错原因:iter()返回的是&MooFile引用,直接访问file.errors相当于尝试将errors字段从引用指向的结构体实例中移走,Rust不允许这种操作。
惯用实现方案
不需要提前单独遍历判断是否存在错误,直接使用可变迭代器iter_mut()获取每个文件的可变引用,再通过Vec::append将每个文件中的错误移到总错误向量即可,完全符合借用规则,且只需要一次遍历:
fn compile(&mut self) -> Result<(), Vec<anyhow::Error>> { // 其他编译逻辑... let mut errors = vec![]; for file in self.files.iter_mut() { // 将当前文件存储的所有错误追加到总错误列表,原文件的errors字段会被清空 errors.append(&mut file.errors); } if errors.is_empty() { Ok(()) } else { Err(errors) } }
方案说明
iter_mut()产出的迭代元素类型为&mut MooFile,持有对每个文件实例的可变访问权限,允许修改内部字段Vec::append会将参数向量中的所有元素移动到自身,参数向量执行后为空,不会产生悬空值- 仅需一次遍历即可完成错误收集,比先遍历判错再遍历取错的写法效率更高
- 全程仅修改
errors字段的内容,没有移动self.files或单个MooFile实例的所有权,不会破坏结构体的有效性
内容的提问来源于stack exchange,提问作者CaseyB
相关产品推荐
相关产品推荐

