25
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");}
}
Maik Klein
  • 15,548
  • 27
  • 101
  • 197
  • There is currently a set of RFCs toward getting downcasting (in one way or another) in Rust. It will be possible one day, but not for now. – Matthieu M. May 16 '15 at 13:30

3 Answers3

16

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<&dyn Health>;
}

trait Health { }

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

impl Health for Monster { }

impl Actor for Plant {
    fn health(&self) -> Option<&dyn 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<dyn $trait_> for T {
            fn as_trait_ref(&self) -> Option<&dyn $trait_> {
                Some(self)
            }
        }

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

impl_maybe_implements!(Health);

trait Actor: MaybeImplements<dyn Health> {
}

let health: Option<&dyn 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<dyn $trait_> for $ty {
            fn as_trait_ref(&self) -> Option<dyn $trait_> {
                Some(self)
            }
        }
    }

    (!$trait_:ident for $ty:ty) => {
        impl MaybeImplements<dyn $trait_> for $ty {
            fn as_trait_ref(&self) -> Option<dyn $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.)

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • This doesn't compile (probably outdated) – Anatoly Bugakov Feb 17 '23 at 20:17
  • 1
    Updated. When I wrote this answer, trait object types were spelled `Trait`; since then, it’s moved to `dyn Trait`. The old syntax still works if your crate uses the 2015 edition, but the new syntax must be used if your crate uses the 2021 edition, which you probably are now. – Chris Morgan Feb 20 '23 at 03:16
  • I'm not sure Rust will ever get negative bounds, but it'll probably get specialization at some point. – Chayim Friedman May 19 '23 at 09:59
10

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.

DK.
  • 55,277
  • 5
  • 189
  • 162
0

It is impossible to check if an object implements a specific trait at runtime.
If you know the concrete type implementing a trait that needs to be tested for, we can use downcasting to get that concrete type through runtime reflection (since 2018).

trait Actor {
    fn actor(&self);
    fn as_any(&self) -> &dyn Any;
}

trait Health {
    fn health(&self);
    fn as_any(&self) -> &dyn Any;
}

struct Plant;

impl Actor for Plant {
    fn actor(&self) {
        println!("Plant Actor");
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}

struct Monster {
    health: f32,
}

impl Actor for Monster {
    fn actor(&self) {
        println!("Monster Actor");
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}

impl Health for Monster {
    fn health(&self) {
        println!("Health: {}", self.health);
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn main() {
    let plant = Box::new(Plant);
    let monster = Box::new(Monster { health: 100f32 });

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

    for a in &actors {
        if let Some(Monster { health, .. }) = (&**a).as_any().downcast_ref::<Monster>() {
            println!("is a monster with health {health}");
        }
        else {
            println!("is a plant");
        }
    }
}

Playground

Kaplan
  • 2,572
  • 13
  • 14