如何在Rust中迭代遍历目录并解析为Collection结构体?
解决方案
方案1:完全安全的迭代实现(无需unsafe/内部可变性)
核心思路:栈中存储当前遍历路径和父Collection的可变引用,严格遵循Rust的借用规则——同一时间仅持有一个可变引用,避免编译错误。为了和递归版本的DFS遍历顺序一致,需要反转目录条目(栈是后进先出结构)。
use std::fs; use std::path::{Path, PathBuf}; use serde::{Serialize, Deserialize}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Collection { pub relative_path: String, pub name: String, pub collections: Vec<Collection>, pub requests: Vec<Request>, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Request { pub relative_path: String, pub name: String, } fn build_collection_from_path_iterative_safe<P: AsRef<Path>>(root_path: P) -> Collection { let root_path = root_path.as_ref(); let root_name = root_path .file_name() .unwrap() .to_string_lossy() .into_owned(); let root_relative_path = root_path.to_string_lossy().into_owned(); let mut root_collection = Collection { relative_path: root_relative_path, name: root_name, collections: vec![], requests: vec![], }; // 栈元素:(当前要遍历的路径, 父Collection的可变引用) let mut stack = vec![(root_path.to_path_buf(), &mut root_collection as &mut Collection)]; while let Some((current_path, parent)) = stack.pop() { if let Ok(entries) = fs::read_dir(¤t_path) { // 反转条目,保证遍历顺序和递归版本一致 let mut entries: Vec<_> = entries.collect(); entries.reverse(); for entry in entries { if let Ok(entry) = entry { let entry_path = entry.path(); if entry_path.is_dir() { let sub_name = entry_path .file_name() .unwrap() .to_string_lossy() .into_owned(); let sub_relative_path = entry_path.to_string_lossy().into_owned(); let mut sub_collection = Collection { relative_path: sub_relative_path, name: sub_name, collections: vec![], requests: vec![], }; // 先获取子集合的可变引用,再推入父集合 let sub_ref = &mut sub_collection; parent.collections.push(sub_collection); // 将子目录和子集合引用推入栈 stack.push((entry_path, sub_ref)); } else if entry_path.is_file() { let file_name = entry_path .file_name() .unwrap() .to_string_lossy() .into_owned(); let file_relative_path = entry_path.to_string_lossy().into_owned(); parent.requests.push(Request { relative_path: file_relative_path, name: file_name, }); } } } } } root_collection }
方案2:使用RefCell实现内部可变性
如果需要更灵活的引用管理(比如同时操作多个父节点),可以用RefCell提供运行时的内部可变性,绕过编译期的借用检查(运行时会保证引用安全,违反规则时会panic)。
use std::cell::RefCell; use std::fs; use std::path::{Path, PathBuf}; use serde::{Serialize, Deserialize}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Collection { pub relative_path: String, pub name: String, pub collections: Vec<RefCell<Collection>>, // 用RefCell包裹子集合 pub requests: Vec<Request>, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Request { pub relative_path: String, pub name: String, } fn build_collection_from_path_iterative_refcell<P: AsRef<Path>>(root_path: P) -> Collection { let root_path = root_path.as_ref(); let root_name = root_path .file_name() .unwrap() .to_string_lossy() .into_owned(); let root_relative_path = root_path.to_string_lossy().into_owned(); let root_collection = Collection { relative_path: root_relative_path, name: root_name, collections: vec![], requests: vec![], }; let root_ref = RefCell::new(root_collection); // 栈元素:(当前路径, 父Collection的RefCell引用) let mut stack = vec![(root_path.to_path_buf(), &root_ref)]; while let Some((current_path, parent_ref)) = stack.pop() { if let Ok(entries) = fs::read_dir(¤t_path) { let mut entries: Vec<_> = entries.collect(); entries.reverse(); for entry in entries { if let Ok(entry) = entry { let entry_path = entry.path(); let mut parent = parent_ref.borrow_mut(); if entry_path.is_dir() { let sub_name = entry_path .file_name() .unwrap() .to_string_lossy() .into_owned(); let sub_relative_path = entry_path.to_string_lossy().into_owned(); let sub_collection = Collection { relative_path: sub_relative_path, name: sub_name, collections: vec![], requests: vec![], }; let sub_ref = RefCell::new(sub_collection); parent.collections.push(sub_ref.clone()); stack.push((entry_path, &sub_ref)); } else if entry_path.is_file() { let file_name = entry_path .file_name() .unwrap() .to_string_lossy() .into_owned(); let file_relative_path = entry_path.to_string_lossy().into_owned(); parent.requests.push(Request { relative_path: file_relative_path, name: file_name, }); } } } } } // 将RefCell中的根集合取出返回 root_ref.into_inner() }
方案对比
- 安全迭代方案:完全符合Rust编译期借用规则,无运行时开销,是最优选择
- RefCell方案:适合复杂场景,有轻微运行时开销,但比unsafe更安全
- unsafe方案:存在内存安全风险(如指针失效导致未定义行为),仅在极端场景下考虑使用
内容的提问来源于stack exchange,提问作者xqcccccccccc
相关产品推荐
相关产品推荐

