rc::Weak extending lifetime?

I have a situation where I would like to pass to a 'static callback an rc::Weak that can be upgraded to the original Rc if it's still around.

When the struct stored in the Rc has i32 or other value types, everything is fine. It works as expected. As soon as I add a reference, though, I get errors about contradicting lifetime requirements. I don't understand why this is... it's as if the rc::Weak is extending the lifetime of the reference, but that shouldn't be true, should it? I mean, the whole point of it, I thought, was to move the borrow-checking to run time (effectively).

Apologies if the above is non-sensical; here's some code to demonstrate the issue...

#![allow(unused)]

use std::cell::RefCell;
use std::rc::Rc;

trait Bar {
    fn baz(&mut self);
}

struct Foo<'a, T> where T: Bar {
    t: &'a mut T,
}

struct OtherFoo<'a, T> where T: Bar {
    foo : Rc<RefCell<Foo<'a, T>>>,
}

fn do_cb<F>(cb : F) where F : Fn() + 'static {
    
}

fn do_thing<'a, T>(t: &'a mut T) -> OtherFoo<'a, T> where T: Bar {
    let foo = Rc::new(RefCell::new(Foo{ t }));
    
    let weak_foo = Rc::downgrade(&foo);
    let cb = move || {
        // This is what breaks... if I comment it out, it's fine.
        if let Some(foo) = weak_foo.upgrade() {
            foo.borrow_mut().t.baz();
        }
    };
    do_cb(cb);
    OtherFoo { foo }
}

(Playground)

The problem is that the value isn't 'static because it captures the 'a input value. If the Weak is upgraded elsewhere, that elsewhere can retrieve and copy out the inner 'a value, but it has no idea what 'a is.

Reference counting with Rc<T> only really works to share ownership of T, but it cannot change the lifetime of T itself (in this case, RefCell<Foo<'a, T>>).

2 Likes

Oh, that makes sense. I didn't think about the possibility of copying it.

What I'm really trying to do is pass a non-'static callback to a library that only takes 'static callbacks and was thinking I could effectively move the borrow checking to run-time and eat the cost in exchange for the flexibility I need. Specifically, I'm trying to wrap JavaScript WebSocket objects as exposed by web_sys (a part of wasm-bindgen) such that I can have a Rust "socket" with callbacks for on-open, on-close, on-message, etc. but not require that these be 'static. Perhaps there's a better way...

The thing you're trying to do fundamentally is sound, but Rc doesn't have the right semantics for it – at least not by itself.

After all, once some code upgrades a Weak to a Rc, nothing prevents it from storing the Rc somewhere and extend its lifetime indefinitely. You need something that dynamically verifies that all references have been dropped, and aborts if not. This necessarily can't be implemented in safe code – after all, if the check were skipped, the behavior would be unsound, yet the check itself isn't unsafe, so there must be unsafe somewhere else to create the potential for unsoundness. But it's possible to create a wrapper with a safe interface.

It feels like something that ought to exist already. From a quick search on crates.io, I wasn't able to find anything, but I bet it's out there somewhere...

Here's a quick attempt at an implementation from scratch – warning: might be completely broken.

Some notes on implementation choices in the above:

  • rc_borrow has to accept a lambda rather than just returning an object with the check in its destructor, because otherwise you could bypass the check by calling mem::forget.
  • But the abort check has to be in a destructor anyway so that it runs even if the function panics, hence RcBorrowBomb.
  • As written it only handles an immutable reference, but you could pass an &RefCell<&mut Foo>, or upgrade it to handle mutability itself...
3 Likes

I have a similar use case as Sydius but I'm having trouble understanding how the proposed rc_borrow solution works. I have a static global hashmap that stores signal handlers. In these signal handlers, I need to invoke a method on an object that I do not want to have a static lifetime. (Otherwise, the requirement of a static lifetime would percolate through the entire application, essentially rendering all of Rust's memory management meaningless. The way I understand it, I would end up with a huge memory leak, i.e., all allocated memory would persist until the end of the program.)

I was also hoping to create the link between the global static anchor and the lifetime managed objects using Rc::weak or, if Rc::weak is not suitable, using an alternate mechanism, but I'm unsure how to do this.

I'll sketch what I intend to do in pseudo C below:

weakref *global;

void signalhandler(void) {
   if (strong = upgrade_weakref(global)) {
         use(strong);
   }
}

main() {
   object * ns = new object();   // not `static
   global = make_suitable_weakref(ns);
   ....
}

My program guarantees that no other code runs during the call to use(strong). I can also guarantee that use() would not make copies of strong itself that would keep references to the object referred to be global (so I could work with an unsafe mechanism that makes these assumptions.) I'm mentioning this because the concern that was raised by CAD97, namely that the lifetime of the inner 'a value is not known may not apply to my use case.

You should open a new thread.