Help on implementing Command pattern with dependent components

Business Requirements

I need to deploy a handful of services to an server environment. The services have dependent relationships (directed acyclic graph). For example, ServiceB relies on ServiceA to be deployed first and ServiceB needs to configure ServiceA (e.g. MyWebService is dependent on Nginx to be deployed beforehand for its reverse proxy needs)

Problem

Attempt #1

I tried to implement this with Command pattern, using Vec, trait objects similar to what Rust book described:

trait Service {
    fn deploy(&self){}
}

struct ServiceA {}
impl Service for ServiceA {}

struct ServiceB<'a> {
    dep: &'a ServiceA,
}
impl<'a> Service for ServiceB<'a> {}

struct Environment {
    services: Vec<Box<dyn Service>>
}
impl Environment {
    fn deploy(&self) {
        for service in &self.services {
            service.deploy();
        }
    }
}

fn main() {
    let service_a = Box::new(ServiceA {});
    let service_b = Box::new(ServiceB { dep: &service_a });
    let environment = Environment {
        services: vec!(service_a, service_b)
    };
    environment.deploy();
}

This results in compile errors:

error[E0597]: `service_a` does not live long enough
  --> src/main.rs:26:45
   |
26 |     let service_b = Box::new(ServiceB { dep: &service_a });
   |                                             ^^^^^^^^^ borrowed value does not live long enough
27 |     let environment = Environment {
28 |         services: vec!(service_a, service_b)
   |                                   -------- cast requires that `service_a` is borrowed for `'static`
...
31 | }
   | - `service_a` dropped here while still borrowed

error[E0505]: cannot move out of `service_a` because it is borrowed
  --> src/main.rs:28:24
   |
26 |     let service_b = Box::new(ServiceB { dep: &service_a });
   |                                              --------- borrow of `service_a` occurs here
27 |     let environment = Environment {
28 |         services: vec!(service_a, service_b)
   |                        ^^^^^^^^  -------- cast requires that `service_a` is borrowed for `'static`
   |                        |
   |                        move out of `service_a` occurs here

Attempt #2

I added Rc for shared ownership:

use std::rc::Rc;

trait Service {
    fn deploy(&self){}
}

struct ServiceA {}
impl Service for ServiceA {}

struct ServiceB<'a> {
    dep: &'a ServiceA,
}
impl<'a> Service for ServiceB<'a> {}

struct Environment {
    services: Vec<Rc<Box<dyn Service>>>
}
impl Environment {
    fn deploy(&self) {
        for service in &self.services {
            service.deploy();
        }
    }
}

fn main() {
    let service_a = Rc::new(Box::new(ServiceA {}));
    let service_b = Rc::new(Box::new(ServiceB { dep: &service_a }));
    let environment = Environment {
        services: vec!(Rc::clone(&service_a), Rc::clone(&service_b))
    };
    environment.deploy();
}

Now the errors become:

error[E0308]: mismatched types
  --> src/main.rs:30:34
   |
30 |         services: vec!(Rc::clone(&service_a), Rc::clone(&service_b))
   |                                  ^^^^^^^^^^ expected trait object `dyn Service`, found struct `ServiceA`
   |
   = note: expected reference `&Rc<Box<dyn Service>>`
              found reference `&Rc<Box<ServiceA>>`

error[E0308]: mismatched types
  --> src/main.rs:30:57
   |
30 |         services: vec!(Rc::clone(&service_a), Rc::clone(&service_b))
   |                                                         ^^^^^^^^^^ expected trait object `dyn Service`, found struct `ServiceB`
   |
   = note: expected reference `&Rc<Box<dyn Service>>`
              found reference `&Rc<Box<ServiceB<'_>>>`

Can you help shed some light on the errors? And I wonder if there is better way to solve this problem in Rust? Thanks!

I'm a newb but I'll take a stab at part of the Rc situation. Internally, Rc::new() already automatically boxes its argument I believe, so maybe get rid of the Box in Vec<Rc<Box<dyn Service>>>.

1 Like

You should be able to use Rc for this, but you have to use an Rc instead of the reference.

struct ServiceB {
    dep: Rc<ServiceA>,
}

In general, if you have a lifetime annotation on any of your service structs, then you're probably doing it wrong.

(Also, you can get rid of the Box inside the Rc.)

2 Likes

Thank you @ToiletScript and @alice . It works like magic!

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.