Implement struct methods to do something on specific field

Hello, I have the following situation, that is best explained with the code below.

I have a struct with multiple similar fields, that I need to modify by calling a kind-of-setter method that calls other methods implemented on the struct.
This single setter method should modify the given field, however as I also require other methods implemented on the struct, I need to parse the struct instance as well as the reference to the field.

This doesn't work in Rust because now I have multiple mutable references to basically the same variable.

struct SomeStruct {
    a: u8,
    y: u8,
    foo: u64
};

impl SomeStruct {
    fn i_need_to_call_this(&mut self) {
        self.foo = 0;
    }

    fn write_to_field(&mut self, reference_to_field: &mut u8) {
        self.i_need_to_call_this();
        *reference_to_field = 42;
    }
}

fn main() {
    let mut this_struct: SomeStruct = SomeStruct { a:0, y: 1, foo: 2 };
    // These two will fail
    this_struct.write_to_field(&mut this_struct.a);
    this_struct.write_to_field(&mut this_struct.y);
}

I found this solution (How to pass a field name of a struct to a method of that struct? - #2 by jofas) that uses an enum representing the fields to choose between them.
However this requires that I remember to also update the enum when making changes (removing / adding fields) to the struct.

Are there other solutions that I'm not considering?
*reference_to_field = 42; would work if I don't pass the reference to the struct instance along, however I need that to call the i_need_to_call_this method ... :confused:

Problems of this type should, when possible, be solved by making the borrows more specific. Not every function needs to be a method. Here is a “naive” application of that principle:

struct SomeStruct {
    a: u8,
    y: u8,
    foo: u64
};

impl SomeStruct {
    fn i_need_to_call_this(foo: &mut u64) {
        foo = 0;
    }

    fn write_to_field(foo: &mut u64, reference_to_field: &mut u8) {
        SomeStruct::i_need_to_call_this(foo);
        *reference_to_field = 42;
    }
}

fn main() {
    let mut this_struct: SomeStruct = SomeStruct { a:0, y: 1, foo: 2 };
    SomeStruct::write_to_field(&mut this_struct.foo, &mut this_struct.a);
    SomeStruct::write_to_field(&mut this_struct.foo, &mut this_struct.y);
}

Of course, this may expose more detailed structure than you want to. Consider this the “just barely makes it work” version, and then refine it from there. To get more help, share with us more of the actual structure of your problem — what do the fields mean? Does i_need_to_call_this() modify multiple fields? Does your struct have any invariants?

You can write such a function in a way that the compiler will help you get it right.

let Self { foo, bar } = self;
match field {
    Field::Foo => *foo = 0,
    Field::Bar => *bar = 0,
}

This way, you will get an error reminding you to update the match, and thus the enum too, any time a field is added or removed.

1 Like

Thanks for your answer, I will try modifying my code accordingly, I'm currently spamming &mut self in nearly every method. That's probably the root cause of this problem now.

a and y are registers of an emulated CPU, while foo is the program counter.

i_need_to_call_this represents emulated CPU instructions that do the same but affecting different registers (a & y).
So i_need_to_call_this writes / reads from another field which represents the RAM, changes foo.

My guess is that the enum will be a good way to handle this problem. You can think of this enum not as referring to fields but referring to registers, and being a small piece of the instruction decoding part of the problem, rather than something introduced purely for the sake of following borrowing rules.

I will be using both of your recommendations, restructuring part of the code to avoid excessive usage of &mut self and usage of the an enum to represent register usage.
Thank you for your help!

I personally like the pattern of representing VM registers by newtyping an array and implementing Index for it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.