简体   繁体   中英

Trait with function without "self" as parameter cannot be made into an object

I am trying to build a entity component system as part of my journey to learn Rust. I had an idea where each component would have a static id, and objects would have a HashMap of the components it contains (with the limit of one component per type).

Here is the object itself:

pub struct Object {
    // TODO : components !
    components: HashMap<i32, Box<dyn Component>>
}

impl Object {
    pub fn add_component<C: Component>(&self) {
        self.components.insert(C::id(), Box::new(C::new()));
    }

    pub fn get_component<C: Component>(&self) -> Option<&C> {
        return self.components.get(C::id())
    }
}

And here is my Component trait:

pub trait Component {
    fn id() -> i32 {
        // one must ensure this returns different id for every component
        return 0;
    }

    fn new<C: Component>() -> C;

    fn require_rendering(&self) -> bool {
        return false;
    }

    fn some_function_on_component(&mut self) {
        // do something on the component 
    }
}

Unfortunately, I get this error: "this trait cannot be made into an object... ...because associated function id has no self parameter"

Could anyone explain why does this not work, and how to work around it?

Most of those are fairly easily fixable by following the compiler messages.

The biggest hurdle I came across while trying to get your code to compile is to downcast the Box<dyn> back to its original type .

That's my attempt, I have no idea if it actually does something in the end, but at least it compiles:)

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

pub struct Object {
    // TODO : components !
    components: HashMap<i32, Box<dyn Component>>,
}

impl Object {
    pub fn new() -> Self {
        Self {
            components: HashMap::new(),
        }
    }

    pub fn add_component<C: 'static + Component>(&mut self) {
        self.components.insert(C::id(), Box::new(C::new()));
    }

    pub fn get_component<C: 'static + Component>(&self) -> Option<&C> {
        self.components
            .get(&C::id())
            .map(|boxed_component| boxed_component.as_any().downcast_ref::<C>().unwrap())
    }
}

pub trait Component {
    fn id() -> i32
    where
        Self: Sized;

    fn new() -> Self
    where
        Self: Sized;

    fn require_rendering(&self) -> bool {
        return false;
    }

    fn some_function_on_component(&mut self) {
        // do something on the component
    }

    fn as_any(&self) -> &dyn Any;
}

Things I've done:

  • Add Self: Sized to id() and new() . Both of those are trait functions and therefore have to be resolved at runtime. The step of resolving the type requires something called a "vtable", which only exists on types that actually have a size. (that's at least my understanding of the problem)
  • Replace the generic parameter on new with Self , as this is what you probably actually want.
  • Add mut to self in add_component
  • Remove default implementation of id() to actually force implementing structs to overwrite it
  • Downcast the &dyn Component to the actual component type in get_component , based on this post . Of course there are probably different solutions, I just picked that one because I didn't feel like doing any further research:)

I didn't solve the problem that you currently only get non-mutable objects out of get_component . This will probably be the next thing you will tackle.

All in all, here is a minimal working example of your code with a "Name" and an "Age" component, just to demonstrate that your approach is feasible:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5d22b64ab924394606a07a043594f608

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM