Can't figure out a function to return a refere

2020-04-17 05:18发布

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)

标签: rust
1条回答
forever°为你锁心
2楼-- · 2020-04-17 05:43

I'll just present a working simplified and more idiomatic version of your code and then explain all the changed I made there:

use std::rc::{Rc, Weak};
use std::any::{Any, AnyRefExt};

struct Shared {
    example: int,
}

struct Widget {
    parent: Option<Weak<Widget>>,
    specific: Box<Any>,
    shared: Shared,
}

impl Widget {
    fn new(specific: Box<Any>, parent: Option<Rc<Widget>>) -> Widget { 
        let parent_option = match parent {
            Some(parent) => Some(parent.downgrade()),
            None => None,
        };
        let shared = Shared { example: 10 };
        Widget{
            parent: parent_option,
            specific: specific,
            shared: shared
        }
    }

    fn get_specific<T: 'static>(&self) -> Option<&T> {
        self.specific.downcast_ref::<T>()
    }
}

struct Woo {
    foo: int,
}

impl Woo {
    fn new() -> Woo {
        Woo { foo: 10 }
    }
}

fn main() {
    let widget = Widget::new(box Woo::new() as Box<Any>, None);

    let specific = widget.get_specific::<Woo>().unwrap();
    println!("{}", specific.foo);
}

First of all, there are needless RefCells inside your structure. RefCells 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>> when Something is a struct (like in your case where Something = Widget) is redundant. Putting an owned box into a reference-counted box is just an unnecessary double indirection and allocation. Plain Rc<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 even Rc<Widget>) is unnecessary limiting for the callers of your code. You can go from Widget to Rc<Widget> easily, but not the other way around. Rust optimizes by-value returns automatically; if your callers need Rc<Widget> they can box the return value themselves:

let rc_w = box(RC) Widget::new(...);

Same thing is also true for Box<Any> returned by Woo::new().

You can see that in the absence of RefCells your get_specific() method can be implemented very easily. However, you really can't do it with RefCell because RefCell uses RAII for dynamic borrow checks, so you can't return a reference to its internals from a method. You'll have to return core::cell::Refs, and your callers will need to downcast_ref() themselves. This is just another reason to use RefCells sparingly.

查看更多
登录 后发表回答