Storing value referencing closure argument in same struct

Wazzup crabbers. So I have stumbled upon this real doozy of a problem that I would appreciate a helping hand with. For context, I am working on React-style state hooks for dumle, the virtual DOM library. What I have working is this

let node = UseState::new(|state: &i32, set_state| {
    html! {
        <div>
            { state.to_string() }
            <button click=move |_| set_state(&|state: &mut i32| *state += 1),>
                {"Increment"}
            </button>
        </div>
    }
});

You specify a state, in this case i32, and a render function which returns a virtual node given the current state. This gets stored in a struct (see src/hook.rs:12) behind some Rc and RefCell:s:

struct UseStateData<N, S, R> {
    /// The virtual node.
    vnode: N,
    /// The mounted DOM node.
    node: Node,
    /// User data.
    user: S,
    /// The render function.
    render: R,
}

What I now want is to allow the rendered virtual nodes to keep references to the state, for example:

fn render() -> impl Vnode {
    let node = UseState::new(|state: &String, set_state| Text(state));
    node
}

but that gives the error:

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/lib.rs:44:52
   |
44 |     UseState::new(|state: &String, set_state| Text(&state))
   |                                                    ^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 44:19...
  --> src/lib.rs:44:19
   |
44 |     UseState::new(|state: &String, set_state| Text(&state))
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that the type `&std::string::String` is not borrowed for too long
  --> src/lib.rs:44:52
   |
44 |     UseState::new(|state: &String, set_state| Text(&state))
   |                                                    ^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that return value is valid for the call
  --> src/lib.rs:42:28
   |
42 | fn render() -> impl Vnode {
   |                ^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.

Here the trait bounds are (see src/hook.rs:95):

S: Default + 'static // The state
R: FnMut(&S, Box<dyn Fn(&Fn(&mut S))>) -> N + 'static // The render function
N: Vnode + 'static // The virtual node

Am I right in thinking that you would have to pin everything since UseStateData would be a self-referential struct? If so how would the trait bound for R look and how would you accommodate the lifetime? Don't really know where to begin - just removing the 'static on N doesn't get me much further.

I'll see if I can come up with a MVE.

Thanks in advance for any help on this.

That doesn't sound safe, because assignment to user data could make vnode referencing freed memory.

So to save the state, you should use Arc<String> (or Arc<str>):

UseState::new(|state: &Arc<str>, set_state| Text(Arc::clone(&state)))

That doesn't sound safe, because assignment to user data could make vnode referencing freed memory.

But the premise here is that user only ever occurs safely when set_state gets called, at which point render is called with the new user state to produce a new vnode referencing the new state. With some Cell/Option juggling in the middle to drop the old vnode before invalidating its references, that should be safe, right?

Here is a MVE: Rust Playground

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.