mercredi 8 juin 2022

Rust: How to implement 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 then 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? Thanks!

Aucun commentaire:

Enregistrer un commentaire