Home > OS >  How do I write generic code that accepts either a trait or a smart pointer to the trait (Box, Rc, et
How do I write generic code that accepts either a trait or a smart pointer to the trait (Box, Rc, et

Time:11-06

I am writing a discrete event simulator that simulates processes, exchanging events. Processes are implementations of the trait process, and are stored in the simulation class, which tells them when to process events.

A simulation can contain processes of only one type, or it can contain processes of different types. This is why I need a freedom to be able to choose dynamic processes.

I currently do it with boxes. Every code that uses the simulation needs to create boxes and put their created objects in boxes.

trait Process {
    // methods
    fn start();
    fn process_event();
}

struct Simulation {
    processes: Vec<dyn Process>
}

impl Simulation {
    fn add_process(&mut self, p: Box<dyn Process>) {
        processes.push(p);
    }
    
    fn run() {
        // calls process_event on processes, based on simulation logic
    }
}

My biggest problem, is that in some cases these processes need to have self-references. Then I need to replace box with either Pin or OwningRef, and I don't want to have every possible simulation, creating a Pin or OwningRef. So, i would want some kind of generic parameter to tell simulation to use different classes for boxes.

A nice extension would be to be able to not have dynamic dispatch at all, and have simulation contain actual process types, not generics.

So, i want something like - Simulation<P> where P is either a Process or Deref<Process>.

CodePudding user response:

Both variants of generic Simulation are possible.

  1. Containing smart pointers to processes (playground):
trait Process {}
struct P {}
impl Process for P {}

struct Simulation<T: std::ops::Deref<Target = dyn Process>   Sized> {
    processes: Vec<T>
}

impl<T: std::ops::Deref<Target = dyn Process>   Sized> Simulation<T> {
    fn add_process(&mut self, p: T) {
        self.processes.push(p);
    }
    
    fn run(&self) {
        // calls process_event on processes, based on simulation logic
    }
}

fn main() {
    let mut s: Simulation<Box<dyn Process>> = Simulation { processes: vec![] };
    s.add_process(Box::new(P {}));
    s.run();
    
    let mut s: Simulation<std::rc::Rc<dyn Process>> = Simulation { processes: vec![] };
    s.add_process(std::rc::Rc::new(P {}));
    s.run();
}
  1. Containing actual process types (playground):
trait Process {}
struct P {}
impl Process for P {}

struct P2 {}
impl Process for P2 {}

struct Simulation<T: Process> {
    processes: Vec<T>
}

impl<T: Process> Simulation<T> {
    fn add_process(&mut self, p: T) {
        self.processes.push(p);
    }
    
    fn run(&self) {
        // calls process_event on processes, based on simulation logic
    }
}

fn main() {
    let mut s: Simulation<P> = Simulation { processes: vec![] };
    s.add_process(P {});
    s.run();
    
    let mut s: Simulation<P2> = Simulation { processes: vec![] };
    s.add_process(P2 {});
    s.run();
}
  1. You can even have one type of Simulation, containing different types of processes (playground):
trait Process {}
struct P {}
impl Process for P {}

struct P2 {}
impl Process for P2 {}

struct Simulation {
    processes: Vec<Box<dyn Process>>
}

impl Simulation {
    fn add_process<T: Process   'static>(&mut self, p: T) {
        self.processes.push(Box::new(p));
    }
    
    fn run(&self) {
        // calls process_event on processes, based on simulation logic
    }
}

fn main() {
    let mut s: Simulation = Simulation { processes: vec![] };
    s.add_process(P {});
    s.run();
    
    let mut s: Simulation = Simulation { processes: vec![] };
    s.add_process(P2 {});
    s.run();
}
  • Related