Sharing a struct between other structs

Hey all, I'm struggling a bit with how to structure my data.

I'm building a wrapper for communicating with a servomotor controller via serial, which then communicates with a number of servos. I was hoping to make something which would work like this:

struct Controller { /* controller stuff */ }
struct Servo { /* ??? */ }

// ... impls ...
fn main() {
  let controller = Controller::new("/dev/ttyUSB0") // this handles all communication with the controller thru serial
  let servoA = Servo::new(&controller, 0) // declare a servo on the controller's first channel
  let servoB = Servo::new(&controller, 1) // declare a servo on the controller's second channel

  servoA.goto(90) // makes the servo go to to 90° by calling a function on controller
  // etc.
}

I'm not sure how to store a reference to controller inside my Servos, because of the following requirements:

  • it has to be mutable (e.g. a servo has to call fns that mutate the controller's state)
  • it has to be shared across any number of servos.

To me that sounds like I need a Rc<RefCell<Controller>>. Is that right ? If that's the case, how would I go about "hiding" that from the user (and preventing them from having to do sth ugly like let controller = Rc::new(RefCell::new(controller)))?

You could have Controller::new return a Rc<RefCell<Controller>> directly:

impl Controller {
    pub fn new(name: &str) -> Rc<RefCell<Self>> {
        /* implementation... */
    }
}

impl Servo {
    pub fn new(controller: &Rc<RefCell<Controller>>, id: u32) -> Self {
        Self {
            controller: controller.clone(),
            id
        }
    }
}

fn main() {
    let controller = Controller::new("/dev/ttyUSB0");
    let servoA = Servo::new(&controller, 0);
    let servoB = Servo::new(&controller, 1);
}

It doesn't entirely "hide" the Rc<RefCell> type from the user, but at least they don't have to write it :slightly_smiling_face:

1 Like

Yes, Rc<RefCell> is the right type for a shared, non-temporary reference to an object. You can also use Arc<Mutex> if you need this to be thread-safe.

In Rust, it's usually counter-productive to try to hide memory management as an implementation detail, because without a GC it's not a detail.

If you really want to hide the wrapper from the type signature, you can:

#[derive(Clone)]
struct Controller {
   inner: Arc<ControllerInner>,
}
2 Likes

You can also have the Controller struct use locks/cells/atomics internally, around individual fields that need to be mutated while shared. Then its methods can take &self, and you can store or pass types like &Controller or Rc<Controller>.

1 Like

Thank you all for your explanations, that clears it up !
Cheers :slight_smile: