Search code examples
rustcastingimmutabilitytraits

Cast non-primitive type in Rust


I am very new to Rust, so apologies if this is a simple question!

I have the following simplified scenario, where I have a trait and two struct types (TypeA and TypeB) that implement the trait. I then have a vector of traits that contains instances of both TypeA and TypeB.

Later on, I would like to iterate the vector and access fields that belong only to TypeA or TypeB depending on what the type is. Below is what I have written. My attempt is to match the type of each item and then downcast the boxed trait iterator to the struct. This works with a & reference, but how can I achieve a mutable reference &mut.

For the & reference consider the following ...

use std::any::Any;

trait MyTrait {
    fn do_something(&self);
    fn as_any(&self) -> &dyn Any;
}

struct TypeA{field_a: u16}
impl MyTrait for TypeA {
    fn do_something(&self) {
        println!("TypeA is doing something.");
    }
    fn as_any(&self) -> &dyn Any {
        self
   }
}

struct TypeB{field_b: u16}
impl MyTrait for TypeB {
    fn do_something(&self) {
        println!("TypeB is doing something.");
    }
    fn as_any(&self) -> &dyn Any {
        self
   }
}

fn main() {
    let vec: Vec<Box<dyn MyTrait>> = vec![
        Box::new(TypeA{field_a:10}),
        Box::new(TypeB{field_b:20}),
    ];

    for item in &vec {
        // Downcast the trait object to its concrete type

        if let Some(type_a) = item.as_any().downcast_ref::<TypeA>() {
            type_a.do_something();
            print!("{}", type_a.field_a);
        } else if let Some(type_b) = item.as_any().downcast_ref::<TypeB>() {
            type_b.do_something();
            print!("{}", type_b.field_b);
        } else {
            println!("Unknown type");
        }
    }
}

However, I want to modify the fields within the downcasted instance, such as

type_a.field_a = 100;

Solution

  • You can do this if you add a way to get a &mut dyn Any on your trait:

    fn as_any_mut(&mut self) -> &mut dyn Any;
    

    The implementation is the same as as_any:

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
    

    You will also need to make vec mutable, and iterate over &mut vec instead of &vec, and then you will be able to do this, for example:

    item.as_any_mut().downcast_mut::<TypeA>()
    

    This gives you an Option<&mut TypeA>.


    However, I would strongly suggest you consider using an enum instead of boxed trait objects. If you have a closed set of variants you need to support, this will be considerably more ergonomic.