How to call Weak::<dyn Fn(T)>::new()?

Minimal failure case:


pub struct EventLoop_Inner<T> {
    msgs: VecDeque<T>,
    handler: Weak<dyn Fn(T)>,
}

pub struct EventLoop<T> {
    inner: Rc<RefCell<EventLoop_Inner<T>>>,
}

impl<T> Clone for EventLoop<T> {
    fn clone(&self) -> Self {
        EventLoop { inner: self.inner.clone() }
    }
}

impl<T> EventLoop<T> {
    pub fn new() -> EventLoop<T> {
        EventLoop {
            inner: Rc::new(RefCell::new(EventLoop_Inner {
                msgs: VecDeque::new(),
                handler: Weak::<dyn Fn(T)>::new(),
            })),
        }
    }

It is complaining to me about unsatisfied dyn Fn(T): Sized

(followup on Soft question: micro components within a process - #3 by zeroexcuses )

EDIT: goal is the initial handler should be a Weak that resolves to None when Weak::upgrade is called

I'm not sure I understand your question correctly, but to me it sounds like Option or Rc would better fit your use case

The problem with Rc is that it causes loops:

  1. EventLoop has Rc to Handler
  2. Handler has Rc to sub_handlers
  3. sub_handlers need to be able to push back to EventLoop, and thus a Rc<EventLoop>

I am purposely picking Weak<Handler> instead of Option<Rc<Handler>> to break this loop.

You can’t call Weak::new for dyn Fn(T). Just like you can’t create a null pointer to unsized types, either. These types require valid pointer metadata which you cannot just get out of thin air.

What you can do is call it for a concrete closure type, then coerce to Weak<dyn Fn(T)>. This involves some extra steps as closure types cannot be named, but the following should work fine:

impl<T> EventLoop<T> {
    pub fn new() -> EventLoop<T> {
        fn weak<T, F: Fn(T)>(_: F) -> Weak<F> {
            Weak::new()
        }
        EventLoop {
            inner: Rc::new(RefCell::new(EventLoop_Inner {
                msgs: VecDeque::new(),
                handler: weak(|_| ()),
            })),
        }
    }
}
3 Likes

Is this effectively the same as:

let t = Rc::new(|_| {});
Rc::downgrade(&t);

?

We are creating a 'concrete" |_| {} and downgrading it to a Weak ?

No. We create |_| () and not use it at all (which doesn’t matter as |_| () has a zero-sized type and is trivial to construct). We use type inference to use its type for a Weak::new call. No Rc is ever created.

So we use a |_| () value for type inference only, then create a Weak<ClosureType> with Weak::new, then coerce it to Weak<dyn Fn(T)>.

This is borderline pedantic, but I'm just curious now. The only difference here I see is:

  1. one approach create a RcBox w/ strong=0, weak=1
  2. 2nd approach creates RcBox w/ strong=1,weak=0 ; then downgrades it to Rcbox w/ strong=0, weak=1

Besides this one difference, the two approaches do the same right ? (or am I missing something deeper)

Weak::new creates no memory allocations. It’s even const fn. (It uses a special address value of usize::MAX to indicate to the implementation that it’s specifically such a dangling weak pointer and operations like upgrade check for that case.)

Creating Rc allocates memory for the reference counters, even if the target type is zero-sized. Downgrading to Weak and dropping the Rc doesn’t free the memory either. It stays until the Weak is dropped.

1 Like

code

Weak:

pub struct Weak<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
> {
    // This is a `NonNull` to allow optimizing the size of this type in enums,
    // but it is not necessarily a valid pointer.
    // `Weak::new` sets this to `usize::MAX` so that it doesn’t need
    // to allocate space on the heap. That's not a value a real pointer
    // will ever have because RcBox has alignment at least 2.
    // This is only possible when `T: Sized`; unsized `T` never dangle.
    ptr: NonNull<RcBox<T>>,
    alloc: A,
}

Weak::new

    pub const fn new() -> Weak<T> {
        Weak {
            ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::<RcBox<T>>(usize::MAX)) },
            alloc: Global,
        }
    }

Weak::inner

    fn inner(&self) -> Option<WeakInner<'_>> {
        if is_dangling(self.ptr.as_ptr()) {
            None
        } else {
            // We are careful to *not* create a reference covering the "data" field, as
            // the field may be mutated concurrently (for example, if the last `Rc`
            // is dropped, the data field will be dropped in-place).
            Some(unsafe {
                let ptr = self.ptr.as_ptr();
                WeakInner { strong: &(*ptr).strong, weak: &(*ptr).weak }
            })
        }
    }

analysis

Okay, so in my case above, we create a RcBox, which we then hold on to while the Weak count is != 0.

In your case, we do NOT create a RcBox. Instead, we create a "dangling pointer" in the ptr: NonNull<RcBox<T>>, field -- and the Weak ptr code is smart enough to check for it and shortcircuit to None.

Okay, I concede these are two different paths. :slight_smile:

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.