Rust 如何在过程宏中处理枚举/结构/字段属性?
Serde支持应用与Rust 如何在过程宏中处理枚举/结构/字段属性?,rust,Rust,Serde支持应用与#[派生(序列化)]一起使用的自定义属性: #[派生(序列化)] 结构资源{ //总是序列化的。 名称:String, //从未序列化。 #[serde(跳过序列化)] hash:String, //使用方法决定是否应跳过该字段。 #[serde(跳过序列化,如果=“Map::is\u empty”)] 元数据:地图, } 我了解如何实现过程宏(在本例中序列化),但如何实现#[serde(skip_serialization)]?我在任何地方都找不到这个信息。他们甚至都不提
#[派生(序列化)]
一起使用的自定义属性:
#[派生(序列化)]
结构资源{
//总是序列化的。
名称:String,
//从未序列化。
#[serde(跳过序列化)]
hash:String,
//使用方法决定是否应跳过该字段。
#[serde(跳过序列化,如果=“Map::is\u empty”)]
元数据:地图,
}
我了解如何实现过程宏(
在本例中序列化
),但如何实现#[serde(skip_serialization)]
?我在任何地方都找不到这个信息。他们甚至都不提这件事。我曾尝试查看serde-derivate
源代码,但它对我来说非常复杂。将字段上的属性作为结构的派生宏的一部分来实现(只能为结构和枚举实现派生宏)
Serde通过检查syn
提供的结构中的每个字段的属性并相应地更改代码生成来实现这一点
您可以在此处找到相关代码:
#[derive(Copy, Clone, Debug, FiniteStateMachine)]
#[state_change(GameEvent, change_condition)] // optional
enum GameState {
#[state_transitions(NeedServer, Ready)]
Prepare { players: u8 },
#[state_transitions(Prepare, Ready)]
NeedServer,
#[state_transitions(Prepare)]
Ready,
}
如果没有该选项,编译器将给出错误消息,如:
state\u change
不属于任何已知属性
这些属性是可选的,我们所做的只是允许指定它们。在派生过程宏时,您可以检查所需的所有内容(包括属性是否存在)和panic代码>在某些条件下,编译器将告诉您有意义的消息
state\u transitions
属性,因为它的处理与处理struct/enum属性没有太大区别(实际上它只是多一点代码),而讨论state\u change
。syn
板条箱为您提供了有关定义的所有必要信息(但不幸的是没有实现(我在这里谈论的是impl
),但这对于处理属性来说已经足够了)。更详细地说,我们需要,,syn::Attribute
以及最后
Vec
-这是您想要的,一个字段的所有属性的列表。在这里可以找到我们的状态转换。当您找到它时,您可能想要获取它的内容,这可以通过使用匹配的syn::MetaItem
enum来完成。只需阅读文档:)下面是一个简单的示例代码,当我们在某个字段上发现state\u change
属性,并检查目标实体是否派生Copy
或Clone
或两者都不派生时,代码会恐慌:
#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))]
pub fn fxsm(input: TokenStream) -> TokenStream {
// Construct a string representation of the type definition
let s = input.to_string();
// Parse the string representation
let ast = syn::parse_derive_input(&s).unwrap();
// Build the impl
let gen = impl_fsm(&ast);
// Return the generated impl
gen.parse().unwrap()
}
fn impl_fsm(ast: &syn::DeriveInput) -> Tokens {
const STATE_CHANGE_ATTR_NAME: &'static str = "state_change";
if let syn::Body::Enum(ref variants) = ast.body {
// Looks for state_change attriute (our attribute)
if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == STATE_CHANGE_ATTR_NAME) {
if let syn::MetaItem::List(_, ref nested) = a.value {
panic!("Found our attribute with contents: {:?}", nested);
}
}
// Looks for derive impls (not our attribute)
if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == "derive") {
if let syn::MetaItem::List(_, ref nested) = a.value {
if derives(nested, "Copy") {
return gen_for_copyable(&ast.ident, &variants, &ast.generics);
} else if derives(nested, "Clone") {
return gen_for_clonable(&ast.ident, &variants, &ast.generics);
} else {
panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
}
} else {
panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
}
} else {
panic!("How have you been able to call me without derive!?!?");
}
} else {
panic!("Finite State Machine must be derived on a enum.");
}
}
fn derives(nested: &[syn::NestedMetaItem], trait_name: &str) -> bool {
nested.iter().find(|n| {
if let syn::NestedMetaItem::MetaItem(ref mt) = **n {
if let syn::MetaItem::Word(ref id) = *mt {
return id == trait_name;
}
return false
}
false
}).is_some()
}
您可能对阅读感兴趣。最后一个链接实际上是我自己的项目,向我自己解释如何在Rust中使用过程宏
经过1.15版本的升级和syn机箱的更新后,无法再检查
enums/structs
的派生,但是,其他一切都正常。您能提供一个简单的示例说明如何执行此操作吗?我看到了您提到的代码,但是也实现了很多东西。如果您使用的是syn
,那么您可以通过访问字段
结构的attr
字段来访问字段的属性。通过检查的syn::MacroInput
的body
字段,然后调用以获取字段的列表,可以获得字段的结构该serde\u codegen\u internal
链接现在无效。如果不确定要指向何处,则会进行编辑。另外,谢谢你的回答,非常有帮助:)@Mihir我刚刚更新了链接,指向了与之前指向的内容类似的内容。就我所记得的,我指的是serde的代码,他们在那里处理过程宏,这个新链接指向他们的新家。自从给出这个答案以来,这个项目已经有了很大的发展,所以早期阅读和理解他们的代码要容易得多,但现在它是一个巨大的代码块,可能不如初学者的阅读那么好。我相信,仍然有一些很好的例子。我会尝试添加链接到我能找到的东西,以及在一分钟内。
#[derive(Copy, Clone, Debug, FiniteStateMachine)]
#[state_change(GameEvent, change_condition)] // optional
enum GameState {
#[state_transitions(NeedServer, Ready)]
Prepare { players: u8 },
#[state_transitions(Prepare, Ready)]
NeedServer,
#[state_transitions(Prepare)]
Ready,
}
#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))]
pub fn fxsm(input: TokenStream) -> TokenStream {
// Construct a string representation of the type definition
let s = input.to_string();
// Parse the string representation
let ast = syn::parse_derive_input(&s).unwrap();
// Build the impl
let gen = impl_fsm(&ast);
// Return the generated impl
gen.parse().unwrap()
}
fn impl_fsm(ast: &syn::DeriveInput) -> Tokens {
const STATE_CHANGE_ATTR_NAME: &'static str = "state_change";
if let syn::Body::Enum(ref variants) = ast.body {
// Looks for state_change attriute (our attribute)
if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == STATE_CHANGE_ATTR_NAME) {
if let syn::MetaItem::List(_, ref nested) = a.value {
panic!("Found our attribute with contents: {:?}", nested);
}
}
// Looks for derive impls (not our attribute)
if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == "derive") {
if let syn::MetaItem::List(_, ref nested) = a.value {
if derives(nested, "Copy") {
return gen_for_copyable(&ast.ident, &variants, &ast.generics);
} else if derives(nested, "Clone") {
return gen_for_clonable(&ast.ident, &variants, &ast.generics);
} else {
panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
}
} else {
panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
}
} else {
panic!("How have you been able to call me without derive!?!?");
}
} else {
panic!("Finite State Machine must be derived on a enum.");
}
}
fn derives(nested: &[syn::NestedMetaItem], trait_name: &str) -> bool {
nested.iter().find(|n| {
if let syn::NestedMetaItem::MetaItem(ref mt) = **n {
if let syn::MetaItem::Word(ref id) = *mt {
return id == trait_name;
}
return false
}
false
}).is_some()
}