Rust使用syn为函数添加代码行时写回文件无修改问题
问题根因
- 对过程宏的执行时机理解完全错误:过程宏是编译期执行的代码,在编译
main.rs的阶段,所有gen!()宏调用就已经被展开替换成固定代码了,运行二进制的时候根本不会执行你写在gen.rs里的宏逻辑。 - 宏输入完全不符合预期:你写的
gen::gen!(&token_stream),编译期传给宏的是源码里字面量&token_stream这段表达式的Token流,根本不是你运行时读取文件解析出来的函数语法树。这段输入匹配不到syn::Item::Fn,直接走错误分支返回原输入,展开后的代码等价于let file_contents_updated = &token_stream;,运行时拿到的就是你未修改的原始文件Token流,写出去自然和原文件完全一致。
正确修改方案
你这个需求是运行时读取、修改、生成Rust源码,属于AST操作工具的场景,根本不需要用过程宏,直接把语法树修改逻辑写成普通公共函数,在二进制运行时调用即可。
步骤1:调整Cargo.toml配置
去掉proc-macro属性,补全syn依赖所需的feature:
[package] name = "gen-demo" version = "0.1.0" edition = "2021" [dependencies] syn = { version = "2.0", features = ["full", "visit-mut"] } quote = "1.0" proc-macro2 = "1.0" [lib] name = "gen" path = "src/gen.rs" [[bin]] name = "main" path = "src/main.rs"
步骤2:重写gen.rs逻辑
把过程宏改成普通语法树处理函数,用syn的可变访问器遍历修改所有函数节点,避免单节点解析无法处理多Item文件的问题:
use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::visit_mut::VisitMut; struct InsertCode; impl VisitMut for InsertCode { fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) { // 先递归处理内部嵌套项,避免漏改 syn::visit_mut::visit_item_fn_mut(self, node); // 在函数块第一行插入目标代码 node.block.stmts.insert( 0, syn::parse2(quote!(println!("count me in");)).unwrap() ); } } pub fn modify_ast(mut file: syn::File) -> TokenStream { InsertCode.visit_file_mut(&mut file); file.to_token_stream() }
步骤3:调整main.rs调用逻辑
去掉宏调用,直接在运行时调用普通处理函数:
use std::env; use std::fs; use std::io::{self, Write}; use std::path::PathBuf; use std::process; use gen::modify_ast; #[derive(Debug)] enum Error { ReadFile(std::io::Error), ParseFile { error: syn::Error, filepath: PathBuf, source_code: String, }, WriteFile(std::io::Error), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::ReadFile(e) => write!(f, "读取文件失败: {}", e), Error::ParseFile { error, filepath, .. } => { write!(f, "解析文件{}失败: {}", filepath.display(), error) } Error::WriteFile(e) => write!(f, "写入文件失败: {}", e), } } } fn main() { if let Err(error) = try_main() { let _ = writeln!(io::stderr(), "{}", error); process::exit(1); } } fn try_main() -> Result<(), Error> { let mut args = env::args_os(); let _ = args.next(); let filepath = PathBuf::from("./src/file-to-parse.rs"); let code = fs::read_to_string(&filepath).map_err(Error::ReadFile)?; let syntax = syn::parse_file(&code).map_err(|error| Error::ParseFile { error, filepath, source_code: code, })?; let updated_content = modify_ast(syntax); fs::write("./src/file-updated.rs", updated_content.to_string()).map_err(Error::WriteFile)?; Ok(()) }
补充说明
如果你确实要实现过程宏给其他业务代码用(比如给标注了宏的函数自动插入代码),那宏的使用方式应该是直接挂在目标函数定义上,而不是在独立二进制里运行时读文件传入:
// 业务代码里直接使用宏 use gen::gen; #[gen] fn init() { println!("Hello, world!"); }
这种场景下才需要保留proc-macro = true配置,把宏改成属性宏实现即可。
内容的提问来源于stack exchange,提问作者Mr.Smithyyy
相关产品推荐
相关产品推荐

