我有2个宏。第一个是special_trait
,一个用于特征声明的属性宏,第二个useful_macro
是与这样的特征一起使用。
也就是说,用户代码会这样写:
#[special_trait]
pub trait MyTrait{}
// meanwhile, in a different file...
use some_mod::MyTrait;
useful_macro!(MyTrait);
Run Code Online (Sandbox Code Playgroud)
现在,special_trait
宏需要以可以使用它MyTrait
的方式分配一些元数据useful_macro
。
这是可能的吗?
我突然想到我可以要求所有用户代码指定特征的完整路径,而不是依赖于use
:
#[special_trait]
pub trait MyTrait{}
// meanwhile, in a different file...
useful_macro!(some_mod::MyTrait);
Run Code Online (Sandbox Code Playgroud)
然后,special_trait
只需要定义一个pub const MyTrait_METADATA: i32 = 42
, 并且useful_macro
可以找到这个元数据 const 的some_mod::MyTrait
路径,因为它有完整的路径,只需要更改最后一段:some_mod::MyTrait_METADATA
。
但是,禁止use
和要求完整路径似乎很卑鄙,如果有更好的方法,我不想这样做。
我是否可以将元数据常量与特征相关联,以便任何可以“访问”特征的宏也可以找到元数据?
Rocket v4 也有同样的问题:
当在根模块之外的模块中声明路由时,您可能会发现在挂载时遇到意外错误:
Run Code Online (Sandbox Code Playgroud)mod other { #[get("/world")] pub fn world() -> &'static str { "Hello, world!" } } #[get("/hello")] pub fn hello() -> &'static str { "Hello, outside world!" } use other::world; fn main() { // error[E0425]: cannot find value `static_rocket_route_info_for_world` > in this scope rocket::ignite().mount("/hello", routes![hello, world]); }
出现这种情况是因为路由!宏将路由名称隐式转换为 Rocket 代码生成的结构名称。解决方案是使用命名空间路径来引用路由:
Run Code Online (Sandbox Code Playgroud)rocket::ignite().mount("/hello", routes![hello, other::world]);
在 Rocket v5(目前,只有一个候选版本)中,这种情况不再发生。例如,这用 Rocket v5 编译:
#[macro_use]
extern crate rocket;
mod module {
#[get("/bar")]
pub fn route() -> &'static str {
"Hello, world!"
}
}
use module::route;
fn main() {
rocket::build().mount("/foo", routes![route]);
}
Run Code Online (Sandbox Code Playgroud)
运行cargo-expand
时,我们看到 Rocket 生成了这样的东西(我缩写):
#[macro_use]
extern crate rocket;
mod module {
pub fn route() -> &'static str {
"Hello, world!"
}
#[doc(hidden)]
#[allow(non_camel_case_types)]
/// Rocket code generated proxy structure.
pub struct route {}
/// Rocket code generated proxy static conversion implementations.
impl route {
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
fn into_info(self) -> ::rocket::route::StaticInfo {
// ...
}
// ...
}
// ...
}
// ...
Run Code Online (Sandbox Code Playgroud)
get
应用于函数的属性宏构造了一个与函数同名的新结构。该结构体包含元数据(或者,更准确地说,包含一个函数into_info()
,该函数返回一个具有正确元数据的结构体——尽管这更多的是 Rocket 使用的实现的细节)。
这是有效的,因为函数声明位于 Value Namespace 中,而 struct 声明位于 Type Namespace 中。该use
声明同时导入.
让我们将其应用于您的示例:您的特征声明位于类型命名空间中,就像结构一样。因此,虽然您不能让special_trait
宏声明一个与 trait 同名的结构,但您可以让该宏声明一个同名的函数,该函数返回一个包含元数据的结构。然后可以调用此函数useful_macro!
来访问特征的元数据。因此,例如,元数据结构可能如下所示:
struct TraitMetadata {
name: String
}
Run Code Online (Sandbox Code Playgroud)
然后您的宏可以扩展如下:
mod other {
#[special_trait]
pub trait MyTrait{}
}
use some_mod::MyTrait;
fn main() {
useful_macro!(MyTrait);
}
Run Code Online (Sandbox Code Playgroud)
对此:
mod other {
pub trait MyTrait{}
pub fn MyTrait() -> TraitMetadata {
TraitMetadata {
name: "MyTrait".to_string()
}
}
}
use other::MyTrait;
fn main() {
do_something_with_trait_metadata(MyTrait());
}
Run Code Online (Sandbox Code Playgroud)
这种设计只有一个问题:如果用户声明了一个与 trait 同名的函数(或存在于值命名空间中的任何其他东西),这将失败。然而:
snake_case
而特征名称是CamelCase
,因此如果用户使用惯用标识符,他将永远不会拥有与特征使用的名称相同的函数。因此,这可能导致冲突的唯一现实方法是另一个宏作者也使用此构造将元数据附加到特征,并且用户将您的属性宏和另一个宏应用于同一 trait。在我看来,这是一个边缘情况,很少发生,不值得支持。
归档时间: |
|
查看次数: |
75 次 |
最近记录: |