Say we have a characters and weapon. Each character has their own way to fight with weapon. From time to time, they change their weapon(perhaps because it got RUSTy). Then the trait for that will be something along the lines of:
trait TCharacter {
fn fight(&self);
fn set_weapon<T: TWeapon>(&mut self, with_weapon: T);
}
trait TWeapon: std::fmt::Debug {
fn use_weapon(&self);
}
Here, the trait design seems perfectly benign. Or doesn't it?
Let's implement King
#[derive(Debug)]
struct King {
weapon: Box<dyn TWeapon>,
}
King
has a weapon as a Box<dyn TWeapon>
, which is a trait object. This is a good design if the weapon can change during the lifespan of the king. So, you can implement TCharacter
and TWeapon
as follows:
impl TCharacter for King {
fn fight(&self) {
println!("king is fighting...");
self.weapon.use_weapon();
}
fn set_weapon<T: TWeapon + 'static>(&mut self, weapon: T) {
self.weapon = Box::new(weapon);
}
}
Okay, with this design, you successfully encapsulate HOW weapon is used by achieving HAS-A
relationship between King
and TWeapon
. As you can see here, King
is not aware of what kind of weapon it will receive.
Now, let's try to implement Knight
but in this time with generic:
#[derive(Debug)]
struct Knight<T: TWeapon> {
weapon: T,
}
Okay, Knight
HAS whatever the type that implements TWeapon
. But then ramification of this is:
impl<T: TWeapon> TCharacter for Knight<T> {
fn fight(&self) {
println!("knight is fighting...");
self.weapon.use_weapon();
}
fn set_weapon<U: TWeapon>(&mut self, with_weapon: U) {
self.weapon = with_weapon; // ERROR! expected type parameter `T`
}
}
For experienced Rust devs, this is obvious because what we are trying to do is we want to set weapon to Knight<T>
while given with_weapon
is of type U
. If we allow them, the type of receiver(self) will become Knight<U>
But then there is no sign for ownership transfer.
If we pretend that it wasn't for TCharacter
trait implemenation, the way you change the state of Knight
will be:
impl<T: TWeapon> Knight<T> {
fn set_weapon<U: TWeapon>(self, with_weapon: U) -> Knight<U> {
Knight {
weapon: with_weapon,
}
}
}
But again, our task is to implement TCharacter
.
Unfortunately, you cannot make it without changing either of the following:
Knight
set_weapon
and implement them for each structs At this point, you may be thinking it doesn't make sense that you have to remove or change the field type. But I guarantee you, you cannot make generic struct that HAS TWeapon
and also implmenets TCharacter
as it is now.
For those curious, let's see the next futile attempts:
Try 1: introducing ownership transfer and associated type:
trait TCharacter {
type Weapon; // associated type
...
//ERROR associated item referring to unboxed trait object for its own trait
fn set_weapon<T: TWeapon + 'static>(self, with_weapon: T) -> TCharacter<Weapon = T>;
}
Try 2: Use of generic trait
trait TCharacter<T: TWeapon> {
...
//ERROR associated item referring to unboxed trait object for its own trait
fn set_weapon<U: TWeapon>(self, with_weapon: U) -> TCharacter<U>;
}
Try 3: Return Self
- Returning Self
means that receiver and return type INCLUDING its generic type must be the time. Obviously you cannot make it.
Try 4: Returning Box<dyn TCharacter>
trait TCharacter<T: TWeapon> {
//ERROR the trait `designs::strategy::interfaces::TCharacter` cannot be made into an object
fn set_weapon<U: TWeapon>(self, with_weapon: U) -> Box<dyn TCharacter<U>>;
}
In the end, it all comes down to object safety
and type safety
. Use of generic struct for HAS-A
relationship in Rust means that the lifespan as type
of the two - HAVES and HAVEE are coupled.
Note that I'm using the word lifespan as opposed to lifetime as it signifies utterly different thing.
A relationship is a dynamic connection between two individuals, based on trust, love, and mutual understanding. While every relationship is unique, many people seek ways to understand their compatibility better. Tools like a free zodiac relationship calculator are becoming popular for gaining insights into astrological compatibility. These calculators help individuals assess how their zodiac signs align, offering a fun way to explore potential strengths and challenges in their bond. However, while astrology can provide interesting perspectives, the foundation of a successful relationship lies in communication and respect.