-
Notifications
You must be signed in to change notification settings - Fork 290
Arguments builder API #796
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
Conversation
- starter methods go straight into the combinators (.this(), .arg(), .args()) - typestate to ensure that calling .this() disallows .new() - .args() is overloaded on an Arguments trait to allow heterogeneous tuples
- Add doc comments to the new APIs - Rename the builders to `Call` and `FunctionCall` - Move the `JsFunction` methods to `JsFunction` instead of `Handle<JsFunction>`
kjvalencik
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this API. This is a big improvement. I think it can be optimized further if we don't tie ourselves to using the existing function call implementation and instead go directly to Node-API.
Goals
- Avoid allocating multiple vecs
- Avoid iterating multiple times
- Non-consuming builder
Since both Handle and JsValue are #[repr(C)] with only a single field, these types are equivalent to napi_value.
It should be safe to transmute directly from &mut [Handle<JsValue>] to &mut [napi_value]. This does not require consuming the Vec or making any copies.
Details: - prepare_call didn't need to be marked unsafe - prepare_call takes an immutable reference to the args vector - abstracted the internal calling/constructing logic in `do_call`/`do_construct` helpers so it can be shared - replace the Vec with a SmallVec in the arguments builders
…` -- a little cleaner since it avoids passing a raw::Local argument
I made these changes but didn't literally transmute the CI is mostly passing, I just need to fix linting errors… |
|
I just realized |
…cts so that `Call::new()` can return a more precise type.
|
Ready for re-review when you have a chance, @kjvalencik 🙏 |
Ah! That's right, this is a raw pointer, so |
kjvalencik
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use a few tests on new functionality. For example, testing non-unit args tuples.
This reverts commit 9e6ba88.
- JsFunction only provides access to builders through an `apply()` method - Rename Call to InvocationBuilder - Rename FunctionCall to CallBuilder
|
@kjvalencik Looking at usage code in the tests yesterday, I didn't quite like where we'd landed with the API. Having builder methods like let o = cx
.argument::<JsFunction>(0)?
.arg(cx.number(0.0))
.construct(&mut cx)?;and it's especially confusing to see the
This makes examples like the above one clearer: let o = cx
.argument::<JsFunction>(0)?
.apply()
.arg(cx.number(0.0))
.construct(&mut cx)?;Even thought it's slightly less concise, when you're already method chaining I don't feel like it makes a huge difference. And I found one-liners often still fit on one line since I also think this makes the docs clearer, which gives me some confidence that it's an improvement: But very open to feedback! |
|
@dherman That's some really great points! I'm convinced. I think |
|
Following up on a Slack conversation @kjvalencik and I had: his let o = cx
.call_with()
.arg(cx.number(0.0))
.apply(&mut cx); |
…ns` and `.construct_with() -> ConstructOptions` API.
|
@kjvalencik Should be ready for another review! I moved the new types into |
|
I'm going to close this for now since I submitted #817 to implement part of this functionality (in particular the |

FirstSecondN'th (I lost count) draft of a function arguments builder API, which tries to provide a more ergonomic experience for invoking JS functions.Benefits
thisbinding in a temp variable before invoking the functionthisbinding ofundefinedfor common caseBefore:
After:
Open questions
Since.call()is generic in two types (the context and the result type), turbofish instantiation is a little ugly (e.g..call::<_, JsArray>(&mut cx)). You can usually do the instantiation in a let binding instead (see the above example), and.exec()avoids having to instantiate in the case where you're ignoring the result. So maybe this is OK, but I'm just calling it out.I madeArgumentsimplementCloneso you can reuse and call an instance of the builder multiple times. We could possibly makecall()etc not consumeselfbut would that require forcing an extra vector copy?Are we OK with requiring.args(())for the empty arguments case? It seems like the least-annoying option we've come up with, compared to:f.with().call(&mut cx)f.args::<JsValue>().call(&mut cx)The one other option I can think of is backwards-incompatibly removing the existing.call()method fromJsFunctionand having.call(),.new(), and.exec()methods directly onJsFunction. But this could be quite a disruptive change to our existing users.