How to create a function returning a borrowed value?

Viewed 4137

I have the following function as part of a Rust WASM application to convert a Boxed closure into the Rust-representation for a JavaScript function.

use js_sys::Function;

type Callback = Rc<RefCell<Option<Closure<FnMut()>>>>;

fn to_function(callback: &Callback) -> &Function {
    callback.borrow().as_ref().unwrap().as_ref().unchecked_ref()
}

However, the compiler complains that the return value uses a borrowed value (obtained with callback.borrow()) so cannot be returned.

Hence, I decided to add lifetime annotations to inform the compiler that this new reference should live as long as the input.

use js_sys::Function;

type Callback = Rc<RefCell<Option<Closure<FnMut()>>>>;

fn to_function<'a>(callback: &'a Callback) -> &'a Function {
    callback.borrow().as_ref().unwrap().as_ref().unchecked_ref()
}

Unfortunately, this hasn't helped and I get the same error. What am I doing wrong here?

2 Answers

Yeah, this isn't going to work.

callback.borrow().as_ref().unwrap().as_ref().unchecked_ref()

Let's break this down in steps:

  1. You're borrowing &RefCell<Option<Closure<FnMut()>>> - so you now have Ref<Option<...>>, which is step #1 of your issues. When this happens, this intermediary value now has a different lifetime than 'a (inferior, to be precise). Anything stemming from this will inherit this lesser lifetime. Call it 'b for now
  2. You then as_ref this Ref, turning it into Option<&'b Closure<FnMut()>>
  3. Rust then converts &'b Closure<FnMut()> into &'b Function

Step 1 is where the snafu happens. Due to the lifetime clash, you're left with this mess. A semi-decent way to solve it the following construct:

use std::rc::{Rc};
use std::cell::{RefCell, Ref};
use std::ops::Deref;

struct CC<'a, T> {
    inner: &'a Rc<RefCell<T>>,
    borrow: Ref<'a, T>
}

impl<'a, T> CC<'a, T> {
    pub fn from_callback(item:&'a Rc<RefCell<T>>) -> CC<'a, T> {
        CC {
            inner: item,
            borrow: item.borrow()
        }
    }
    pub fn to_function(&'a self) -> &'a T {
        self.borrow.deref()
    }
}

It's a bit unwieldy, but it's probably the cleanest way to do so.

A new struct CC is defined, containing a 'a ref to Rc<RefCell<T>> (where the T generic in your case would end up being Option<Closure<FnMut()>>) and a Ref to T with lifetime 'a, auto-populated on the from_callback constructor.

The moment you generate this object, you'll have a Ref with the same lifetime as the ref you gave as an argument, making the entire issue go away. From there, you can call to_function to retrieve a &'a reference to your inner type.

There is a gotcha to this: as long as a single of these objects exists, you will (obviously) not be able to borrow_mut() on the RefCell, which may or may not kill your use case (as one doesn't use a RefCell for the fun of it). Nevertheless, these objects are relatively cheap to instantiate, so you can afford to bin them once you're done with them.

An example with Function and Closure types replaced with u8 (because js_sys cannot be imported into the sandbox) is available here.

Although I really like Sébastien's answer and explanation, I ended up going for Ömer's suggestion of using a macro, simply for the sake of conciseness. I'll post the macro in case it's of use to anyone else.

macro_rules! callback_to_function {
  ($callback:expr) => {
    $callback
      .borrow()
      .as_ref()
      .unwrap()
      .as_ref()
      .unchecked_ref()
  };
}

I'll leave Sébastien's answer as the accepted one as I believe it is the more "correct" way to solve this issue and he provides a great explanation.

Related