0

How can I store an arbitrary struct and recover it as a trait later?

I do not know the trait that will be requested. In some cases the struct might implement the trait but in others it might not.

I tried the object oriented way by storing the struct as Any and then downcasting it to the trait I need. However, as I understand, it is not possible to do since Rust doesn't have runtime reflection.

use std::{any::Any, collections::HashMap};

pub trait MyTrait {}

pub struct MyStruct {}
impl MyTrait for MyStruct {}

fn main() {
    let my_struct = MyStruct {};
    let as_any: Box<dyn Any> = Box::new(Box::new(my_struct));

    let mut hashmap = HashMap::new();
    hashmap.insert("key", as_any);

    // In this example, I try to downcast to my trait, but it could be an other trait elsewhere in the code.
    match hashmap.get_mut("key").unwrap().downcast::<Box<dyn MyTrait>>() {
        Some(_) => println!("Casting worked"),
        None => println!("Casting failed")
    }
}

Edit: Adding a little bit more details. This is for a library, I don't know in advance the traits or the structs that will be stored in the HashMap.

  • 3
    Not in the general sense. `Any` has no way of knowing if the original type implements `MyTrait` or not. It doesn't even properly know what type it is; `.downcast()` only works if the *concrete* type that is requested matches a unique identifier from the `dyn Any` trait object. So you'd have to convert from `Box` to `Box` to `Box`, but of course, this requires us to know what the concrete type was (not a great abstraction). – kmdreko Dec 28 '22 at 23:57
  • Can you not keep it as `Box` from the start? – kmdreko Dec 28 '22 at 23:57
  • Unfortunatly no, some structs stored in the `HashMap` might not implement `MyTrait` so I store it to in generic trait like `Any` and then try to downcast it to the trait I need. – Jonathan C.-R. Dec 29 '22 at 02:28
  • I updated the question to clarify this point. – Jonathan C.-R. Dec 29 '22 at 02:46
  • It sounds like you're about five design decisions deep down the rabbit hole of poor architecture. Rather than asking how to interface with this convoluted `HashMap`, can you explain to us how you ended up with a `HashMap` of arbitrary objects whose types aren't known at compile-time and which conditionally implement a trait you want to call methods against sometimes? It feels to me like you're missing an abstraction earlier on in your program that would avoid this whole mess. See [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Silvio Mayolo Dec 29 '22 at 03:00
  • I want to store multiple data provider which implement different interfaces to interact with. For instance, I have a PostgreSQL and S3 providers, PostgreSQL impl `Contacts` and `PhotosGallery` and S3 impl `FileSystem` and `PhotosGallery`. If I need a photo gallery for my app, both providers should be available. Also, this is meant to be a library so I don't know in advance the interfaces or the providers. The important part is that any provider can implement any interfaces but doesn't have to implement every interfaces. – Jonathan C.-R. Dec 29 '22 at 04:37
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community Dec 29 '22 at 06:47
  • You can create a `Box`, convert it to `Box`, then box it and convert it to `Box`. Now you can downcast to `Box`. – Chayim Friedman Dec 29 '22 at 07:44

1 Answers1

0

you can do something like that by using enums:

use std::any::Any;
use std::collections::HashMap;

pub enum MyEnum {
    MyTrait(Box<dyn MyTrait>),
    Any(Box<dyn Any>),
}

pub trait MyTrait {}

pub struct MyStruct {}
impl MyTrait for MyStruct {}

fn main() {
    let my_struct = MyStruct {};
    // let as_any: Box<dyn Any> = Box::new(Box::new(my_struct));

    let mut hashmap = HashMap::new();
    hashmap.insert("key", MyEnum::MyTrait(Box::new(my_struct)));

    // In this example, I try to downcast to my trait, but it could be an other trait elsewhere in the code.
    match hashmap.get_mut("key") {
        Some(my_val) => match my_val {
            MyEnum::MyTrait(_) => println!("its my trait"),
            MyEnum::Any(_) => println!("something else"),
        },
        None => println!("doesn't exist"),
    }
}
  • Thanks for your answer. I realized I forgot an important detail, I don't know the trait in advance, so cannot make an enum with all the traits. I added it to my question. – Jonathan C.-R. Dec 29 '22 at 04:53