4

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Zak
  • 1,910
  • 3
  • 16
  • 31
  • Possible XY problem, Rc is a smart pointer, you shouldn't borrow it, please [read](https://doc.rust-lang.org/book/ch15-04-rc.html). – Ömer Erden Dec 28 '18 at 21:14
  • Yes, but you do need to `borrow()` from a `RefCell`, `callback.borrow()` will dereference the `Rc` implicitly and call `borrow()` on the underlying `RefCell`. – Zak Dec 28 '18 at 21:16
  • 1
    Ok but read the [reference](https://github.com/rust-lang/rust/blob/master/src/libcore/cell.rs#L758) ," The borrow lasts until the returned `Ref` exits scope" . This means you'll not able to return the borrowed one. – Ömer Erden Dec 28 '18 at 21:59
  • Okay, so is there a way to make this work? – Zak Dec 28 '18 at 22:03
  • 1
    @ÖmerErden was on point. Pretty much the only way is to hold a `struct` containing the `Ref`, which isn't pretty, but works. – Sébastien Renauld Dec 28 '18 at 22:18
  • 1
    Additionally, let's say you have a Callback and it can change at runtime(i assume thats why you are using RefCell), your function works against the Refcell's nature because it boxes the inner data, it expects you to access inner data via itself. I believe you are trying to make some util function to unwrap reference of inner data, i suggest you to write macro for it but If your main concern is not a interior mutability, you don't need to use RefCell. – Ömer Erden Dec 28 '18 at 22:22
  • Thank you for your time and explanation @ÖmerErden, I'll look into using a macro! – Zak Dec 28 '18 at 23:25

2 Answers2

4

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.

Sébastien Renauld
  • 19,203
  • 2
  • 46
  • 66
  • 1
    If you are not able to use `borrow_mut` for the whole lifetime you shouldn't use `RefCell`, it brings complexity and gives nothing more than `Rc` . – Ömer Erden Dec 28 '18 at 22:28
  • 1
    @ÖmerErden I wouldn't recommend it with a caveat if there wasn't a way around it. The OP's use case is most likely a case where he needs the `RefCell` - an oft-updated closure element present on both the JS ABI and rust side (this is obvious from the types used in his example), while having the `RefCell` content **and** reference last until the next update. It's not a good solution *everywhere*, but in this case, you can very easily get around this limitation. – Sébastien Renauld Dec 28 '18 at 22:35
  • Sadly, I believe my use case is the problematic one described – I am calling this whilst I am trying to declare the callback in the first place (I know!), it's a recursive callback function. I'll look into this though as well as the possible macro solution, thanks for your time; that explanation is really helpful. – Zak Dec 28 '18 at 23:28
  • @Zak take it one step further and move the closure into the object (if you can), and provide a way to `swap` the value from the outside. – Sébastien Renauld Dec 28 '18 at 23:33
  • Hmmm...that's interesting, surely I won't be able to swap the closure though as I am still using it (in order to actually define it)? – Zak Dec 28 '18 at 23:37
  • @Zak if you never swap it, what do you need the `RefCell` for? I expected from the `RefCell` that you'd at least update it once in a while. Should you actually want to do that, if the entire thing is in that object, you can quite literally destroy and recreate or `swap` from the outside and everything will follow suit. – Sébastien Renauld Dec 28 '18 at 23:40
  • @SébastienRenauld The `RefCell` is used in order update it, but it only needs to be updated once, on definition of the closure. However, whilst it is being defined, I need access to the closure itself in order to refer to it for recursion. It is essentially the example given [here](https://rustwasm.github.io/wasm-bindgen/examples/request-animation-frame.html). – Zak Dec 28 '18 at 23:44
  • 1
    @Zak then this answer is indeed the right one. When you need to access it (but not modify), you create an object like the one I've shown in the answer, use it for read-only usage, then destroy it when you're done. :-) – Sébastien Renauld Dec 28 '18 at 23:48
  • Okay, I see – thanks, that will work! Thanks for the help – Zak Dec 28 '18 at 23:53
2

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.

Zak
  • 1,910
  • 3
  • 16
  • 31