0

I'm trying to use a trait object in a hashmap so I can call an update function.

This here is really the code I'm trying to write. I am pretty sure that I can change this code to get this to work the way that I want as-is, but I figured I can turn this into a learning opportunity because the Sized stuff is not quite clear to me.

let mut hm: HashMap<SomeEnum, Box<dyn SomeTrait>> = HashMap::new();
hm.entry(SomeEnum::Type1).and_modify(|b| b.some_other_func()).or_insert(/* HOW */ default());

But I don't really understand the Sized restrictions on trait objects or what Sized is (or why using Default as a Supertrait prevents the object from being Sized). If the entry does not exist in the hashmap, I would like to add the default version of that object to the entry.

Here's an reprex showing my issue.

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash)]
enum SomeEnum {
    Type1,
    Type2,
}

// SomeTrait is a subtrait of Default
// Because I want every type to implement Default
trait SomeTrait: Default {
    fn new() -> Self {
        Self {
            ..Default::default()
        }
    }
    fn some_other_func(&self);
}

// SomeStruct implements Default and SomeTrait
struct SomeStruct {
    foo: i32,
}

impl Default for SomeStruct {
    fn default() -> Self {
        SomeStruct { foo: 10 }
    }
}

impl SomeTrait for SomeStruct {
    fn some_other_func(&self) {}
}

fn main() {
    let mut hm: HashMap<SomeEnum, Box<dyn SomeTrait>> = HashMap::new();

    hm.entry(SomeEnum::Type1)
        .and_modify(|b| b.some_other_func())
        .or_insert(/* HOW */ default());
}

I cannot use a generic type instead of the trait object because I do/will have multiple implementations of this trait.

I have also tried creating a new trait that is a subtrait of both Default and the one I want:

trait AutoSomeType: SomeType + Default {}

trait SomeType {
    fn new() -> Self
    where
        Self: Sized,
    {
        Self {
            ..Default::default()
        }
    }
    fn some_other_func(&self);

}

I got here somehow based on a compiler recommendation, but I feel like this isn't on the right track to a solution.

exicx
  • 1
  • 1
  • Related: https://stackoverflow.com/questions/30353462/how-to-clone-a-struct-storing-a-boxed-trait-object – PitaJ Feb 15 '23 at 19:26
  • 3
    What type will be defaulted? Suppose the trait is implemented for `A` and `B`. Will `default()` give you the defaulted instance of `A` or `B`? – Chayim Friedman Feb 15 '23 at 19:29
  • @PitaJ I don't really see how that is related, except the fact that both are builtin traits. – Chayim Friedman Feb 15 '23 at 19:31
  • The question I raised is essentially the first reason `Default` is not object-safe (it has a method without a self parameter, an associated method). The second (it returns `Self`) can be solved for your case. – Chayim Friedman Feb 15 '23 at 19:33
  • @ChayimFriedman Excellent question. I hadn't considered how it would "know" which one I wanted. There's no linkage between the enum type and the structs. Of course. So I'll probably end up with a match on the enum type then and inserting the new struct if it's missing rather than trying the .or_insert() method. – exicx Feb 15 '23 at 19:34
  • This is exactly what you should do (but there is no reason to avoid `or_insert()`, just `or_insert(Box::new(Type::default()))`). You, as the owner of the map, have the information what type you want - the trait doesn't know, so you should create it. – Chayim Friedman Feb 15 '23 at 19:36

0 Answers0