-
Notifications
You must be signed in to change notification settings - Fork 282
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
feat(neon-macros): Export Macro #1025
Conversation
0030a58
to
a9df7be
Compare
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 is incredible. No joke, I have never been so excited for the DX of Neon!
My suggestions are mostly for documentation and comments.
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.
Looks great! I cannot wait for us to merge this one.
Just one markdown bug to fix -- I left a suggestion.
crates/neon/src/macros.rs
Outdated
/// #### `context` | ||
/// | ||
/// The `#[neon::export]` macro looks checks if the first argument has a type of | ||
/// &mut FunctionContext` to determine if the [`Context`](crate::context::Context) |
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.
/// &mut FunctionContext` to determine if the [`Context`](crate::context::Context) | |
/// `&mut FunctionContext` to determine if the [`Context`](crate::context::Context) |
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.
LGTM!
This PR builds on top of #1024 and makes several significant changes to module loading.
Export magic, without explicit registration, is possible because of the
linkme
crate. When exporting, we use the crate to add a "create" function to a static slice inside Neon (doc hidden).TODOneon::export
#1029FunctionContext
instead of borrowed. Filed follow-up: Improve error messages for#[neon::export]
#1031Addon Initialization
Previously,
#[neon::main]
generated an unmangledunsafe extern "C" fn napi_register_module_v1
inside the users crate. This known symbol is invoked by Node when an addon is loaded. Instead of creating and exporting this function in the macro, it is moved to Neon itself. This has a nice side effect of no longer emittingunsafe
code in the user's crate.Instead
#[neon::main]
will register their function in a global, doc hidden, slice inside Neon. Even thoughlinkme
supports arbitrary lengths, Neon continues to only support a single main. This is for a few reasons:The primary benefit of this change is that
#[neon::main]
is now optional. If a user doesn't need to execute any special code at module load, they can skip it completely. There is a small behavior change where having multiplemain
becomes a runtime panic instead of a compile time error.TryIntoJs
The
TryIntoJs
trait is added as an analog toTryFromJs
. It is used to convert a Rust value into a JavaScript value. There are impls on many built-in types as well as theJson
type introduced with extractors.TryIntoJs
is not an exact mirror ofTryFromJs
; there are two key differences:Output
type added. Rust types have the opportunity to express the exact JavaScript type they will use. This is useful to avoid infallible downcasts.Error
The
neon::types::extract::Error
type is a new extractor used to make error handling easy when working with#[neon::export]
exported functions. At it's core, it is a wrapper aroundBox<dyn std::error::Error + Send + Sync + 'static>
. This is very similar to types found in app error handling crates likeanyhow
.There are two special parts of this error:
kind
to determine what type of JS error is represented (e.g.,Error
,TypeError
)TryIntoJs
for converting to a JS errorAlong with a blanket impl of
TryIntoJs
onResult
whereT: TryIntoJs, E: TryIntoJs
, functions can return this error and a JavaScript error will be automatically thrown.✨ Magic ✨
Error
has a blanket impl ofFrom<E>
whereE: Into<Box<dyn Error + Send + Sync + 'static>>
. This means most errors can be automatically converted with?
, includinganyhow::Error
and&'static str
. Users can change return signatures toResult<T, neon::types::extract::Error>
and use idiomatic error handling.Limitation:
Error
is somewhat un-idiomatic because it doesn't implementstd::error::Error
. This is because the blanketFrom<T>
impl would conflict with the aforementionedFrom
. Crates likeanyhow
andeyre
have this same limitation.#[neon::export]
The
neon::export
macro allows registering values to be exported at addon load. If amain
function is not provided, they will be exported automatically. However, if a user registers a#[neon::main]
they must manually export. This gives the opportunity to transform if necessary.Globals
In most cases, it's as easy as adding
#[neon::export]
to the item being exported. This will work on bothconst
andstatic
.By default exported values use the Rust identity as the JavaScript export name. However, users can customize by including the
name
attribute in the annotation. This is especially useful if they would like the JavaScript field name to be an invalid Rust identifier.Users can use the
Json
extractor toserde_json::to_string
andJSON.parse
the response when exporting.Since this is likely to be a common pattern and it impacts the Rust code, which otherwise doesn't need the wrapper, a convenience attribute can be used instead. This adds the
Json
wrapper when creating without changing the global value.Functions
Functions register themselves identically to globals and similarly can use
name = ".."
to change the export name. The ✨ magic ✨ of function exports are provided byFromArgs
andTryIntoJs
. These allow a very simple wrapper that does two things:cx.args()
using the function artiyTryIntoJs::try_into_js
on the return typeThe argument list is inspected to see if the first argument is
FunctionContext
. If it is, this will also be provided to the function. Unlike functions created withJsFunction::new
, these get a borrowedFunctionContext
in order to avoid soundness issues when callingtry_into_js
(could allow multiple contexts to be active).Since users may have renamed the
FunctionContext
import, we also allow acontext
attribute to force it to be included.Lastly, since many users will want to leverage
serde
for complex arguments and return values, wrappers can be automatically injected instead of needing to explicitly include theJson
extractor by adding ajson
attribute.Using
json
requires also inspecting the return type to see if it is aResult
. If the return type is aResult
,res.map(Json)
is used instead ofJson(res)
.Tasks
The Neon Task API is an ergonomic way to schedule work to be performed on the libuv worker pool and return a promise when it completes. When using
#[neon::export]
on a function atask
attribute can be added (#[neon::export(task)]
) to make the body execute asynchronously on another thread instead.Unlike previous examples, all arguments must be
Send
. This means you cannot use this simplified version with exported functions that referenceFunctionContext
.