You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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(&current_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(&current_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 04:48:10