Consider the following example:
struct S {
a: String,
b: String,
}
I have a macro which is called like this:
my_macro!(S);
I want to access the field names of the struct in the macro like this:
macro_rules! my_macro {
($t:ty) => {{
let field_names = get_field_names($t);
// do something with field_names
}};
}
I'm new to Rust and macros, so maybe I'm missing something obvious.
A macro is expanded during parsing, more or less; it has no access to the AST or anything like that—all it has access to is the stuff that you pass to it, which for my_macro!(S)
is purely that there should be a type named S
.
If you define the struct as part of the macro then you can know about the fields:
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"]
… but this is, while potentially useful, often going to be a dubious thing to do.
Here is another possibility that does not require to write a macro (however, the field names will be resolved at run time):
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);
}
}
(this solution needs the rustc-serialize
crate)
The derive(Default)
has been added to avoid having to manually create a struct as you wanted (but a struct will still be created).
This solution works by encoding the struct to a String
in JSON format and decoding it to a Json
. From the Json
object, we can extract the field names (if it is an Object
variant).
A possibly more efficient method is to write its own encoder:
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(()) }
}
which can be used as such:
fn main() {
let mut fields = FieldNames::new();
{
let mut encoder = FieldsEncoder::new(&mut fields);
S::default().encode(&mut encoder).unwrap();
}
println!("{:?}", fields.names);
}
I wanted to do the same: access the field names of a struct. But with the added complication that the struct was already using a #[derive()]
style macro, which is incompatible with macro_rules!
solution. As I expect my use case to be rather common, here's a quick write-down of my solution.
My ultimate goal was to write a CSV
header line corresponding to a struct Record
with the csv
crate, even when no record is written (writing records is usually done via serialize()
, but we sometimes filter all records and still want a valid empty CSV
file as output). This exact problem has also been formulated in another SO question and that this is not possible with just the csv
crate is a known and currently unresolved issue.
My solution to the extra complication with the #[derive()]
macro on the struct is to use the #[derive(FieldNamesAsArray)]
macro defined by the struct-field-names-as-array
crate.
You need to define the dependency in Cargo.toml
:
[dependencies]
struct-field-names-as-array = "0.1"
Then you can simply annotate the struct Record
in your something.rs
module with the respective derive macro and use the resulting constant Record::FIELD_NAMES_AS_ARRAY
for header writing:
// 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,
})?;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With