Creating wrapper to call closure on RefCell contents

I want to write a convenience wrapper which takes a borrow to a RefCell and a closure as input, and simply calls the closure with a reference to the value contained in the RefCell:

fn call_cell<'a, T, F, R>(c: &'a RefCell<T>, f: F) -> R
where
    F: FnOnce(&'a T) -> R,
    R: 'static
{
    let r = c.try_borrow().unwrap();
    // in my actual code I have an alternative branch for the failed to borrow case
    f(&*r)
}    

This code doesn't compile: "r dropped while still borrowed".

Usually I have no trouble with the borrow checker, but for the life of me I can't figure this one out. I thought that R: 'static meant it's not possible for the output of f to contain a borrowed value.

Would anyone be able to explain in more helpful terms why this snippet is not compiling? Any tips how to implement a working solution would also be appreciated.

Thanks in advance.


Playground is here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=17e5dbe818fd71dcbbc77a2803611de6

The lifetime of the reference cannot be as long as the reference to the RefCell, in fact the reference cannot outlast the return value of try_borrow(). However, your signature says that the reference passed to the closure must last at least as long as the reference to the RefCell, hence the error.

Whether the return value of the closure can last forever or not is irrelevant to the issue.

To fix it, change it such that the closure must work with any lifetime.

fn call_cell<T, F, R>(c: &RefCell<T>, f: F) -> R
where
    F: for<'a> FnOnce(&'a T) -> R,
{
    let r = c.try_borrow().unwrap();
    // in my actual code I have an alternative branch for the failed to borrow case
    f(&*r)
}    

or in the shorthand form:

fn call_cell<T, F, R>(c: &RefCell<T>, f: F) -> R
where
    F: FnOnce(&T) -> R,
{
    let r = c.try_borrow().unwrap();
    // in my actual code I have an alternative branch for the failed to borrow case
    f(&*r)
}    

Ah, understood. Many thanks @alice for the explanation and recommendation - the shorthand form suggested works perfectly!

I'm now experimenting with going a step further and making it so that the call_cell function can work for closures which take either &T or &mut T.

It seems to me that the solution to do this should be using a trait.

However, naively moving the shorthand form of call_cell into a trait now fails the borrow checker again.

trait CallCell<T>: Sized {
    fn call_cell<F, R>(c: &RefCell<T>, f: F) -> R
    where
        F: FnOnce(Self) -> R;
}

impl<T> CallCell<T> for &T {
    fn call_cell<F, R>(c: &RefCell<T>, f: F) -> R
    where
        F: FnOnce(Self) -> R
    {
        let r = c.try_borrow().unwrap();
        // in my actual code I have an alternative branch for the failed to borrow case
        f(&*r)
    }
}

This time, the try_borrow() call is marked as the source of the error. I think this is a similar problem to before. impl<T> CallCell<T> for &T is requiring on a specific (implicit) lifetime, so again the closure FnOnce(Self) -> R is placing an incompatible constaint on the lifetime of its argument.

I don't believe I'm aware of any syntax to resolve this. Am I right in thinking that this further step to support both &T and &mut T with a trait is not possible?

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=60a7e34e091bb7d443c338866c8535ef

I recommend just supporting closures that take a mutable reference, as those are strictly more powerful than immutable references.

:+1: that's a sensible strategy! Thanks again.

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.