Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create functions from closures #811

Merged
merged 4 commits into from
Nov 17, 2021
Merged

Create functions from closures #811

merged 4 commits into from
Nov 17, 2021

Conversation

kjvalencik
Copy link
Member

@kjvalencik kjvalencik commented Oct 6, 2021

What

Allow Neon users to create functions from closures.

    cx.export_function("count_called", {
        let n = std::cell::RefCell::new(0);

        move |mut cx| {
            *n.borrow_mut() += 1;

            Ok(cx.number(*n.borrow()))
        }
    })?;

How

The closure is boxed and added as data. It works very similar to the current functions except the trampoline function is generic.

Dropping the Rust closure requires napi_add_finalizer which was added in Node-API 5. For this reason, when using older Node-API versions, only fn instead of Fn are accepted.

Future Expansion

It would be unsound to accept FnMut because a function may be reentrant. A function could trivially accept a reference to itself and then call that function. However, it is very cumbersome to use RefCell to get access to mutable state.

Since most functions will not have any possibility for reentrancy and since closure lifetime inference is very limited in Rust, it would be useful for Neon to provide a JsFunction::new_mut that accepts an FnMut and wraps it in a RefCell.

test/napi/lib/functions.js Outdated Show resolved Hide resolved
@dherman
Copy link
Collaborator

dherman commented Oct 13, 2021

Would it be better to leave them the same and document the limitation that closures will be leaked?

We talked about this and decided it's better to conditionally compile it for now and avoid the leak; most users won't even notice the lack of functionality in older Node-API versions but it will avoid a hard-to-detect bug.

@dherman
Copy link
Collaborator

dherman commented Oct 15, 2021

As appealing as this is, unfortunately allowing FnMut is unsound. You can make a function call itself recursively, which Rust doesn't allow for closures (since there can be a live active borrow of the closure data).

let mut data = /* ... */;

let f = JsFunction::new(move |mut cx| {
    let this: Handle<JsObject> = cx.this()?;
    let method: Handle<JsFunction> = this.get(&mut cx, "recursive")?;
    let n: Handle<JsNumber> = cx.argument(0)?;
    let n = n.value(&mut cx) as u32;
    println!("obj.recursive({})", n);

    do_something(&data); // borrow the mutable data in the closure

    if (n > 0) {
        let n = cx.number(n - 1);
        let args = vec![n.upcast()];
        method.call(&mut cx, this, args)?;
    }
});

obj.set("recursive", f)?;

@kjvalencik kjvalencik force-pushed the kv/boxed branch 2 times, most recently from 67c049d to 29886fd Compare October 20, 2021 19:59
@kjvalencik kjvalencik changed the base branch from main to next/0.10 October 20, 2021 20:07
@kjvalencik kjvalencik force-pushed the kv/boxed branch 2 times, most recently from fc0e01b to 45e123a Compare October 20, 2021 20:09
Copy link
Collaborator

@dherman dherman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. I just have a few suggestions for clarity and one question about a possible leak.

test/napi/src/js/functions.rs Show resolved Hide resolved
test/napi/src/js/functions.rs Show resolved Hide resolved
test/napi/lib/functions.js Show resolved Hide resolved
test/napi/src/js/functions.rs Show resolved Hide resolved
test/napi/lib/functions.js Show resolved Hide resolved
src/types/mod.rs Outdated Show resolved Hide resolved
crates/neon-runtime/src/napi/fun.rs Show resolved Hide resolved
crates/neon-runtime/src/napi/fun.rs Show resolved Hide resolved
crates/neon-runtime/src/napi/fun.rs Show resolved Hide resolved
Copy link
Collaborator

@dherman dherman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarifying comments. Looks great!

@kjvalencik kjvalencik changed the title RFC: Create functions from closures Create functions from closures Nov 17, 2021
@kjvalencik kjvalencik merged commit afca75e into next/0.10 Nov 17, 2021
@kjvalencik kjvalencik deleted the kv/boxed branch November 17, 2021 17:40
dherman pushed a commit that referenced this pull request Dec 1, 2021
feat(neon): Create functions from closures
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants