考虑以下示例:
struct S {
a: String,
b: String,
}
我有一个宏,其名称如下:
my_macro!(S);
我想像这样访问宏中结构体的字段名称:
macro_rules! my_macro {
($t:ty) => {{
let field_names = get_field_names($t);
// do something with field_names
}};
}
我是 Rust 和宏的新手,所以也许我遗漏了一些明显的东西。
宏在解析过程中或多或少会被扩展;它无法访问 AST 或类似的东西 - 它所能访问的只是您传递给它的东西,这对于
my_macro!(S)
来说纯粹是应该有一个名为 S
的类型。
如果您将结构定义为宏的一部分,那么您可以了解这些字段:
macro_rules! my_macro {
(struct $name:ident {
$($field_name:ident: $field_type:ty,)*
}) => {
struct $name {
$($field_name: $field_type,)*
}
impl $name {
// This is purely an example—not a good one.
fn get_field_names() -> Vec<&'static str> {
vec![$(stringify!($field_name)),*]
}
}
}
}
my_macro! {
struct S {
a: String,
b: String,
}
}
// S::get_field_names() == vec!["a", "b"]
…但这虽然可能有用,但通常会是一件可疑的事情。
这是另一种不需要编写宏的可能性(但是,字段名称将在运行时解析):
extern crate rustc_serialize;
use rustc_serialize::json::{Encoder, Json};
use rustc_serialize::json::Json::Object;
use rustc_serialize::Encodable;
#[derive(Default, RustcEncodable)]
struct S {
a: String,
b: String,
}
fn main() {
let mut json = "".to_owned();
{
let mut encoder = Encoder::new(&mut json);
S::default().encode(&mut encoder).unwrap();
}
let json = Json::from_str(&json).unwrap();
if let Object(object) = json {
let field_names: Vec<_> = object.keys().collect();
println!("{:?}", field_names);
}
}
(此解决方案需要
rustc-serialize
板条箱)
添加了
derive(Default)
以避免必须根据需要手动创建结构(但仍会创建结构)。
此解决方案的工作原理是将结构体编码为 JSON 格式的
String
,然后将其解码为 Json
。从 Json
对象中,我们可以提取字段名称(如果它是 Object
变体)。
一个可能更有效的方法是编写自己的编码器:
struct FieldNames {
names: Vec<String>,
}
impl FieldNames {
fn new() -> FieldNames {
FieldNames {
names: vec![],
}
}
}
struct FieldsEncoder<'a> {
fields: &'a mut FieldNames,
}
impl<'a> FieldsEncoder<'a> {
fn new(fields: &mut FieldNames) -> FieldsEncoder {
FieldsEncoder {
fields: fields,
}
}
}
type EncoderError = ();
impl<'a> Encoder for FieldsEncoder<'a> {
fn emit_struct<F>(&mut self, _name: &str, _len: usize, f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> {
f(self)
}
fn emit_struct_field<F>(&mut self, f_name: &str, _f_idx: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> {
self.fields.names.push(f_name.to_owned());
Ok(())
}
type Error = EncoderError;
fn emit_nil(&mut self) -> Result<(), Self::Error> { Err(()) }
fn emit_usize(&mut self, _v: usize) -> Result<(), Self::Error> { Err(()) }
fn emit_u64(&mut self, _v: u64) -> Result<(), Self::Error> { Err(()) }
fn emit_u32(&mut self, _v: u32) -> Result<(), Self::Error> { Err(()) }
fn emit_u16(&mut self, _v: u16) -> Result<(), Self::Error> { Err(()) }
fn emit_u8(&mut self, _v: u8) -> Result<(), Self::Error> { Err(()) }
fn emit_isize(&mut self, _v: isize) -> Result<(), Self::Error> { Err(()) }
fn emit_i64(&mut self, _v: i64) -> Result<(), Self::Error> { Err(()) }
fn emit_i32(&mut self, _v: i32) -> Result<(), Self::Error> { Err(()) }
fn emit_i16(&mut self, _v: i16) -> Result<(), Self::Error> { Err(()) }
fn emit_i8(&mut self, _v: i8) -> Result<(), Self::Error> { Err(()) }
fn emit_bool(&mut self, _v: bool) -> Result<(), Self::Error> { Err(()) }
fn emit_f64(&mut self, _v: f64) -> Result<(), Self::Error> { Err(()) }
fn emit_f32(&mut self, _v: f32) -> Result<(), Self::Error> { Err(()) }
fn emit_char(&mut self, _v: char) -> Result<(), Self::Error> { Err(()) }
fn emit_str(&mut self, _v: &str) -> Result<(), Self::Error> { Err(()) }
fn emit_enum<F>(&mut self, _name: &str, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_enum_variant<F>(&mut self, _v_name: &str, _v_id: usize, _len: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_enum_variant_arg<F>(&mut self, _a_idx: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_enum_struct_variant<F>(&mut self, _v_name: &str, _v_id: usize, _len: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_enum_struct_variant_field<F>(&mut self, _f_name: &str, _f_idx: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_tuple<F>(&mut self, _len: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_tuple_arg<F>(&mut self, _idx: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_tuple_struct<F>(&mut self, _name: &str, _len: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_tuple_struct_arg<F>(&mut self, _f_idx: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_option<F>(&mut self, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_option_none(&mut self) -> Result<(), Self::Error> { Err(()) }
fn emit_option_some<F>(&mut self, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_seq<F>(&mut self, _len: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_seq_elt<F>(&mut self, _idx: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_map<F>(&mut self, _len: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_map_elt_key<F>(&mut self, _idx: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
fn emit_map_elt_val<F>(&mut self, _idx: usize, _f: F) -> Result<(), Self::Error> where F: FnOnce(&mut Self) -> Result<(), Self::Error> { Err(()) }
}
可以这样使用:
fn main() {
let mut fields = FieldNames::new();
{
let mut encoder = FieldsEncoder::new(&mut fields);
S::default().encode(&mut encoder).unwrap();
}
println!("{:?}", fields.names);
}
我想做同样的事情:访问结构体的字段名称。但更复杂的是,该结构已经使用了
#[derive()]
样式宏,这与 macro_rules!
解决方案 不兼容。由于我预计我的用例相当常见,因此这里是我的解决方案的快速记录。
我的最终目标是用
CSV
箱编写与 struct Record
相对应的
csv
标题行,即使没有写入记录(写入记录通常是通过 serialize()
完成的,但有时我们会过滤所有记录,但仍然需要一个有效的空 CSV
文件作为输出)。这个确切的问题也在另一个SO问题中得到了阐述,并且仅使用csv
板条箱是不可能的,这是一个已知且当前未解决的问题。
针对结构体上的
#[derive()]
宏带来的额外复杂性,我的解决方案是使用 #[derive(FieldNamesAsArray)]
crate定义的
struct-field-names-as-array
宏。
您需要在
Cargo.toml
中定义依赖关系:
[dependencies]
struct-field-names-as-array = "0.1"
然后您可以简单地使用相应的派生宏注释
struct Record
模块中的 something.rs
并使用生成的常量 Record::FIELD_NAMES_AS_ARRAY
进行标头写入:
// csv-specific imports
use csv::WriterBuilder;
use serde::Serialize;
// import for getting the field names array
use struct_field_names_as_array::FieldNamesAsArray;
// Serialize from serde, to write `Record`s systematically
// FieldNamesAsArray to get the field names
#[derive(Serialize,FieldNamesAsArray)]
struct Record {
field_1: String,
field_2: u64,
}
// ensure that serializing records does not write a header with
// the `.has_headers(false)`
let mut csv_writer = csv::WriterBuilder::new()
.has_headers(false)
.from_path("foo.csv")?;
// Manually write out the header.
csv_writer.write_record(Record::FIELD_NAMES_AS_ARRAY)?;
// `serialize()` records later, if some condition is met.
// But we also have a correct header if this condition is never met.
if some_condition {
csv_writer.serialize(Recor {
field_1: "some_string",
field_2: 71028743,
})?;
}
此过程宏将返回带注释的结构体的字段
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(GetAttributesMacro)]
pub fn get_attributes_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_get_attributes(&ast)
}
fn impl_get_attributes(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let fields = match ast.data {
syn::Data::Struct(ref data) => match data.fields {
syn::Fields::Named(ref fields) => fields.named.iter().map(|f| f.ident.clone().unwrap()),
_ => unimplemented!(),
},
_ => unimplemented!(),
};
let gen = quote! {
impl GetAttributesMacro for #name {
fn get_attributes(&self) -> Vec<&str> {
let mut vec = Vec::new();
#(
vec.push(stringify!(#fields));
)*
return vec;
}
}
};
gen.into()
}
示例:
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, GetAttributesMacro)]
#[serde(rename_all = "camelCase")]
pub struct Asset {
pub id: String,
}
fn main() {
let d = deployment::Asset {
id: String::from("test"),
};
for i in v {
println!("{}", i)
}
}
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/proc`
id