有没有一种方法可以在宏中获取结构体的字段名?

20
考虑以下示例:
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 和宏,所以可能会忽略一些显而易见的东西。


一个想法是使用宏为给定的结构创建一个命令行界面。我想要解构这个结构,并根据每个字段的类型创建相应的选项/标志。然而,虽然我现在正在谈论它,但我不确定宏是否是最好的方式来做到这一点... - Joel Hermanns
嗯,也许只使用泛型和特质就可以做到那个。 - Joel Hermanns
3个回答

37

宏在解析期间进行展开;它没有访问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"]

...... 但是这样做虽然有潜在的用处,但通常会是一个可疑的事情。


5
这里还有另一个可能性,不需要编写宏(但是字段名称将在运行时解析):
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);
}

4
我想做同样的事情:访问结构体的字段名。但是有一个额外的复杂性,即该结构体已经使用了#[derive()]风格的宏,这与macro_rules! solution不兼容。由于我预计我的用例会相当常见,所以在此快速记录一下我的解决方案。
我的最终目标是使用 csv库编写与struct Record对应的CSV头行,即使没有记录被写入(通常通过serialize()来完成记录的写入,但有时我们会过滤所有记录,仍然希望得到一个有效的空CSV文件作为输出)。这个问题也在另一个SO问题中提出,而使用只有csv库是不可能解决这个问题的,这是一个已知且尚未解决的问题。 我解决结构体上#[derive()]宏的额外复杂性的方法是使用由struct-field-names-as-array库定义的#[derive(FieldNamesAsArray)]
你需要在 Cargo.toml 文件中定义依赖关系:
[dependencies]
struct-field-names-as-array = "0.1"

然后,您可以在something.rs模块中使用相应的派生宏简单地注释struct Record,并使用生成的常量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,
    })?;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接