Do work on nested struct

I'm a bit puzzled about what is the idiomatic way to work with this case.

Let's say I have:

pub struct A {
    b: B,
}

struct B {
    ...
}

impl A {
    pub fn new() -> Self {
        ...
    }
}

impl B {
    fn do_work(&self) {
        ... 
    }
}

fn main() {
    let var = A::new();
}

At this point I need to do some work on B.

I can see at least three way to do that:

  1. Made struct B and the do_work function public so that it can be accessed directly by the user (not advised):
var.b.do_work();
  1. Add a new helper function in A to do the work on B (not scalable):
impl A {
    pub fn do_work_on_B(&self) {
        self.b.do_work();
    }
}
  1. Add a "getter" method for B:
impl A {
    fn get_B(&self) -> &B {
        &self.b
    }
}

fn main() {
    let var = A::new();
    let pb = var.get_B();
    pb.do_work();
}

Any other idiomatic way to achieve this or what is the "common RUST" way to accomplish this?

Does A contain more fields besides b, or is it meant to be only a container for b?

It does contain more fields besides b.

It depends really; if you have no invariants on b and are willing to have it available as a field forever, making a field public is fine. Usually better than a mutable getter IMO.[1]

Not advised if you have invariants on b though.

The example makes it looks like the user of A wants to operate on B independently of A (as opposed to A needing to utilize B in it's own A-centric methods). In which case the getter (or public field, if acceptable)[2] is probably fine.[3]

Those or variations on them are mainly what comes to mind.

Sometimes it make sense to take a F: FnMut(&B) closure or a similar visitor pattern to have more control of the borrow or aftermath. But that's usually because you have a specific reason; it's not a default design.


  1. Still more limiting to A since you can't non-breakingly move it to a nested field or other storage (HashMap entry, ...), but more flexible for consumers who don't need to borrow all of A to get the B. ↩︎

  2. Rust will probably get read-only fields of some sort eventually too ↩︎

  3. A & getter can be as much of a commitment as a &mut getter if there's any shared mutability like a RefCell involved though. ↩︎

Yes, probably I should add some context to explain why.

In this case struct A and B must be nested because represent (two portion of) a data block that is serialized to (and deserialized from) a TOML file using the toml crate.

During the execution though, the user actions can modify data that is present in A or B so indeed sometimes the user of A want to operate on B independently of A.

Then it's really going to depend on the invariants IMO. A &mut getter doesn't protect A much more than a public field (the user could just entirely replace b[1]).

So if you have invariants, you need to wrap modifying access to B in some way, a common form of which would be mirroring the methods of B on A.

If you have no invariants I'd probably just make it public, personally, but a getter is still an option.


  1. so the only protection is structural -- if you want to refactor A or rename the b field or something -- which helps not at all for invariants ↩︎

2 Likes

I might suggest that if the external representation (the TOML file and the corresponding structure) don't match the internal needs of the program, that you not use the external representation as your internal representation. In a situation like this, for example, I might be tempted to have a function pair for this:

/* read into `parsed */
 // split the struct into two separate values
let (a, b) = parsed.into_parts();
/* … operate on a, b, or both, as needed … */
// recombine them into a single value for serialization
let output = A::from_parts(a, b);
/* serialize `output` */
2 Likes

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.