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.
- 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();
}
- 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();
}
- 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();
}