Is there an idiomatic way to implement a component template?

Basically, an object (structure) is created by arranging different components. Each specific component is easily replaced by another component corresponding to the interface (I assume this property).

I'm currently trying to implement with traits that have led me to some errors and made me start to think if this is a common thing in Rust.

// usage example fn main() { let obj = MainObject::new(Component1::new(), Component2::new(), Component3()); // Where each component is a type(struct) with some well predefined methods. } 

The main idea is to implement the Component template commonly used in games. Basically, the game will contain many different objects, with small variations in behavior and the data contained. Instead of having a hierarchy of a large class, objects are composed of standard components, a more complete example would be.

 pub struct Container { input: InputHandlerComponent, // Probably a trait physics: PhysicsComponent, // Probably a trait renderer: RendererCompoent // Probably a trait } impl Container { fn new(p: PhysicsComponent, i: InputComponent, r: RenderComponent) -> Container { Container {input: i, physics: p, renderer: r} } } struct ConcretePhysicsComponent; impl PhysicsComponent for ConcretePhysicsComponent { // ... } struct ConcreteInputComponent; impl InputComponent for ConcreteInputComponent { // ... } struct ConcreteRendererComponent; impl RendererComponent for ConcreteRendererComponent { // ... } struct AnotherConcreteRendererComponent; impl RendererComponent for AnotherConcreteRendererComponent { // ... } // usage example fn main() { let obj = Container::new(ConcreteInputComponent::new(), ConcretePhysicsComponent::new(), ConcreteRendererComponent::new()); // Where each component is a type(struct) with some well predefined methods. // This is a slightly modified version of this object, with changed rendering behaviour let obj2 = Container::new(ConcreteInputComponent::new(), ConcretePhysicsComponent::new(), AnotherConcreteRendererComponent::new()); } 
+4
source share
1 answer

It looks like you're just asking about traits, a few specific implementations of this trait, and a wrapper object that restricts itself to types that implement that trait. If desired, the container can implement the attribute, delegating it to the internal object.

 trait Health { fn life(&self) -> u8; fn hit_for(&mut self, lost_life: u8); } #[derive(Debug, Copy, Clone)] struct WimpyHealth(u8); impl Health for WimpyHealth { fn life(&self) -> u8 { self.0 } fn hit_for(&mut self, lost_life: u8) { self.0 -= lost_life * 2; } } #[derive(Debug, Copy, Clone)] struct BuffHealth(u8); impl Health for BuffHealth { fn life(&self) -> u8 { self.0 } fn hit_for(&mut self, lost_life: u8) { self.0 -= lost_life / 2; } } #[derive(Debug, Copy, Clone)] struct Player<H> { health: H, } impl<H> Health for Player<H> where H: Health { fn life(&self) -> u8 { self.health.life() } fn hit_for(&mut self, lost_life: u8) { self.health.hit_for(lost_life) } } fn main() { let mut player_one = Player { health: WimpyHealth(128) }; let mut player_two = Player { health: BuffHealth(128) }; player_one.hit_for(12); player_two.hit_for(12); println!("{:?}", player_one); println!("{:?}", player_two); } 

it is impossible to have an array of such Players without using boxed values

It is right. An array or vector (or any general type, really) must be of the same type. This is especially important for arrays / vectors because their memory layout is continuous and each element must be in a fixed interval.

If you were allowed to have different types, then you may have one player who had health, which took 1 byte, and another player with health took 2 bytes. Then all offsets will be wrong.

You can implement the Health attribute for Box<Health> , and then Player objects can be saved sequentially, but they have a pointer to the corresponding specific Health implementation through the field.

 impl<H: ?Sized> Health for Box<H> where H: Health { fn life(&self) -> u8 { (**self).life() } fn hit_for(&mut self, lost_life: u8) { (**self).hit_for(lost_life) } } fn main() { let mut players = vec![ Player { health: Box::new(WimpyHealth(128)) as Box<Health> }, Player { health: Box::new(BuffHealth(128)) as Box<Health> } ]; for player in players.iter_mut() { player.hit_for(42); } println!("{:?}", players[0].life()); println!("{:?}", players[1].life()); } 
+5
source

All Articles