Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Referring to Traits of generic objects seems impossible

Tags:

rust

Consider the following simple structs:

struct Monster {
    // ...
}

struct Player {
    // ...
}

struct Missile {
    // ...
    //target: ???,
}

When writing game logic, it is very common to have objects refer to each other. In the example above, we have the structs Monster, Player and Missile to illustrate the kinds of interactions needed.

Imagine we have the following traits: Position and Think. All the above structs implements Position, and all except Missile implements Think.

The first thing to note is that Missile is a homing missile: It stores a target and whenever the game updates Missile objects, it will move it toward it's target.

As far as I can tell, it is impossible to sensibly store the target of this Missile in Rust.

Obviously, the missile doesn't own its target. It just wants to access its Position trait. The game objects must be shared, through Rc or Gc. But it's not like the Missile can just store a Weak<???> reference to something with a Position trait. A Box<Position> means consuming whatever object had that trait. A Box<Any> does not allow downcasting to traits.

Making Missile<T> and storing the target as Weak<T> doesn't help. How would those Missile<T> be stored? In one Collection<T> for each kind of object the missile targets? Game objects will need to be more generic, and the dreadful Box<Any> seems inevitable.

I'm rather new to Rust. It baffles my mind that this is straight out impossible. Surely, I must be missing something?

like image 204
porgarmingduod Avatar asked Sep 30 '22 02:09

porgarmingduod


1 Answers

I'm also pretty new to Rust, but here's what I've found. You can use the std::rc::Rc<T> struct in combination with Box. To have a mutable shared references, you need to wrap boxed item in a std::cell::RefCell.

So, your player, monster, missile example would go something like this:

use std::cell::RefCell;
use std::rc::Rc;

trait Position {
    fn position(&mut self);
}

struct Monster;

impl Position for Monster {
    fn position(&mut self) {
        println!("Rawr I am getting the monster's position");
    }
}

struct Player {
    x: i32,
}

impl Position for Player {
    fn position(&mut self) {
        println!("Getting player's position {}", self.x);
        self.x += 1;
    }
}

struct Missile {
    target: Rc<RefCell<Box<Position>>>,
}

fn main() {
    // Create some stuff
    let player = Rc::new(RefCell::new(Box::new(Player{x: 42}) as Box<Position>));
    let monster = Rc::new(RefCell::new(Box::new(Monster) as Box<Position>));

    // Our missile: initial target - monster
    let mut missile = Missile{target: monster};

    // Should be a monster
    missile.target.borrow_mut().position();

    // Redirect missile to player
    missile.target = player.clone();

    // Should be a player
    missile.target.borrow_mut().position();

    // Show that it is in fact a reference to the original player
    player.borrow_mut().position();
}

However, it is usually possible to design entity systems that don't require shared references, and this is considered to be more idiomatic Rust. If your entity system is really complex, I'd recommend using an Entity Component System.

EDIT: Improved code and information, and removed some inaccurate information.

like image 183
Teddy DeRego Avatar answered Oct 16 '22 13:10

Teddy DeRego