Last active
August 29, 2015 13:56
-
-
Save bvssvni/9327294 to your computer and use it in GitHub Desktop.
Emulating dynamic types: How to implement Any for owned Trait pointers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! | |
//! >rustc --version | |
//! rustc 0.10-pre (ee8f45e 2014-02-18 13:41:49 -0800) | |
//! | |
//! In this example we will implement Any for a trait pointer. | |
//! This is useful when we want to design an Entity/Component system | |
//! that is extensible beyond the library. | |
//! | |
//! As example we have two objects 'Player' and 'Enemy'. | |
//! Both implement the trait 'Entity' which is used 90% of the time. | |
//! In a few cases we want to access type specific methods. | |
//! The Any trait can be used to cast dynamically from | |
//! Entity to Player or Enemey with the method 'as_ref'. | |
//! | |
//! This example is somewhat complicated because | |
//! the Any trait is not implemented on trait pointers. | |
//! We can't cast ~Entity to Any. | |
//! We need to make Entity inherit from Any and implement some extra traits. | |
//! This requires some copy and pasting from the standard library. | |
//! | |
// Some imports to do unsafe operations. | |
// This is used by the special traits implemented on Any. | |
use std::cast::transmute; | |
use std::unstable::intrinsics; | |
use std::unstable::intrinsics::TypeId; | |
/// Specifies how to control a player. | |
#[deriving(Show)] | |
pub enum UserInput { | |
Mouse, | |
Keyboard, | |
GamePad, | |
} | |
/// Players are controlled by user input. | |
pub struct Player { | |
name: ~str, | |
input: UserInput, | |
} | |
/// Enemies are not controlled by user input. | |
pub struct Enemy { | |
name: ~str | |
} | |
/// Entity inherits from the Any trait. | |
/// Any is implemented by all types, but not for trait pointers by default. | |
pub trait Entity : Any { | |
/// Get the name of the entity. | |
fn get_name<'a>(&'a self) -> &'a str; | |
} | |
/// This code is copied from standard library. | |
impl AnyOwnExt for ~Entity { | |
#[inline] | |
fn move<T: 'static>(self) -> Result<~T, ~Entity> { | |
if self.is::<T>() { | |
unsafe { | |
// Extract the pointer to the boxed value, temporary alias with self | |
let ptr: ~T = transmute(self.as_void_ptr()); | |
// Prevent destructor on self being run | |
intrinsics::forget(self); | |
Ok(ptr) | |
} | |
} else { | |
Err(self) | |
} | |
} | |
} | |
/// This code is copied from standard library. | |
impl<'a> AnyRefExt<'a> for &'a Entity { | |
#[inline] | |
fn is<T: 'static>(self) -> bool { | |
// Get TypeId of the type this function is instantiated with | |
let t = TypeId::of::<T>(); | |
// Get TypeId of the type in the trait object | |
let boxed = self.get_type_id(); | |
// Compare both TypeIds on equality | |
t == boxed | |
} | |
#[inline] | |
fn as_ref<T: 'static>(self) -> Option<&'a T> { | |
if self.is::<T>() { | |
Some(unsafe { transmute(self.as_void_ptr()) }) | |
} else { | |
None | |
} | |
} | |
} | |
/// This code is copied from standard library. | |
impl<'a> AnyMutRefExt<'a> for &'a mut Entity { | |
#[inline] | |
fn as_mut<T: 'static>(self) -> Option<&'a mut T> { | |
if self.is::<T>() { | |
Some(unsafe { transmute(self.as_mut_void_ptr()) }) | |
} else { | |
None | |
} | |
} | |
} | |
impl Entity for Player { | |
fn get_name<'a>(&'a self) -> &'a str { | |
self.name.as_slice() | |
} | |
} | |
impl Entity for Enemy { | |
fn get_name<'a>(&'a self) -> &'a str { | |
self.name.as_slice() | |
} | |
} | |
pub enum EntityType { | |
PlayerType(UserInput), | |
EnemyType, | |
} | |
/// Creates new entities. | |
fn create_entity(name: ~str, entity_type: EntityType) -> ~Entity { | |
match entity_type { | |
PlayerType(input) => | |
~Player { | |
name: name, input: input | |
} as ~Entity, | |
EnemyType => ~Enemy { name: name } as ~Entity, | |
} | |
} | |
fn main() { | |
// We build a list of entities. | |
let mut entities: ~[~Entity] = ~[]; | |
entities.push(create_entity(~"Player1", PlayerType(Keyboard))); | |
entities.push(create_entity(~"Player2", PlayerType(GamePad))); | |
entities.push(create_entity(~"Boss", EnemyType)); | |
// Cast one of the entities back to Player. | |
match entities[1].as_ref::<Player>() { | |
// Prints out 'GamePad'. | |
Some(player) => println!("{}", player.input), | |
None => {} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment