Petr Jindra
Petr Jindra

Reputation: 49

Rust - Casting super trait to trait

I have a container where I can store items implementing the CommonBase trait and I have some other traits (TypeA and TypeB for example, but there can be unknown number of other TypeX traits) with the CommonBase as their super trait. I store objects implementing ItemA to container and I can get a reference to item back from the container, but as reference to the CommonBase.

I want to cast the item back to its original type like this:

fn cast_to<T: SuperTrait>(val: dyn CommonBase) -> Result<T, dyn Error> {...}

Can anyone help please?

Here is my experimental code

use std::error::Error;
use std::rc::Rc;

pub trait CommonBase {
    fn to_common(self: &Self);
}

pub trait TypeA: CommonBase {
    fn do_a(self: &Self);
}

pub trait TypeB: CommonBase {
    fn do_b(self: &Self);
}

pub struct StructA {}

impl CommonBase for StructA {
    fn to_common(self: &Self) { todo!() }
}

impl TypeA for StructA {
    fn do_a(self: &Self) { todo!() }
}


pub struct StructB {}

impl CommonBase for StructB {
    fn to_common(self: &Self) { todo!() }
}

impl TypeB for StructB {
    fn do_b(self: &Self) { todo!() }
}

pub struct Container {
    items: Vec<Rc<dyn CommonBase>>,
}


impl Container {
    pub fn new() -> Container {
        Container { items: Vec::new() }
    }

    pub fn add(self: &mut Self, item: Rc<dyn CommonBase>) {
        self.items.push(item);
    }

    pub fn get(self, idx: usize) -> Rc<dyn CommonBase> {
        self.items.get(idx).unwrap().clone()
    }
}

fn cast_to<T: CommonBase>(val: Rc<dyn CommonBase>) -> Result<Rc<dyn T>, Box<dyn Error>> {...}  // <- some magic is done here

fn main() {
    let mut container = Container::new();
    let item_a = Rc::new(StructA {});
    let item_b = Rc::new(StructB {});
    container.add(item_a);  // index 0
    container.add(item_b);  // index 1

    let stored_a_as_common: Rc<dyn CommonBase> = container.get_common(0);  // actually TypeA
    let stored_a: Rc<dyn TypeA> = cast_to(stored_a_as_common).unwrap();
    stored_a.do_a();
}

Upvotes: 0

Views: 800

Answers (1)

prog-fh
prog-fh

Reputation: 16850

Here is your code with a few additions.

For this, you need std::any::Any. Each struct subject to dynamic cast should be known as dyn Any in order to try the dynamic cast; this is the purpose of .as_any() and .as_any_mut() in the example. Using .downcast_ref() or .downcast_mut() requires you target a type, not a trait.

I don't think you can have at the same time two Rcs pointing towards the same struct but declared with different dyn traits. The best to do, in my opinion, is to only deal with references (wherever they come from, Rc or something else).

You expected an Error when the dynamic cast is impossible, then I wrote the function this way. Note however that .downcast_ref() returns an Option and this is generally good enough (None means that the cast is impossible); we could simplify the code with just this line val.as_any().downcast_ref::<T>().

use std::error::Error;
use std::rc::Rc;

use std::any::Any;

pub trait CommonBase: Any {
    fn to_common(self: &Self);
    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

pub trait TypeA: CommonBase {
    fn do_a(self: &Self);
}

pub trait TypeB: CommonBase {
    fn do_b(self: &Self);
}

pub struct StructA {}

impl CommonBase for StructA {
    fn to_common(self: &Self) {
        todo!()
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

impl TypeA for StructA {
    fn do_a(self: &Self) {
        println!("doing a");
    }
}

pub struct StructB {}

impl CommonBase for StructB {
    fn to_common(self: &Self) {
        todo!()
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

impl TypeB for StructB {
    fn do_b(self: &Self) {
        println!("doing b");
    }
}

pub struct Container {
    items: Vec<Rc<dyn CommonBase>>,
}

impl Container {
    pub fn new() -> Container {
        Container { items: Vec::new() }
    }

    pub fn add(
        self: &mut Self,
        item: Rc<dyn CommonBase>,
    ) {
        self.items.push(item);
    }

    pub fn get(
        &self,
        idx: usize,
    ) -> Rc<dyn CommonBase> {
        self.items.get(idx).unwrap().clone()
    }
}

fn cast_to<T: CommonBase>(
    val: &dyn CommonBase
) -> Result<&T, Box<dyn Error>> {
    if let Some(t_ref) = val.as_any().downcast_ref::<T>() {
        Ok(t_ref)
    } else {
        Err("bad cast")?
    }
}

#[allow(dead_code)] // unused in this example
fn cast_to_mut<T: CommonBase>(
    val: &mut dyn CommonBase
) -> Result<&mut T, Box<dyn Error>> {
    if let Some(t_ref) = val.as_any_mut().downcast_mut::<T>() {
        Ok(t_ref)
    } else {
        Err("bad cast")?
    }
}

fn main() {
    let mut container = Container::new();
    let item_a = Rc::new(StructA {});
    let item_b = Rc::new(StructB {});
    container.add(item_a); // index 0
    container.add(item_b); // index 1

    let stored_a_as_common: Rc<dyn CommonBase> = container.get(0); // actually TypeA
    let stored_a: &dyn TypeA =
        cast_to::<StructA>(stored_a_as_common.as_ref()).unwrap();
    stored_a.do_a();

    let stored_b_as_common: Rc<dyn CommonBase> = container.get(1); // actually TypeB
    let stored_b: &dyn TypeB =
        cast_to::<StructB>(stored_b_as_common.as_ref()).unwrap();
    stored_b.do_b();
}
/*
doing a
doing b
*/

Upvotes: 1

Related Questions