How can I downcast from Box<Any> to a trait object type?
pub struct WidgetWrap {
// ...
widget: RefCell<Box<Any>>,
}
At some point I want to cast Box<Any>
to Box<WidgetTrait>
let mut cell = widget.borrow_mut();
let w = cell.downcast_mut::<Box<WidgetTrait>>();
This gives me an error of this kind:
error: instantiating a type parameter with an incompatible type
`Box<WidgetTrait>`, which does not fulfill `'static` [E0144]
What does this really mean?
I've looked at How to fix: value may contain references; add `'static` bound to `T` and did try adding + 'static
everywhere.
pub struct WidgetWrap {
// ...
widget: RefCell<Box<Any + 'static>>,
}
let mut cell = widget.borrow_mut();
let w = cell.downcast_mut::<Box<WidgetTrait + 'static>>();
It fixes the compile errors, but fails when I try to unwrap the downcasted box as shown above. And yes, the content of the box is an object that implements WidgetTrait
.
Obviously, I am coding in Rust at a level that I don't quite understand, but maybe someone can help me get a better grip on the concepts involved in the above task.
(I shall ignore the 'static
part as it’s comparatively irrelevant for the parts I’m explaining.)
Box<Trait>
for a given trait Trait
is stored as two pieces of data: a pointer to the actual data in memory and a pointer to the vtable for its type’s implementation of Trait
.
From that, you may see that you can only have one level of traityness—if you have a Box<WidgetTrait>
and you box it again as Box<Any>
, you would only be able to get it out as a Box<WidgetTrait>
object. Similarly, if you take a type Widget
that implements WidgetTrait
and box it in a Box<Any>
, you can only get it out as a Widget
object, not as a Box<WidgetTrait>
object.
Such is the nature of the type IDs being used internally: unlike in a dynamic or VM-based language, the type system is purely a compile-time construct; there is no such thing as the type system at runtime.
The solution, if you really need a solution along these lines (you probably don’t; sticking with just a Box<WidgetTrait>
is probably the best way) is to have a trait which also implements what Any
does. This is not the simplest thing at present, but can be done. Teepee’s Header
trait is an example of how this can work; a Box<Header>
object will have the header-transforming methods as well as Any
’s .downcast_ref()
and so forth.
The original code works as-is in at least Rust 1.25; presumably there was a bug or limitation in the compiler that has since been fixed:
use std::{any::Any, cell::RefCell};
trait WidgetTrait {}
fn example(widget: RefCell<Box<dyn Any>>) {
let mut cell = widget.borrow_mut();
let _w = cell.downcast_mut::<Box<dyn WidgetTrait>>();
}
As pointed out by trentcl, that doesn't mean that this does what you want:
This compiles, I admit, but it cannot succeed when the content of the box is an object that implements
WidgetTrait,
because it downcasts specifically toBox<WidgetTrait (+ 'static)>
. You'd have to create aBox<Box<WidgetTrait + 'static>>
for it to work. (Put another way, the type parameter ofdowncast_mut
is the unboxed typeT
, notBox<T>
.)
use std::{any::Any, cell::RefCell};
trait WidgetTrait {
fn demo(&mut self);
}
fn example(widget: RefCell<Box<dyn Any>>) {
let mut cell = widget.borrow_mut();
match cell.downcast_mut::<Box<dyn WidgetTrait>>() {
Some(w) => w.demo(),
None => println!("Not here!"),
}
}
struct Alpha(u8);
impl WidgetTrait for Alpha {
fn demo(&mut self) {
self.0 += 1;
dbg!(self.0);
}
}
fn main() {
let b: Box<dyn Any> = Box::new(Alpha(0));
let r = RefCell::new(b);
example(r);
}
Not here!
Boxing it twice:
fn main() {
let b: Box<dyn WidgetTrait> = Box::new(Alpha(0));
let b2: Box<dyn Any> = Box::new(b);
let r = RefCell::new(b2);
example(r);
}
[src/main.rs:19] self.0 = 1
See also:
- How to get a reference to a concrete type from a trait object?