Hard time creating a multi-subtype HashMap

Hello, I have the following MappedStore:

pub struct MappedStore<T: Message + Default + Clone> {
    pub cache: RefCell<HashMap<u32, T>>,
}
impl<T: Message + Default + Clone> MappedStore<T> {}

Which works great, but I'm having trouble creating a HashMap with different types T inside MappedStore. I tried enum variation but I can't specify Message + Default + Clone and it wont satisfy the compiler. I believe a trait object would be the way to go but with Default and Clone rust won't be able to create it.

pub trait StorableMessage: Message + Default + Clone {}

pub struct MappedStoreTable {
    stores: HashMap<String, MappedStore<Box<dyn StorableMessage>>>,
}

Problems:


the trait `StorableMessage` cannot be made into an object
`StorableMessage` cannot be made into an object

the size for values of type `(dyn StorableMessage + 'static)` cannot be known at compilation time
the trait `Sized` is not implemented for `(dyn StorableMessage + 'static)`
the trait `Message` is implemented for `Box<M>`
required because of the requirements on the impl of `Message` for `Box<(dyn StorableMessage + 'static)>`

Thanks in advance.

Clone is doable if you use an enum (not dyn) and #[derive(Clone)] on that enum.

For Default, you'll need to decide: what is the default value? You'll have to pick one of the types, or have an enum variant for being the "default/unspecified/nothing" value. Perhaps you don't actually need Default, though — after all, the HashMap can simply not contain a value for that key.

If you want more specific advice about your problem, you'll need to tell us more of what the problem is — what kinds of different types do you want to store, and for what purpose?

4 Likes

Box is a "fundamental" type, which means that you can implement traits for Box<LocalType> as if you were implementing for LocalType. In particular here, you can implement Default and Clone for Box<dyn StorableMessage>, even though the type and traits are both foreign.

For the former you'd have to choose a suitable base type. For the latter you'll need a little further help to bridge the dyn type erasure, e.g.:

pub trait StorableMessage: Message {
    // Implementers should just return `Box<self.clone()>`
    fn dyn_clone(&self) -> Box<dyn StorableMessage /* + Send + Sync */>;
}

impl Clone for Box<dyn StorableMessage /* + Send + Sync */> {
    fn clone(&self) -> Self {
        self.dyn_clone()
    }
}

Box<dyn Trait> doesn't automatically implement Trait, so you'd have to implement that as well.

I'm creating GRPC services for a project, those are using Protobuf messages. I also need to store some configuration data on server and well since I already have nice binary serialization in the protobuf message itself then thats what I use. MappedStore manages a collection of multiple indexed protobuf messages. For the store to function I had (compiler required it) to add traits such as Message + Default + Clone so it could encode and decode a generic protobuf message.

I have that working and now I'm trying to create a HashMap where I can access multiple mapped stores for a GRPC service. In the future I would store just references to the stores, but I decided to first figure out how to get multiple MappedStore<MessageA> and MappedStore<MessageB> into the same HashMap while both MessageA and MessageB are valid protobuf messages.

My problem is I cant find the solution to a HashMap that allows multiple types of protobuf messages which would satisfy traits of my MappedStore, rather I don't even know if such a multiple type which satisfies multiple traits can be done

So I wondered why I specify the same traits on my struct and impl and realized I only need Message trait on my struct with allowed me to simplify my HashMap type to just the the Message trait. However the compiler errors out with it being unsized at compilation time. But how am I supposed to do so when different messages have different sizes? I thought the Box type would prevent something like this when it allocates to the heap.

pub struct MappedStoreTable {
    stores: HashMap<String, MappedStore<Box<dyn Message>>>,
}

At a guess, something on Message implies Sized, but that's just a guess. What's the full error (from cargo check, say), and the declaration of Message?

The Sized is probably generated for each message struct from protoc.

Cargo check:

error[E0277]: the size for values of type `(dyn Message + 'static)` cannot be known at compilation time
  --> src/services/store.rs:12:13
   |
12 |     stores: HashMap<String, Box<MappedStore<dyn Message>>>,
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `(dyn Message + 'static)`
note: required by a bound in `store::MappedStore`
  --> src/store.rs:18:24
   |
18 | pub struct MappedStore<T: Message> {
   |                        ^ required by this bound in `store::MappedStore`
help: consider relaxing the implicit `Sized` restriction
   |
18 | pub struct MappedStore<T: Message + ?Sized> {
   |                                   ++++++++

error[E0277]: the size for values of type `(dyn Message + 'static)` cannot be known at compilation time
  --> src/services/store.rs:22:40
   |
22 |     pub fn add_store(&mut self, store: Box<MappedStore<dyn Message>>) {
   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `(dyn Message + 'static)`
note: required by a bound in `store::MappedStore`
  --> src/store.rs:18:24
   |
18 | pub struct MappedStore<T: Message> {
   |                        ^ required by this bound in `store::MappedStore`
help: consider relaxing the implicit `Sized` restriction
   |
18 | pub struct MappedStore<T: Message + ?Sized> {
   |                                   ++++++++

I think you meant MappedStore<Box<dyn Message>>, not Box<MappedStore<dyn Message>>.

Thanks for pointing that out, I was trying things out by rearranging the Box type. Still results in a very similar/same error.

error[E0277]: the size for values of type `(dyn Message + 'static)` cannot be known at compilation time
  --> src/services/store.rs:12:13
   |
12 |     stores: HashMap<String, MappedStore<Box<dyn Message>>>,
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `(dyn Message + 'static)`
   = help: the trait `Message` is implemented for `Box<M>`
   = note: required because of the requirements on the impl of `Message` for `Box<(dyn Message + 'static)>`
note: required by a bound in `store::MappedStore`
  --> src/store.rs:18:27
   |
18 | pub struct MappedStore<T: Message> {
   |                           ^^^^^^^ required by this bound in `store::MappedStore`

So, Message isn't your own trait, so the ability to implement Message for Box<dyn Message> it out of your hands. There is this:

impl<M> Message for Box<M>
where
    M: Message,

But because there's no M: ?Sized, it doesn't apply to M = dyn Message. Apparently the reason is that the implementation uses methods that have a Sized bound.

You might be able to create your own subtrait that meets your use-case (ala the example linked in that last comment?), but that's where I stopped digging.

1 Like

Thanks a lot, I'll have an attempt at that.

I think I made some progress, I changed MappedStore to just pub struct MappedStore<T> {} without any traits for T and I have a compiling HashMap of MappedStore<dyn Message>.

pub struct MappedStoreTable<'a> {
    stores: HashMap<String, &'a MappedStore<Box<dyn Message>>>,
}

impl<'a> MappedStoreTable<'a> {
    pub fn new() -> Self {
        Self {
            stores: HashMap::new(),
        }
    }

    pub fn add_store(&mut self, store: &'a MappedStore<Box<dyn Message>>) {
        self.stores.insert("test".to_string(), store);
    }
}

But now i wonder how do I add stores to the map when they used a generic and not a Box. I can wrap my types in a Box but there is still something wrong that Im not grasping.

Instancing code:
Note: I wrapped CpuTempResponse in a Box only to try out if I even need to

let cpu_store = MappedStore::<Box<CpuTempResponse>>::new(env::current_dir().unwrap(), None);
let gpu_store = MappedStore::<GpuTempResponse>::new(env::current_dir().unwrap(), None);
let storeTable = MappedStoreTable::new();
storeTable.add_store(&cpu_store);
storeTable.add_store(&gpu_store);
error[E0308]: mismatched types
  --> tests/core_test.rs:17:26
   |
17 |     storeTable.add_store(&cpu_store);
   |                          ^^^^^^^^^^ expected trait object `dyn Message`, found struct `CpuTempResponse`
   |
   = note: expected reference `&pitron_core::store::MappedStore<Box<(dyn Message + 'static)>>`
              found reference `&pitron_core::store::MappedStore<Box<CpuTempResponse>>`

error[E0308]: mismatched types
  --> tests/core_test.rs:18:26
   |
18 |     storeTable.add_store(&gpu_store);
   |                          ^^^^^^^^^^ expected struct `Box`, found struct `GpuTempResponse`
   |
   = note: expected reference `&pitron_core::store::MappedStore<Box<(dyn Message + 'static)>>`
              found reference `&pitron_core::store::MappedStore<GpuTempResponse>`