Downcasting Box<dyn Any> to arbitrary types

I have a couple of functions that all return a different type and I decided to go with a Box as the return type.

I have sort of coded myself into a corner in the sense that there are several other functions that need to know the concrete type of the values returned in the Box.

I resulted to using the code below, initially I thought I could just convert the inner type into any type (dumb me) but I realised that it doesn't work like that. The downcasting only works if the inner value is of the type you are downcasting to, else you get a None.

(&*box_dyn_any_type).downcast_ref::<f64>().unwrap().clone();

My question are there methods to do the downcasting in the way that I want or I should totally change the return type of the impl and use let's say an enum that has variants that cover all the types, the my functions return

It's not clear to me what exactly you want. What's supposed to happen when you've type-erased a String and then somehow pull out a Vec<f32>, or whatever?

If just you mean, "I now have long if-else chains trying to downcast to every known implementor", using an enum instead is probably the answer. Perhaps with a crate to help reduce dispatching boilerplate.

Or sometimes you can just macro up some implementation for every type, depending on the use case.

1 Like

What is the way you want?

It's unclear what you're trying to do. But "downcasting" to a type other than what the type of the actual value is wouldn't be downcasting, which refers to going down in a type hierarchy. Casting say an i32 to an f32 is just a different sort of thing which is also called casting.

If you have functions which you want to call and get an f64 from calling them, then possibly you should make those functions return f64. It's impossible to say without more understanding about what your goals are.

1 Like

jezz! you kidding right? I literally just clicked away from dyn Trait implementations - Learning Rust to this page to see your reply, had to go back and check the name :grinning:

Sincere apologies for the vagueness of the question and thank you all for making time to reply.
Here is an attempt to make it more clearer

So when I say "I have a couple of functions that all return a different type and I decided to go with a Box as the return type",
I mean the functions are part of a trait implementation
The impl is actually an attempt to use the Visitor pattern.
The functions all return different types but bc of generic trait, they all need to use the same return type.

impl my_generic_trait<Boy<dyn Any> for TypeWithLowerHierarchies {
    fn return_type_a() -> Box<dyn Any> {};
    fn return_type_b() -> Box<dyn Any> {};
    fn return_type_c() -> Box<dyn Any> {};
    fn return_type_d() -> Box<dyn Any> {};
}

In another part, I am taking a Box that is returned from any of the functions above and converting it from Box into type_(a|b|c| other_types)() so I can create methods that work on those concrete types.

An example of such a scenario is me taking a Box and trying converting into an f64, I have places that I need to convert to a String etc

I have seen other implementations like using an enum

enum Boxed {
   type_a(String),
   type_b(f64),
   type_b(bool)
}

so instead of returning a Box they return the Boxed and pattern match of which variant was returned and create the methods and know they are dealing with the right type.

Still somewhat unclear but possibly either you should be using an enum as suggested, or some boxed trait other than Any

1 Like

In this situation my general advice is:
Go with the enum

Even for experts, the dyn Any or dyn Trait approach is much harder, and rarely has benefits

4 Likes

Nah, citation needed.

First of all, don't speak for "the experts". "Much harder" is subjective – maybe it is for you, but anyone who used dyn Trait a couple of times will have discovered its benefits and limitations. It's not rocket science.

Furthermore, enums and dynamic dispatch serve different purposes. While it is possible that OP's concrete use case is better served by enums, in general it's simply not possible to substitute dynamic dispatch with enums.

When you need to coerce from arbitrary types to a single type, and the set of source types is not fixed at compile time, you simply can't use an enum. Good luck making an HTTP router with enum instead of dyn Fn.

1 Like

Well, if the router is macro-based (i.e. generated inside the consumer crate) and not function-based...

2 Likes

I agree.
If you need to coerce from arbitrary types, then enum just cannot work.
But, often, you can move the consumer crate "down", and make it importing all needed types.
Then the enum approach works again.

Please also note that this is URLO and the OP was not able to clearly state their problem. I'm trying to get them over the hurdle. I strongly believe that in this situation "take the enum approach" really is a helpful answer, even without understanding the problem at hand. Based on personal experience :wink:

I don't know if this helps but some place I found a description of how to get the underlying type out of a Box<dyn AsAny> using .downcast_ref.
It requires implementing a trait for each of the various types in use that converts them to &dyn Any.

I started playing with that idea and ended with this example that shows use of passing Box<dyn AsAny> as a parameter, returning it from a function and passing it through channels.

// Experiments in using downcasting Any into concrete type.
//
use std::any::Any;
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};

// The indirection through `as_any` is because using `downcast_ref`
// on `Box<A>` *directly* only lets us downcast back to `&A` again.
// The method ensures we get an `Any` vtable that lets us downcast
// back to the original, concrete type.
trait AsAny {
    fn as_any(&self) -> &dyn Any;
}

#[derive(Debug, Clone, Copy)]
struct Cat;

impl AsAny for Cat {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

#[derive(Debug, Clone, Copy)]
struct Dog;

impl AsAny for Dog {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn eat_any(pet: Box<dyn AsAny>) {
    let pet: &dyn Any = pet.as_any();
    if let Some(_cat) = pet.downcast_ref::<Cat>() {
        println!("Eating cat.");
    }
    if let Some(_ddog) = pet.downcast_ref::<Dog>() {
        // Do something with dog
        println!("Eating dog.");
    }
}

fn create_any() -> Box<dyn AsAny> {
    let message: Box<dyn AsAny + Send> = Box::new(Dog);
    message
}

#[tokio::main]
async fn main() {
    let cat: Box<dyn AsAny + Send> = Box::new(Cat);
    eat_any(cat);

    let dog: Box<dyn AsAny + Send> = Box::new(Dog);
    eat_any(dog);

    let (tx1, mut rx) = mpsc::channel(10);
    let tx2 = tx1.clone();

    let j1 = tokio::spawn(async move {
        loop {
            let message: Box<dyn AsAny + Send> = Box::new(Cat);
            tx1.send(message).await.unwrap();
            sleep(Duration::from_millis(1000)).await;
        }
    });

    let j2 = tokio::spawn(async move {
        sleep(Duration::from_millis(500)).await;
        loop {
            let message: Box<dyn AsAny + Send> = Box::new(Dog);
            tx2.send(message).await.unwrap();
            sleep(Duration::from_millis(1000)).await;
        }
    });

    let j3 = tokio::spawn(async move {
        loop {
            if let Some(message) = rx.recv().await {
                let message: &dyn Any = message.as_any();
                if let Some(_c) = message.downcast_ref::<Cat>() {
                    println!("Meow");
                }

                if let Some(_d) = message.downcast_ref::<Dog>() {
                    // Do something with dog
                    println!("Woof");
                }
            }
        }
    });
    let (_, _, _) = tokio::join!(j1, j2, j3);
}

Better write a blanket impl.

Bear with me. I have only been using Rust for four years so I still don't know how to do that. I only found out what a "blanket implementation" was yesterday. When I replace my 'AsAny' trait implementations for Catand Dog with:

impl<T: 'static> AsAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

My code compiles fine but fails to run properly:

// Experiments in using downcasting Any into concrete type.
//
use std::any::Any;
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};

// The indirection through `as_any` is because using `downcast_ref`
// on `Box<A>` *directly* only lets us downcast back to `&A` again.
// The method ensures we get an `Any` vtable that lets us downcast
// back to the original, concrete type.
trait AsAny {
    fn as_any(&self) -> &dyn Any;
}

#[derive(Debug, Clone, Copy)]
struct Cat;

#[derive(Debug, Clone, Copy)]
struct Dog;

impl<T: 'static> AsAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn eat_any(pet: Box<dyn AsAny>) {
    let pet: &dyn Any = pet.as_any();
    if let Some(_cat) = pet.downcast_ref::<Cat>() {
        println!("Eating cat.");
    } else if let Some(_dog) = pet.downcast_ref::<Dog>() {
        // Do something with dog
        println!("Eating dog.");
    } else {
        println!("Cannot eat {:?}.", pet);
    }
}

fn create_any() -> Box<dyn AsAny> {
    let message: Box<dyn AsAny + Send> = Box::new(Dog);
    message
}

#[tokio::main]
async fn main() {
    let cat: Box<dyn AsAny + Send> = Box::new(Cat);
    eat_any(cat);

    let dog: Box<dyn AsAny + Send> = Box::new(Dog);
    eat_any(dog);

    let (tx1, mut rx) = mpsc::channel(10);
    let tx2 = tx1.clone();

    let j1 = tokio::spawn(async move {
        loop {
            let message: Box<dyn AsAny + Send> = Box::new(Cat);
            tx1.send(message).await.unwrap();
            sleep(Duration::from_millis(1000)).await;
        }
    });

    let j2 = tokio::spawn(async move {
        sleep(Duration::from_millis(500)).await;
        loop {
            let message: Box<dyn AsAny + Send> = Box::new(Dog);
            tx2.send(message).await.unwrap();
            sleep(Duration::from_millis(1000)).await;
        }
    });

    let j3 = tokio::spawn(async move {
        loop {
            if let Some(message) = rx.recv().await {
                let message: &dyn Any = message.as_any();
                if let Some(_c) = message.downcast_ref::<Cat>() {
                    println!("Meow");
                }

                if let Some(_d) = message.downcast_ref::<Dog>() {
                    // Do something with dog
                    println!("Woof");
                }
            }
        }
    });
    let (_, _, _) = tokio::join!(j1, j2, j3);
}

Meh.

fn eat_any(pet: Box<dyn AsAny>) {
    let pet: &dyn Any = pet.as_ref().as_any();

You are downcasting the Box, not its pointee. Works with .as_ref().

(For this reason and more, taking an owned Box<dyn Trait> when a bare &dyn Trait would have sufficed is not a stellar idea.)

That's great. Thanks. Only now I have no idea how it works.

You mean like this:

// Experiments in using downcasting Any into concrete type.
//
use std::any::Any;
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};

// The indirection through `as_any` is because using `downcast_ref`
// on `Box<A>` *directly* only lets us downcast back to `&A` again.
// The method ensures we get an `Any` vtable that lets us downcast
// back to the original, concrete type.
trait AsAny {
    fn as_any(&self) -> &dyn Any;
}

#[derive(Debug, Clone, Copy)]
struct Cat;

#[derive(Debug, Clone, Copy)]
struct Dog;

impl<T: 'static> AsAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn eat_any(pet: &dyn AsAny) {
    let pet: &dyn Any = pet.as_any();
    if let Some(_cat) = pet.downcast_ref::<Cat>() {
        println!("Eating cat.");
    } else if let Some(_dog) = pet.downcast_ref::<Dog>() {
        // Do something with dog
        println!("Eating dog.");
    } else {
        println!("Cannot eat {:?}.", pet);
    }
}

fn create_any() -> Box<dyn AsAny> {
    let message: Box<dyn AsAny + Send> = Box::new(Dog);
    message
}

#[tokio::main]
async fn main() {
    let cat = Cat;
    eat_any(&cat);

    let dog = Dog;
    eat_any(&dog);

    let (tx1, mut rx) = mpsc::channel(10);
    let tx2 = tx1.clone();

    let j1 = tokio::spawn(async move {
        loop {
            let message: Box<dyn AsAny + Send> = Box::new(Cat);
            tx1.send(message).await.unwrap();
            sleep(Duration::from_millis(1000)).await;
        }
    });

    let j2 = tokio::spawn(async move {
        sleep(Duration::from_millis(500)).await;
        loop {
            let message: Box<dyn AsAny + Send> = Box::new(Dog);
            tx2.send(message).await.unwrap();
            sleep(Duration::from_millis(1000)).await;
        }
    });

    let j3 = tokio::spawn(async move {
        loop {
            if let Some(message) = rx.recv().await {
                let message: &dyn Any = message.as_ref().as_any();
                if let Some(_cat) = message.downcast_ref::<Cat>() {
                    println!("Meow");
                } else if let Some(_dog) = message.downcast_ref::<Dog>() {
                    println!("Woof");
                } else {
                    println!("Can't handle: {:?}", message);
                }
            }
        }
    });
    let (_, _, _) = tokio::join!(j1, j2, j3);
}

.as_ref() returns a reference to the pointee of the Box.

Thanks. I was just playing with .as_ref. Like so:

    // A Cat in a Box
    let cat = Box::new(Cat);
    eat_any(cat.as_ref());

    // A Dog on the stack
    let dog = Dog;
    eat_any(&dog);

If Cat & Dog have some fields like:

#[derive(Debug, Clone, Copy)]
pub struct Cat { pub years: u8}

How to do this?

if let Some(mut cat) = pet.downcast_ref::<Cat>() {
    cat.years = 8;
    ^^^^^^^^^^^^^ `cat` is a `&` reference, so the data it refers to cannot be written

Thanks!

downcast_mut (read the docs?)