Return a reference to a boxed struct with trait

Hello all!

I suspect this is a common problem but I can't find solution elsewhere.

I try to return a reference to a boxed struct (ImageResource) having specific trait (Resource).

trait Resource {
// ...
}

struct ImageResource {
// ...
}

impl ImageResource {
    fn width() -> u32 { /*...*/ }
}

impl Resource for ImageResource {
// ...
}

struct ResourceManager {
    registry: HashMap<u32, Box<dyn Resource>>,  // id to resource
}

fn image(rman: &mut ResourceManager, name: &str) -> &ImageResource {
    let resource_id = calculate_hash(&name.to_string()) as u32;

    // Try to return resource from registry.
    match rman.registry.get(&resource_id) {
        Some(&r) => return r.as_ref() as ImageResource, // problem here!
        None => (),
    }

    // ... // Get img_data.

    // Put resource in registry.
    let resource = Box::new(ImageResource { data: img_data });

    rman.registry.insert(resource_id, resource);

    &resource
}

I suspect the problem is here:

return r.as_ref() as ImageResource,

r is a Box<dyn Resource> and I want to return it as a &ImageResource so the caller can execute ImageResource specific functions (like width()).

As you guess, there will be other kind of resource type (model, texture, etc.).

  • Can this be done?
  • Is this the good way to handle this? Rust tends to invert how data is managed compared to OOP and I could miss the point (again).

Thanks in advance! :slight_smile:

You can't cast a &dyn Resource to an &ImageResource because the trait object might not be an ImageResource.

1 Like

Thanks alice! Does that mean I need to have as many hash map than resource type?

Like this:

struct ResourceManager {
    registry_img: HashMap<u32, ImageResource>,
    registry_sound: HashMap<u32, SoundResource>,
    // ...
}

So I can't have a true global ressource id?

You could probably engineer something using the Any trait, but I think I would prefer a hash map per resource. Alternatively an enum is probably better than a trait.

1 Like

Thanks for pointing Any, it looks like the kind of RTTI I could use elsewhere. Reading Any code, I decided to run for the unsafe way. I could go back and use Any if RTTI is really a requirement.

In the purpose of knowledge, I share it here:

pub fn shader_code(&mut self, name: &str) -> &ShaderCodeResource {
    let resource_id = calculate_hash(&name.to_string()) as ResourceId;

    // Push new data if needed
    if !self.registry.contains_key(&resource_id) {

        // ...read the shader code...

        let resource = Box::new(ShaderCodeResource { data: shader_code });

        self.registry.insert(resource_id, resource);
    }

    // Try to return resource from registry.
    match self.registry.get(&resource_id) {
        Some(r) => {
            return unsafe {
                &*(r.as_ref() as *const dyn Resource as *const ShaderCodeResource)
            }
        }
        None => panic!("Can't get resource after insert."),
    }
}

So:

return unsafe {
    &*(r.as_ref() as *const dyn Resource as *const ShaderCodeResource)
}

This part is actually taken from the Any source code, but without the Option deal, as we would actually use unwrap() for this.

It seems to work. I will see on the long run.

Thanks again for the precious help! :slight_smile:

Please don't do that. Just use an enum :frowning:

1 Like

Ouch. That smells like a really bad idea. You are basically using unsafe there in order to prevent type checking. Don't do that, it completely undermines the idea of strong, static typing.

Why are you trying to return a concrete type if your are storing trait objects? Or, conversely, why are you storing trait objects if you need a concrete type?

1 Like

OK, so it's a bad idea. :frowning:

I'm not sure to get how an enum can help here. A trait function returning an enum representing a type? Like hand-made RTTI?

Even with that, Once I know my dyn Resource is ShaderCodeResource or ImageResource, how am I supposed to access to concrete type functions?

I try to avoid any check at runtime.

So I can use function specialized for the concrete type. Like a compile() function on a ShaderCodeResource.

So I can have a unique identifier for resources.

But maybe I am wrong in the way I'm handling this and Any (or enum once I get how to) is the way to go.

You use an enum instead of the trait. The Resource type would be an enum.

1 Like

Unique identifiers are borderline useless without type information.

If you have a table of resources of N different types with unique IDs, but there's no way to take an ID and find the correct type, you effectively already have N different tables of different types; they're just interleaved for some reason. That is, you still have to know what type a thing is before you can use its ID to retrieve it from the table. The ID itself doesn't tell you anything. So you might as well actually make it N different tables with different types, and let the ID values overlap; the type information makes an ImageResourceId(42) distinct from a ShaderCodeResourceId(42) even though they both have the value 42. They're not interchangeable.

The only way to make them interchangeable is to put the type information inside the table -- either with some form of Any, or with an enum. Then you can have just a plain ResourceId(42) and look it up and decide what to do with the resource when you get it.

3 Likes