I am taking a Rust course, and there is a home assignment I got stuck on.
We are creating a "smart home" library, which should have an entity for House, an entity for Room and an interface for a smart device. The user should be able to create an instance of a house, add an arbitrary number of rooms to it, implement the interface for smart devices and add any number of those to the rooms. The house should provide a possibility to generate reports from all devices in all the rooms, and the user must be able to interact with all the devices in all the rooms.
My first approach was to make the House
to store a vec of rooms, and then the rooms store vecs of devices
pub struct House<'a> {
pub name: String,
pub rooms: Vec<&'a Room<'a>>,
}
pub struct Room<'a> {
pub name: String,
pub devices: Vec<&'a dyn Device>,
}
pub trait Device: Named + Report {}
The problem with that approach is that when I implement the Device trait and add instances of those devices to the rooms, when I get them back, their type is unknown. I know it is possible to look at their TypeIDs and then cast them, but I don't want to do this just yet.
Then I tried another approach:
Lets implement a DeviceStorage<T: Device>
. Whenever you add a thing to it, it returns an UUID, and then you can store that UUID in a room. And then, when we want to generate a report, we will be able to pass all the device-specific storages to a house, the house will pass them down to the rooms, the rooms will filter the devices in those storages by the UUIDs those rooms hold and make the required reports. That way we will always know the type of those devices because they are stored in generic storages.
pub struct DeviceStorage<T: Device> {
pub devices: HashMap<uuid::Uuid, T>,
pub devices_by_name: HashMap<String, uuid::Uuid>,
}
impl<T: Device> DeviceStorage<T> {
pub fn add(&mut self, device: T) -> uuid::Uuid {
let s = uuid::Uuid::new_v4();
self.devices_by_name.insert(device.name().clone(), s);
self.devices.insert(s, device);
s
}
pub fn by_uuids(&self, uuids: &[uuid::Uuid]) -> Vec<&T> {
self.devices
.iter()
.filter(move |(uuid, _)| uuids.contains(uuid))
.map(|(_, socket)| socket)
.collect::<Vec<&T>>()
}
}
fn main() {
let mut sockets = p2::DeviceStorage::<PlugSocket>::new();
let uuid1 = sockets.add(PlugSocket::new_grid_socket("one"));
let uuid2 = sockets.add(PlugSocket::new_grid_socket("two"));
let mut house = p2_locs::House::new("house of the rising sun");
house.devices.push(uuid1);
house.devices.push(uuid2);
}
Again, the next step in my plan was to put such device storages in a vector and pass them to the house to generate a report. However, the problem now is that I cannot make a vector of these device storage instances because they are of different types:
let my_vec: Vec<&DeviceStorage<&dyn Device>> = vec![&sockets, &thermos];
---
the trait bound `&dyn another_rust_thing::api::Device: another_rust_thing::api::Device` is not satisfied
the following other types implement trait `another_rust_thing::api::Device`:
another_rust_thing::devices::PlugSocket
another_rust_thing::devices::Thermometer
Is there a proper approach for doing this (I mean, storing things in collections of collections and still keeping track of their types)? Or is there a proper way of declaring that vector of DeviceStorages that would work?