如何将 "metadata" 分配给特征?

How can I assign "metadata" to a trait?

我有2个宏。第一个是 special_trait,一个用于特性声明的属性宏,第二个是 useful_macro 用于此类特性。

也就是说,用户代码会写成:

#[special_trait]
pub trait MyTrait{}

// meanwhile, in a different file...

use some_mod::MyTrait;

useful_macro!(MyTrait);

现在,special_trait 宏需要以 useful_macro 可以使用的方式将一些元数据分配给 MyTrait

这可能吗?如何实现?

可能但次优的解决方案

我想到我可以要求所有用户代码指定特征的完整路径,而不是依赖 use:

#[special_trait]
pub trait MyTrait{}

// meanwhile, in a different file...

useful_macro!(some_mod::MyTrait);

然后,special_trait只需要定义一个pub const MyTrait_METADATA: i32 = 42useful_macro可以找到这个元数据const的路径,因为它有完整的some_mod::MyTrait路径,并且只有最后一段需要更改:some_mod::MyTrait_METADATA.

但是,禁止 use 并要求完整路径似乎很刻薄,如果有更好的方法,我不想这样做。

我能否以某种方式将元数据常量与特征相关联,使得具有“访问”特征的任何宏也可以找到元数据?

Rocket v4有同样的问题:

When a route is declared inside a module other than the root, you may find yourself with unexpected errors when mounting:

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]);
}

This occurs because the routes! macro implicitly converts the route's name into the name of a structure generated by Rocket's code generation. The solution is to refer to the route using a namespaced path instead:

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]);
}

当运行 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 {
            // ...
        }
        // ...
    }
    // ...
}
// ...

应用于函数的get属性宏构造一个与函数同名的新结构。该结构包含元数据(或者,更准确地说,包含一个函数 into_info(),该函数 returns 具有正确元数据的结构——尽管这是 Rocket 使用的实现的更多细节)。

之所以有效,是因为 function declarations live in the Value Namespace while struct declarations live in the Type Namespace. The use declaration imports both.


让我们将其应用于您的示例:您的特征声明位于类型命名空间中,就像结构一样。因此,虽然你不能让你的 special_trait 宏声明一个与特征具有相同名称的结构,但你 可以 让那个宏声明一个 函数 与 returns 包含元数据的结构同名。然后 useful_macro! 可以调用此函数来访问特征的元数据。因此,例如,元数据结构可能如下所示:

struct TraitMetadata {
    name: String
}

你的宏可以这样扩展:

mod other {
    #[special_trait]
    pub trait MyTrait{}
}

use some_mod::MyTrait;
fn main() {
    useful_macro!(MyTrait);
}

对此:

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());
}

这种设计只有一个问题:如果用户声明了一个与特征同名的函数(或任何其他存在于值命名空间中的函数),这将失败。然而:

  1. 在惯用的 Rust 中,函数名称是 snake_case 而特征名称是 CamelCase,所以如果用户使用惯用标识符,他将永远不会拥有与特征同名的函数使用。
  2. 即使用户使用非惯用名称,对特征和函数使用相同的标识符也只是自找麻烦。我怀疑任何人(好吧,除了宏作者之外的任何人)会这样做。

因此,这可能导致冲突的唯一现实方式是另一个宏作者也在使用此构造将元数据附加到特征,并且用户应用您的属性宏 and另一个宏 具有相同的特征 。在我看来,这是一个很少发生的边缘案例,不值得支持。