field_accessor

Crates.iofield_accessor
lib.rsfield_accessor
version0.6.2
created_at2022-05-04 03:56:28.75513+00
updated_at2025-09-27 04:26:34.203347+00
descriptionA procedural macro to get and set a struct field by String dynamically.
homepage
repositoryhttps://github.com/europeanplaice/field_accessor
max_upload_size
id580177
size339,493
Tomohiro Endo (europeanplaice)

documentation

README

field_accessor

With this procedural macro, you can dynamically get and update a field of the struct by a String type variable. It can be good for you if you don't know which field you want when compiling. The functionality is similar to python's getattr, setattr.

Installation

Add the crate from crates.io:

[dependencies]
field_accessor = "0.6.2"

You can also run cargo add field_accessor to record the current patch version automatically.

What's new in v0.6.2

  • The derive macro now propagates struct generics and lifetimes so you can use FieldAccessor with structs that contain reference fields or other borrowed data.

Breaking changes in v0.6

  • FieldEnum variants now hold references instead of owned values (for example DogFieldEnum::name(&String)), so pattern matches that moved the field value must be updated.
  • The generated trait and inherent methods take &str for field names instead of &String. Code storing function pointers or relying on previous signatures needs to adjust.

About this macro

This macro generates a GetterSetter<T> trait for the derived struct and implements it for every field type that appears in the struct. Each implementation exposes the same API, allowing you to work with any field whose type is T.

pub trait StructNameGetterSetter<T> {
fn get(&self, field_string: &str) -> Result<&T, String>;
fn get_mut(&mut self, field_string: &str) -> Result<&mut T, String>;
fn take(&mut self, field_string: &str) -> Result<T, String>;
fn replace(&mut self, field_string: &str, src: T) -> Result<T, String>;
fn set(&mut self, field_string: &str, value: T) -> Result<(), String>;
}

impl StructNameGetterSetter<String> for StructName { /* generated */ }
impl StructNameGetterSetter<u32> for StructName { /* generated */ }
// ...one impl per distinct field type

In addition to the trait implementations, the macro adds inherent methods such as swap, getenum, and getstructinfo on the struct itself.

get

fn get(&self, field_string: &str) -> Result<&T, String>;

Returns an immutable reference to the requested field. You need to specify T (for example let value: &String = dog.get(name)?;) so that the compiler can pick the correct implementation.

get_mut

fn get_mut(&mut self, field_string: &str) -> Result<&mut T, String>;

Returns a mutable reference to the field corresponding to field_string.

set

fn set(&mut self, field_string: &str, value: T) -> Result<(), String>;

Replaces the field with value and discards the previous value.

take

fn take(&mut self, field_string: &str) -> Result<T, String>;

Replaces the field with T::default() (so T must implement Default) and returns the previous value.

replace

fn replace(&mut self, field_string: &str, src: T) -> Result<T, String>;

Moves src into the field and returns the previous value.

swap

fn swap(&mut self, field_string: &str, field_string_y: &str) -> Result<(), String>;

Swaps the contents of two fields that share the same type. If the types differ, the macro does not generate a matching arm and the call fails at runtime.

getenum

fn getenum(&self, field_string: &str) -> Result<StructNameFieldEnum<'_>, String>;

Returns the requested field wrapped in a generated enum that preserves the field's name and returns references. This avoids having to specify T explicitly when you need to work with heterogeneously typed fields.

getstructinfo

fn getstructinfo(&self) -> StructNameStructInfo;

Provides metadata about the struct: the declared field names, their type names (as strings), and the struct name itself.

Usage and Example

run

use field_accessor::FieldAccessor;

#[derive(FieldAccessor)]
struct Dog {
    name: String,
    age: u32,
    life_expectancy: u32,
    friends: Vec<String>,
}

fn main() {
    let mut dog = Dog {
        name: "Taro".to_string(),
        age: 3,
        life_expectancy: 9,
        friends: vec!["Mike".to_string(), "Nozomi".to_string()],
    };

    dog.set("name", "Jiro".to_string()).unwrap();
    let nickname: &mut String = dog.get_mut("name").unwrap();
    nickname.push_str(" the Dog");

    let age: &u32 = dog.get("age").unwrap();
    println!("{} is {} years old", nickname, age);

    let previous_name: String = dog.replace("name", "Ken".to_string()).unwrap();
    println!("{} used to be called {}", dog.name, previous_name);

    let removed_friends: Vec<String> = dog.take("friends").unwrap();
    println!("friends: {:?}", removed_friends);

    dog.swap("age", "life_expectancy").unwrap();
    println!("age={}, life_expectancy={}", dog.age, dog.life_expectancy);

    let fields = ["name", "age", "life_expectancy"];
    for field in fields.iter() {
        match dog.getenum(field).unwrap() {
            DogFieldEnum::name(v) => println!("name: {}", v),
            DogFieldEnum::age(v) => println!("age: {}", v),
            DogFieldEnum::life_expectancy(v) => println!("life_expectancy: {}", v),
        }
    }
}

output (abridged)

Jiro the Dog is 3 years old
Ken used to be called Jiro the Dog
friends: ["Mike", "Nozomi"]
age=9, life_expectancy=3
name: Ken
age: 9
life_expectancy: 3

This code is generated at compiling.

Borrowed fields

Starting from v0.6.2 you can derive FieldAccessor for structs that borrow data. Generics and lifetimes declared on the struct are forwarded to every generated impl.

use field_accessor::FieldAccessor;

#[derive(FieldAccessor)]
struct Config<'a> {
    title: &'a str,
}

let config = Config { title: "demo" };
assert_eq!(config.get("title").unwrap(), &"demo");

Known issues

You need to specify the data type of the returned value. If it is not given, the compiler cannot infer the type. This restriction reduces the convenience of using this macro.

#[derive(FieldAccessor)]
struct Dog {
    name: String,
    age: u32,
    life_expectancy: u32,
}

let mut dog = Dog {
    name: "Taro".to_string(),
    age: 3,
    life_expectancy: 9,
};
let fields = vec![
    "name".to_string(),
    "age".to_string(),
    "life_expectancy".to_string(),
];
for field_name in fields.into_iter(){
    let fieldvalue = dog.get(&field_name).unwrap();
};

This code raises an error.

let fieldvalue = dog.get(&field_name).unwrap();
    ----------       ^^^ cannot infer type for type parameter `T` declared on the trait `GetterSetter`
    |
    consider giving `fieldvalue` the explicit type `&T`, where the type parameter `T` is specified

A workaround is to replace get with getenum. This macro defines (struct name)FieldEnum<'a> behind the scenes for you like below.

enum DogFieldEnum<'a> {
    name(&'a String),
    age(&'a u32),
    life_expectancy(&'a u32),
}

You can use this as a return type. With this enum you can get any field's value without concerning a field's type while still borrowing the data.

let mut dog = Dog {
    name: "Taro".to_string(),
    age: 3,
    life_expectancy: 9,
};
let fields = vec!["name", "age", "life_expectancy"];
let mut fieldvalues = Vec::new();
for field_name in fields.into_iter() {
    fieldvalues.push(dog.getenum(field_name).unwrap());
}
assert!(matches!(fieldvalues[0], DogFieldEnum::name(value) if *value == "Taro"));
assert!(matches!(fieldvalues[1], DogFieldEnum::age(value) if *value == 3));

Getting struct's information

You can get the information of the struct with (field name)StructInfo by calling getstructinfo.

Definition of (field name)StructInfo

struct DogStructInfo {
    field_names: Vec<String>,
    field_types: Vec<String>,
    struct_name: String
}

Example

let info = dog.getstructinfo();
println!("{:?}", info);
for i in info.field_names.iter() {
    let fieldvalue = dog.getenum(i).unwrap();
    println!("{:?}", fieldvalue);
}

output

DogStructInfo { field_names: ["name", "age", "life_expectancy"], field_types: ["String", "u32", "u32"], struct_name: "Dog" }

name("Jiro")
age(4)
life_expectancy(10)

Author

Tomohiro Endo (europeanplaice@gmail.com)

Commit count: 53

cargo fmt