Hello Rust Community!
This may be a bit of a nebulous ask, but I would appreciate some general feedback from seasoned Rust veterans. I'm fairly new to the language and implemented this basic Event Simulator as a way to improve my Rust. It's pretty simple - there are a bunch of objects that send events to each other. This is my first project working with traits. It works insofar as the small example at the end (a single object echoing events to itself), but I'm not happy with several elements. I'd appreciate any tips you're willing to offer to improve it, but I do have some specific concerns and questions below and would appreciate specific feedback about those. Small tweaks, complete redesigns.. all fair game, I just want to learn. Thanks!
-
How can I eliminate the HashMap? It's being used as a hack to allow a
QueuedEventto contain a "reference" (String key) to aSimObject. I tried references at first, but things got really ugly with lifetimes. Plus, it ultimately needs to be a mutable reference because we need to call itsprocess_event()method (line 101), and that seems like it will break some borrowing rules. -
You'll see I use
Box<dyn SimEvent>in several places. I don't have a particular reason to useBox<>except that I couldn't figure out another way. Open to other ways of passing/holdingSimEventsif there's an advantage. I like the<X: SimEvent>notation, but not sure how it would work here. See next question for possible consideration. -
Eventually, a particular
SimObject's process_event() method needs to be able to "downcast" the inputSimEvent (or at least access methods/fields). ImagineExampleEvent carried some data and had a method. How couldprocess_event()determine that theSimEventwas anExampleEventand access those? I've looked intoAny, which seems like it would work, but I'm not sure how to properly use it with traits and/or boxes. Are there other solutions? For example, I wish I could defineExampleObject'sprocess_event()to take an argument that implementsSimEvent + SomeOtherTrait, but my attempts to do so cause compiler complaints about matching the trait definition. -
The uuids in the
QueuedEvents feel a little dumb - always initialized to 0, then set inEventSimulator->queue_event(). Is there a way I could get them to have a counter id that is correct at initialization?
Here's the code. Thanks so much for your time.
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};
trait SimEvent {}
trait SimObject {
fn process_event(&mut self, e: Box<dyn SimEvent>, time: u64) -> Vec<QueuedEvent>;
}
struct QueuedEvent {
event: Box<dyn SimEvent>,
target: String,
delivery_time: u64,
uuid: u64,
}
impl QueuedEvent {
fn new(event: Box<dyn SimEvent>, target: String, delivery_time: u64) -> Self {
Self {
event: event,
target: target,
delivery_time: delivery_time,
uuid: 0,
}
}
}
// stuff to make QueuedEvent orderable
impl Ord for QueuedEvent {
fn cmp(&self, other: &Self) -> Ordering {
if self.delivery_time != other.delivery_time {
self.delivery_time.cmp(&other.delivery_time).reverse()
} else {
self.uuid.cmp(&other.delivery_time).reverse()
}
}
}
impl PartialEq for QueuedEvent {
fn eq(&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
impl Eq for QueuedEvent {}
impl PartialOrd for QueuedEvent {
fn partial_cmp(&self, other: &QueuedEvent) -> Option<Ordering> {
Some(self.cmp(other))
}
}
struct EventSimulator {
current_time: u64,
end_time: u64,
uuid_counter: u64,
queue: BinaryHeap<QueuedEvent>,
objects: HashMap<String, Box<dyn SimObject>>,
}
impl EventSimulator {
fn new(end_time: u64) -> Self {
let q = BinaryHeap::new();
let o: HashMap<String, Box<dyn SimObject>> = HashMap::new();
Self {
current_time: 0,
end_time: end_time,
uuid_counter: 0,
queue: q,
objects: o,
}
}
fn add_object(&mut self, name: String, object: Box<dyn SimObject>) {
self.objects.insert(name, object);
}
fn run(&mut self) {
loop {
let qe = match self.queue.pop() {
Some(q) => q,
None => {
println!(
"Event Queue is empty, ending simulation at time {}",
self.current_time
);
break;
}
};
self.current_time = qe.delivery_time;
if self.current_time >= self.end_time {
println!(
"self.current_time >= self.end_time ({}>={}), ending simulation",
self.current_time, self.end_time
);
break;
}
let target = match self.objects.get_mut(&qe.target) {
Some(t) => t,
None => {
println!("SimObject {} not found! Exiting.", qe.target);
break;
}
};
for returned_event in target.process_event(qe.event, self.current_time) {
self.queue_event(returned_event);
}
}
}
fn queue_event(&mut self, mut qe: QueuedEvent) {
qe.uuid = self.uuid_counter;
self.uuid_counter += 1;
self.queue.push(qe);
}
}
// BEGIN EXAMPLE APPLICATION
struct ExampleEvent {}
impl SimEvent for ExampleEvent {}
struct ExampleObject {}
impl SimObject for ExampleObject {
fn process_event(&mut self, e: Box<dyn SimEvent>, time: u64) -> Vec<QueuedEvent> {
println!("Got the event at time {}", time);
vec![QueuedEvent::new(e, String::from("test_obj_1"), time + 1)]
}
}
fn main() {
let mut sim = EventSimulator::new(10);
sim.add_object(String::from("test_obj_1"), Box::new(ExampleObject {}));
let e = Box::new(ExampleEvent {});
let qe = QueuedEvent::new(e, String::from("test_obj_1"), 1);
sim.queue_event(qe);
sim.run();
}