Neskews
Neskews

Reputation: 824

Access struct field by variable

I want to iterate over over the fields of a struct and access its respective value for each iteration:

#[derive(Default, Debug)]
struct A {
    foo: String,
    bar: String,
    baz: String
}


fn main() {
    let fields = vec!["foo", "bar", "baz"];
    let a: A = Default::default();

    for field in fields {
        let value = a[field] // this doesn't work
    }
}

How can I access a field by variable?

Upvotes: 8

Views: 7391

Answers (4)

Jonathan Tran
Jonathan Tran

Reputation: 15306

Reflection

Although this is not idiomatic Rust, you can do it using reflection. This is implemented by the bevy_reflect crate.

Your example would be implemented like this.

use bevy_reflect::{GetField, Reflect};

#[derive(Reflect)]
struct A {
    foo: String,
    bar: String,
    baz: String
}

fn main() {
    let fields = vec!["foo", "bar", "baz"];
    let a = A {
        foo: "one".to_string(),
        bar: "two".to_string(),
        baz: "three".to_string()
    };

    for field in fields {
        let value = a.get_field::<String>(field).unwrap();
        println!("{value}");
    }
}

This will print:

one
two
three

You can also extract the field names of an arbitrary struct that implements Reflect.

use bevy_reflect::{Reflect, Struct};

#[derive(Reflect)]
struct A {
    foo: String,
    bar: String,
    baz: String
}

fn main() {
    let a = A {
        foo: "one".to_string(),
        bar: "two".to_string(),
        baz: "three".to_string()
    };

    for (i, value) in a.iter_fields().enumerate() {
        let field_name = a.name_at(i).unwrap();
        if let Some(value) = value.downcast_ref::<String>() {
            println!("{} has value: {}", field_name, *value);
        }
    }
}

This outputs:

foo has value: one
bar has value: two
baz has value: three

You can even get nested fields using path strings.

See the crate's readme for more information.

Upvotes: 0

Tee
Tee

Reputation: 111

By using pattern matching, you can iterate over its fields.

#[derive(Default, Debug)]
struct A {
    foo: String,
    bar: String,
    baz: String
}

impl A {
    fn get(&self, field_string: &str) -> Result<&String, String> {
        match field_string {
            "foo" => Ok(&self.foo),
            "bar" => Ok(&self.bar),
            "baz" => Ok(&self.baz),
            _ => Err(format!("invalid field name to get '{}'", field_string))
        }
    }
}

fn main() {
    let fields = vec!["foo", "bar", "baz"];
    let a = A {
        foo: "value_of_foo".to_string(), 
        bar: "value_of_bar".to_string(), 
        baz: "value_of_baz".to_string()
    };

    for field in fields {
        let value = a.get(field).unwrap();
        println!("{:?}", value);
    }
}

returns

"value_of_foo"
"value_of_bar"
"value_of_baz"

I have written a macro that automatically implements such code for any struct. field_accessor (https://github.com/europeanplaice/field_accessor).

Cargo.toml

[dependencies]
field_accessor = "0"
use field_accessor::FieldAccessor;

#[derive(Default, Debug, FieldAccessor)]
struct A {
    foo: String,
    bar: String,
    baz: String
}

fn main() {
    let a = A {
        foo: "value_of_foo".to_string(), 
        bar: "value_of_bar".to_string(), 
        baz: "value_of_baz".to_string()
    };

    for field in a.getstructinfo().field_names.iter() {
        let value = a.get(field).unwrap();
        println!("{:?}", value);
    }
}

It also returns

"value_of_foo"
"value_of_bar"
"value_of_baz"

Upvotes: 11

Neskews
Neskews

Reputation: 824

Based on the answer of sshashank124 I came to the conclusion that I should use an Hashmap instead of a struct:

fn main() {
    let mut B = HashMap::new();
    B.insert("foo", 1);
    B.insert("bar", 2);
    B.insert("baz", 3);

    let fields = vec!["foo", "bar", "baz"];

    for &field in &fields {
        let value = B.get(field);
    }
}

Upvotes: 3

sshashank124
sshashank124

Reputation: 32197

Rust doesn't have any way of iterating directly over its fields. You should instead use a collection type such as Vec, array or one of the collections in std::collections if your data semantically represents a collection of some sort.

If you still feel the need to iterate over the fields, perhaps you need to re-consider your approach to your task and see if there isn't a more idiomatic/proper way to accomplish it

Upvotes: 7

Related Questions