Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to check if an object implements a trait at runtime?

Tags:

rust

trait Actor{
    fn actor(&self);
}
trait Health{
    fn health(&self);
}
struct Plant;
impl Actor for Plant{
    fn actor(&self){
        println!("Plant Actor");
    }
}
struct Monster{
    health: f32
}
impl Actor for Monster{
    fn actor(&self){
        println!("Monster Actor");
    }
}
impl Health for Monster{
    fn health(&self){
        println!("Health: {}",self.health);
    }
}
fn main() {
    let plant = Box::new(Plant);
    let monster = Box::new(Monster{health: 100f32});

    let mut actors : Vec<Box<Actor>> = Vec::new();
    actors.push(plant);
    actors.push(monster);

    for a in &actors{
        a.actor();
        /* Would this be possible?
        let health = a.get_trait_object::<Health>();
        match health{
            Some(h) => {h.health();},
            None => {println!("Has no Health trait");}
        }
        */
    }
}

I am wondering if something like this could be possible?

let health = a.get_trait_object::<Health>();
match health{
    Some(h) => {h.health();},
    None => {println!("Has no Health trait");}
}
like image 681
Maik Klein Avatar asked May 16 '15 09:05

Maik Klein


People also ask

How do you check if a type implements a trait in Rust?

This is not currently possible; specialization can fix it though. However, it's a pretty weird requirement – one doesn't usually check whether or not a trait is implemented, that's not how you design interfaces.

What is a trait object?

A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.


2 Answers

It is not possible to do this in Rust at present, nor is it likely to ever become possible; it is, however, possible to construct similar abstractions as part of your trait:

trait Actor {
    fn health(&self) -> Option<&Health>;
}

trait Health { }

impl Actor for Monster {
    fn health(&self) -> Option<&Health> { Some(self) }
}

impl Health for Monster { }

impl Actor for Plant {
    fn health(&self) -> Option<&Health> { None }
}

Rust is expected to get negative bounds at some point; when that comes, you’ll be able to have something like this:

trait MaybeImplements<Trait: ?Sized> {
    fn as_trait_ref(&self) -> Option<&Trait>;
}

macro_rules! impl_maybe_implements {
    ($trait_:ident) => {
        impl<T: $trait_> MaybeImplements<$trait_> for T {
            fn as_trait_ref(&self) -> Option<&$trait_> {
                Some(self)
            }
        }

        impl<T: !$trait_> MaybeImplements<$trait_> for T {
            fn as_trait_ref(&self) -> Option<&$trait_> {
                None
            }
        }
    }
}

impl_maybe_implements!(Health);

trait Actor: MaybeImplements<Health> {
}

let health: Option<&Health> = actor.as_trait_ref();

This will reduce the boilerplate from every implementation of a trait to just one per trait, but that stage is not yet upon us. Still, you could take the middle ground of the two approaches:

trait MaybeImplements<Trait: ?Sized> {
    fn as_trait_ref(&self) -> Option<&Trait>;
}

macro_rules! register_impl {
    ($trait_:ident for $ty:ty) => {
        impl MaybeImplements<$trait_> for $ty {
            fn as_trait_ref(&self) -> Option<$trait_> {
                Some(self)
            }
        }
    }

    (!$trait_:ident for $ty:ty) => {
        impl MaybeImplements<$trait_> for $ty {
            fn as_trait_ref(&self) -> Option<$trait_> {
                None
            }
        }
    }
}

register_impl!(Health for Monster);
register_impl!(!Health for Plant);

Play around with different ways of handling it until you find something you like! The possibilities are limitless! (Because Rust is Turing‐complete.)

like image 180
Chris Morgan Avatar answered Oct 12 '22 17:10

Chris Morgan


As of 1.0, no. Rust doesn't provide any dynamic downcasting support, with the exception of Any; however, that only allows you to downcast to a value's specific concrete type, not to arbitrary traits that said concrete type implements.

I believe you could implement such casting manually, but that would require unsafe code that would be easy to get wrong; not the sort of thing I want to try and summarise in an SO answer.

like image 41
DK. Avatar answered Oct 12 '22 17:10

DK.