ccleve
ccleve

Reputation: 15809

In Rust, possible to downcast a trait to an owned type?

In Rust, I'm able to downcast a trait object to a concrete type using .as_any().downcast_ref(). That works ok, but it returns a &ref, not the owned object itself.

How can I get ownership of a downcast object?

Update: In response to the comments, here is what I was trying to do:

trait Foo {
    fn hello() -> String;
    fn as_any(&self) -> &dyn Any;
}

struct Bar {}

impl Foo for Bar {
    fn hello() -> String {
        "hello".to_owned()
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn some_func(foo: impl Foo) {
    // I want a Bar here, not a &Bar
    let bar_opt = foo.as_any().downcast_ref::<Bar>();
}

I can see why .as_any() would not work, because it returns a reference.

Upvotes: 0

Views: 610

Answers (2)

kmdreko
kmdreko

Reputation: 60493

Normally when talking about downcasting to a owned type you'd have an owned trait object like Box<dyn Trait>. Your original code allows you to get a reference to dyn Any, but to get a Box<dyn Any> from your trait object you can take Box<Self> as the receiver. Like this:

trait Foo {
    fn hello() -> String;
    fn into_any(self: Box<Self>) -> Box<dyn Any> where Self: Sized + 'static {
        self
    }
}

Then you can call .into_any() on a Box<dyn Trait>. And then J. Kadditz's answer is right that you can just .downcast() it to your desired type.


However in your some_func sample, you don't actually have a trait object; you have a generic type that implements the trait. You could use the method above to shove it into a Box use dynamic dispatch to get it as Box<dyn Any> and then downcast and dereference it back to the desired type, but that in principle should be unnecessary.

You can't go straight from owned value to owned value through Any since it only really provides for dynamicism but here's an unsafe way using TypeId (which is how Any is safe) that I think should work:

use std::any::TypeId;

trait Foo {
    fn hello() -> String;
    fn downcast<T: 'static>(self) -> Result<T, Self> where Self: Sized + 'static {
        if TypeId::of::<T>() == TypeId::of::<Self>() {
            let t = unsafe { std::ptr::read(&self as *const _ as *const T) };
            std::mem::forget(self);
            Ok(t)
        } else {
            Err(self)
        }
    }
}

Then using it in your function would look like this:

fn some_func(foo: impl Foo + 'static) {
    if let Ok(bar) = foo.downcast::<Bar>() {
        // do stuff with bar
    }
}

Upvotes: 1

J. Kadditz
J. Kadditz

Reputation: 433

This is achieved using Box and the downcast() method, which consumes the boxed trait object and returns a 'Result' containing either the downcasted object or an error if the types don't match. I would need your specific code to provide an implementation for your situation, but here's an example of its use:

use std::any::Any;

let animal: Box<dyn Any> = Box::new("Dog");
if let Ok(dog) = animal.downcast::<String>() {
    println!("It's a dog named {}", dog);
} else {
    println!("Not a dog");
}

The code tries to downcast a trait object 'animal' to a String, allowing us to use String methods if successful

Upvotes: 2

Related Questions