How does this Box<dyn FnMut> conversion to Rc<RefCell<dyn FnMut>> work?

let set_pan_position: Rc<RefCell<dyn FnMut(&Vec2f64)>> =
    Rc::new(RefCell::new(pan.set_position));

where pan.set_position has type:

pub set_position: Box<dyn FnMut(&Vec2f64), Global>

Hello! How does the above conversion work? Alternatives that DON'T work:

  • pan.set_position is unsized, so it can't be dereferenced in order to do something like this: Rc::new(RefCell:new(*pan.set_position))
  • It's not like we're using Rc::from([some boxed value]) to make use of the fn from implementation on the screenshot below:
#[cfg(not(no_global_oom_handling))]
#[stable(feature = "shared_from_slice", since = "1.21.0")]
impl<T: ?Sized, A: Allocator> From<Box<T, A>> for Rc<T, A> {
    /// Move a boxed object to a new, reference counted, allocation.
    ///
    /// # Example
    ///
    /// ```
    /// # use std::rc::Rc;
    /// let original: Box<i32> = Box::new(1);
    /// let shared: Rc<i32> = Rc::from(original);
    /// assert_eq!(1, *shared);
    /// ```
    #[inline]
    fn from(v: Box<T, A>) -> Rc<T, A> {
        Rc::from_box_in(v)
    }
}

it's simple: Box<dyn FnMut()> implements FnMut().

1 Like

The expression on the right side of the = constructs a Rc<RefCell<Box<dyn FnMut...>>>. The dyn value is never moved out of its Box.

Then, that value is coerced to the declared type Rc<RefCell<dyn FnMut...>> with another layer of dyn, which is allowed because Box implements FnMut when its contents do.

(By the way, please post text quotes instead of screenshots. Screenshots can be unreadable to some users or in some viewing conditions, and text allows us to copy parts of your code for easier testing and discussion of it.)

2 Likes

Very cool! This explains the behavior :raising_hands:

However - this means that it does not “work” in the sense that the Box is still there :frowning:

I guess me asking how to make it leave the Box to reduce the indirection would make the topic decay to this which suggested an inconvenient trait workaround - I suppose the situation is still the same and we can’t directly convert a Box to a Rc<RefCell>?

That’s right. The approach I would take, if possible, is to avoid the Box and create Rc<RefCell<dyn FnMut...>> right away. Remember that both Rc and RefCell have functions fn(&mut Self) -> &mut T, so you can use it like a Box until you actually need the sharing. If it helps, you can define a wrapper:

/// Construct this only with `Rc`s that haven't been cloned.
struct NotRcYet<T: ?Sized>(pub Rc<RefCell<T>>);

impl<T: ?Sized> AsMut<T> for NotRcYet<T> {
    fn as_mut(&mut self) -> &mut T {
        Rc::get_mut(&mut self.0).unwrap().get_mut()
    }
}

Then, use NotRcYet as a type to mark the absence of sharing. This actually has one advantage over your original plan: it doesn't re-allocate in order to move out of the Box. It's not very ergonomic, though. (What it really wants to be is the unstable UniqueRc type, which actually implements DerefMut and CoerceUnsized.)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.