Most of this is boilerplate, provided as a compilable example. Scroll down.
use std::rc::{Rc, Weak};
use std::cell::RefCell;
use std::any::{Any, AnyRefExt};
struct Shared {
example: int,
}
struct Widget {
parent: Option<Weak<Box<Widget>>>,
specific: RefCell<Box<Any>>,
shared: RefCell<Shared>,
}
impl Widget {
fn new(specific: Box<Any>,
parent: Option<Rc<Box<Widget>>>) -> Rc<Box<Widget>> {
let parent_option = match parent {
Some(parent) => Some(parent.downgrade()),
None => None,
};
let shared = Shared{pos: 10};
Rc::new(box Widget{
parent: parent_option,
specific: RefCell::new(specific),
shared: RefCell::new(shared)})
}
}
struct Woo {
foo: int,
}
impl Woo {
fn new() -> Box<Any> {
box Woo{foo: 10} as Box<Any>
}
}
fn main() {
let widget = Widget::new(Woo::new(), None);
{
// This is a lot of work...
let cell_borrow = widget.specific.borrow();
let woo = cell_borrow.downcast_ref::<Woo>().unwrap();
println!("{}", woo.foo);
}
// Can the above be made into a function?
// let woo = widget.get_specific::<Woo>();
}
I'm learning Rust and trying to figure out some workable way of doing a widget hierarchy. The above basically does what I need, but it is a bit cumbersome. Especially vexing is the fact that I have to use two statements to convert the inner widget (specific
member of Widget
). I tried several ways of writing a function that does it all, but the amount of reference and lifetime wizardry is just beyond me.
Can it be done? Can the commented out method at the bottom of my example code be made into reality?
Comments regarding better ways of doing this entire thing are appreciated, but put it in the comments section (or create a new question and link it)
I'll just present a working simplified and more idiomatic version of your code and then explain all the changed I made there:
First of all, there are needless
RefCell
s inside your structure.RefCell
s are needed very rarely - only when you need to mutate internal state of an object using only&
reference (instead of&mut
). This is useful tool for implementing abstractions, but it is almost never needed in application code. Because it is not clear from your code that you really need it, I assume that it was used mistakingly and can be removed.Next,
Rc<Box<Something>>
whenSomething
is a struct (like in your case whereSomething
=Widget
) is redundant. Putting an owned box into a reference-counted box is just an unnecessary double indirection and allocation. PlainRc<Widget>
is the correct way to express this thing. When dynamically sized types land, it will be also true for trait objects.Finally, you should try to always return unboxed values. Returning
Rc<Box<Widget>>
(or evenRc<Widget>
) is unnecessary limiting for the callers of your code. You can go fromWidget
toRc<Widget>
easily, but not the other way around. Rust optimizes by-value returns automatically; if your callers needRc<Widget>
they can box the return value themselves:Same thing is also true for
Box<Any>
returned byWoo::new()
.You can see that in the absence of
RefCell
s yourget_specific()
method can be implemented very easily. However, you really can't do it withRefCell
becauseRefCell
uses RAII for dynamic borrow checks, so you can't return a reference to its internals from a method. You'll have to returncore::cell::Ref
s, and your callers will need todowncast_ref()
themselves. This is just another reason to useRefCell
s sparingly.