我不知道如何导入和示例化行,使它们能够容忍不存在的文件/模块和结构体。
我试着创建一个宏,根据它在目录中找到的文件展开成这样的行,使用我发现的一个有承诺的crate-include_optional
-它允许在编译时检查已经存在的文件(因为它是一个宏)。但是,我不知道如何在宏中正确地使用它,我也没有设法使用它没有宏使用的例子在底部的docs conditional compilation一章。if cfg!(unix) { "unix" } else if cfg!(windows) { "windows" } else { "unknown" }
(来自文档)
对比if include_optional::include_bytes_optional!("day1.rs").is_some() { Some(&day01::Day01 {}) } else { None } // assume day1.rs and thus Day01 are non-existent
(我尝试做同样的事情)
我的if语句编译了这两种情况,包括无法访问的代码(导致编译错误),尽管根据文档,它应该不适用于cfg!
(“条件编译”)。
本质上,我想要的是这种形式的东西:
// Macro to generate code based on how many files/structs has been created
// There are anywhere between 1-25 days
get_days_created!;
/* // Let's assume 11 have been created so far, then the macro should evaluate to this code:
* mod day1;
* use day1 as day0#;
* // ...
* mod day11;
* use day11 as day11;
*
* // ...
* fn main() -> Result<(), i32> {
* let days : Vec<&dyn Day> = vec![
* &day01::Day01 {},
* // ...
* &day11::Day11 {},
* ];
* // ...
* }
*/
1条答案
按热度按时间omhiaaxx1#
解决方案是创建一个
proc_macro
.这些函数类似于常规宏,只是它们允许您编写一个实际代码的函数,而不是给定(并返回)一个'TokenStream
'来解析给定的标记(以及宏应该扩展到哪些标记)。要创建proc_macro,你需要知道的第一条也是最重要的一条信息是你不能在任何地方都这样做。相反,你需要创建一个新的库,并在它的
Cargo.toml
文件中设置proc-macro = true
。然后你可以在它的lib.rs
中声明它们。一个TOML的例子如下所示:然后你可以在这个库中创建宏作为常规函数,使用
#[proc_macro]
属性/注解。下面是一个lib.rs
* 的例子,依赖性尽可能少 *。对于我的问题,输入TokenStream
是不相关的,可以忽略,相反,你想生成并返回一个新的:这个问题可以认为已经得到了回答,但我认为答案可以而且应该进一步扩展,因此我会这样做。
一些超出问题范围的其他解释和建议
但是除了
proc_macro
之外还有很多其他的crate可以帮助你解析输入流和构建输出流,值得注意的是依赖项syn
和quote
,为了帮助它们,有crateproc_macro2
。合成器板条箱
使用
syn
,你可以得到有用的类型,方法和宏来解析输入Tokenstream
。本质上,使用一个结构体Foo实现syn::parse::Parse
和宏let foo = syn::parse_macro_input!(input as Foo)
,你可以更容易地将其解析为一个定制的结构体,这要归功于syn::parse::ParseStream
。注意,当使用sugary
?
语法时,syn::Result
返回类型允许解析错误的良好传播:input.parse::<SomeType>()?
报价箱
使用
quote
,你可以得到一个有用的宏来生成一个更类似于macro_rules
的tokenstream,作为一个参数,你可以编写基本上常规的代码,并通过前缀#
告诉它使用变量的值。请注意,您不能只传递包含字符串的变量,然后期望它扩展为标识符,因为字符串解析为值
"foo"
(包括引号),即mod "day1";
而不是mod day1;
。您需要将它们转换为:proce_macro2::Ident
proc_macro2::TokenStream
后者还允许转换具有多个Ident的较长字符串,并管理诸如文字等内容,从而可以完全跳过
quote!
宏,直接使用此tokenstream(如import_days
中所示)。下面的例子创建了一个动态名称的struct,并为它实现了一个特定的trait:
最后,关于如何实施我的问题
这"章"有点多余,因为我基本上用前两个代码片段来回答它(
.toml
和fn import_days
),其余的可以被认为是读者的练习。然而,尽管问题 * 是关于在编译时在宏中读取文件系统以"动态地"改变其扩展 (某种程度上),我写了一个更一般的形式,问如何达到一个特定的结果(因为以前的我不知道宏的可以做到这一点)。还有一个事实是,本"章"中的最后一个宏-
impl_day
(问题中根本没有提到)-作为如何完成两个相邻但重要且相关的任务的一个很好的例子。1.正在检索和使用调用站点的文件名。
1.使用
syn
依赖项解析input TokenStream
,如上所示。换句话说:了解以上所有内容后, 这就是如何创建宏来导入所有目标文件、示例化所有目标文件的结构体,以及从当前文件名声明+定义结构体。*
导入所有目标文件:
请参见上面开头的
import_days
。使用所有目标文件中的结构体示例化Vec:
正在为调用此宏的当前文件声明和定义结构: