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

如何在Rust中条件导入模块并向vec!添加结构体实例(仅模块存在时)

Rust 编译时检测文件存在并生成模块导入代码的解决方案

问题背景

我没法写出兼容不存在文件、模块和结构体的导入及实例化代码。尝试用include_optional crate(编译时检测文件存在性)编写宏生成代码,但无论宏内使用还是参照Rust条件编译示例调用,都无法成功。

Rust官方条件编译示例:

if cfg!(unix) { "unix" } else if cfg!(windows) { "windows" } else { "unknown" }

我的尝试代码(假设day1.rs和Day01不存在):

if include_optional::include_bytes_optional!("day1.rs").is_some() { Some(&day01::Day01 {}) } else { None }

问题核心:上述if语句会编译两个分支(包括不可达分支),导致未定义模块/结构体的编译错误,但cfg!会直接剔除不符合条件的分支,不会触发此类错误。我需要实现如下功能:

// 根据已创建的文件/结构体数量生成代码的宏
// 天数范围1-25天
get_days_created!;
/* // 假设已创建11天的文件,宏应生成如下代码:
 * mod day1;
 * use day1 as day01;
 * // ...
 * mod day11;
 * use day11 as day11;
 *
 * // ...
 * fn main() -> Result<(), i32> {
 *     let days : Vec<&dyn Day> = vec![
 *         &day01::Day01 {},
 *         // ...
 *         &day11::Day11 {},
 *     ];
 *     // ...
 * }
*/

失败原因

include_optional::include_bytes_optional!虽为编译时执行,但返回的是Option<&'static [u8]>,对应的if分支属于运行时分支。Rust会对所有分支的代码进行合法性检查,即便某分支在编译时可确定不可达,未定义的模块/结构体仍会触发编译错误。而cfg!是编译时条件判断,会直接剔除不符合条件的分支,不会对其代码做检查,这是两者的核心差异。

解决方案

方案1:使用Build Script生成代码

通过构建脚本在编译前检测文件存在性,直接生成对应的模块导入和实例化代码,无需宏处理。

  1. 创建build.rs文件,编写检测逻辑:
use std::fs;
use std::path::Path;

fn main() {
    let mut generated_code = String::new();

    // 写入公共Day trait定义(根据实际需求调整)
    generated_code.push_str("pub trait Day { fn run(&self) -> Result<(), i32>; }\n\n");

    // 遍历1-25天,检测对应文件是否存在
    for day in 1..=25 {
        let day_str = format!("day{}", day);
        let file_path = Path::new("src").join(format!("{}.rs", day_str));
        
        if file_path.exists() {
            // 添加模块导入
            generated_code.push_str(&format!("pub mod {};\n", day_str));
            // 添加两位数字的别名(如day1 → day01)
            let alias = format!("day{:02}", day);
            generated_code.push_str(&format!("pub use {} as {};\n", day_str, alias));
        }
    }

    // 生成获取days向量的函数
    generated_code.push_str("\npub fn get_days() -> Vec<&'static dyn Day> {\n    let mut days = Vec::new();\n");
    for day in 1..=25 {
        let alias = format!("day{:02}", day);
        let file_path = Path::new("src").join(format!("day{}.rs", day));
        
        if file_path.exists() {
            let struct_name = format!("Day{:02}", day);
            generated_code.push_str(&format!("    days.push(&{}::{} {{}});\n", alias, struct_name));
        }
    }
    generated_code.push_str("    days\n}\n");

    // 将生成的代码写入文件
    fs::write("src/generated_days.rs", generated_code)
        .expect("Failed to write generated days code");
}
  1. 在src/main.rs中导入生成的代码:
mod generated_days;
use generated_days::{Day, get_days};

fn main() -> Result<(), i32> {
    let days = get_days();
    for day in days {
        day.run()?;
    }
    Ok(())
}

方案2:使用过程宏生成代码

编写自定义过程宏,在编译时直接检测文件存在性并生成对应代码,无需额外构建脚本。

  1. 创建一个过程宏crate(需在Cargo.toml中添加proc-macro = true),编写宏逻辑:
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use std::path::Path;

#[proc_macro]
pub fn get_days_created(_input: TokenStream) -> TokenStream {
    let mut mod_items = Vec::new();
    let mut day_instances = Vec::new();

    // 遍历1-25天
    for day in 1..=25 {
        let day_str = format!("day{}", day);
        let file_path = Path::new("src").join(format!("{}.rs", day_str));
        
        if file_path.exists() {
            // 生成模块导入语句
            let day_ident = syn::Ident::new(&day_str, Span::call_site());
            mod_items.push(quote! {
                pub mod #day_ident;
            });

            // 生成别名语句
            let alias_str = format!("day{:02}", day);
            let alias_ident = syn::Ident::new(&alias_str, Span::call_site());
            mod_items.push(quote! {
                pub use #day_ident as #alias_ident;
            });

            // 生成结构体实例
            let struct_name = format!("Day{:02}", day);
            let struct_ident = syn::Ident::new(&struct_name, Span::call_site());
            day_instances.push(quote! {
                &#alias_ident::#struct_ident {}
            });
        }
    }

    // 拼接所有生成的代码
    let output = quote! {
        #(#mod_items)*

        pub fn get_days() -> Vec<&'static dyn Day> {
            vec![#(#day_instances),*]
        }
    };

    output.into()
}
  1. 在主项目的Cargo.toml中依赖该过程宏crate,然后在src/main.rs中使用:
use your_proc_macro_crate::get_days_created;

// 定义公共Day trait
pub trait Day {
    fn run(&self) -> Result<(), i32>;
}

// 调用宏生成代码
get_days_created!();

fn main() -> Result<(), i32> {
    let days = get_days();
    for day in days {
        day.run()?;
    }
    Ok(())
}

内容的提问来源于stack exchange,提问作者felix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 16:10:33