Hacker News new | past | comments | ask | show | jobs | submit login
Supercharge Your Node.js with Rust (yieldcode.blog)
101 points by skwee357 3 months ago | hide | past | favorite | 23 comments

Neon maintainer here, happy to answer any questions folks might have!

It's a pretty exciting time for Neon. We're nearly feature-complete for a 1.0 release, which will come with a bunch of benefits and should help us build momentum for future improvements:

  * No more legacy backend -- just one simple and clean way to use Neon, fully audited for safety.
  * Strong ABI and API stability guarantees.
  * Convenient APIs for general-purpose multithreading and creating custom async Node events.
  * Ergonomic and modern use of JS promises, which interact beautifully with async Rust code.
Some of the things I'm looking forward to exploring post-1.0 is even more ergonomics and performance improvements.

Is there a way to generate automatic bindings for rust/js code from the AST of the language and a list of definitions to specify? I'm imaging how powerful a Bazel ruleset for this could be. Something like

  js_rust_library(... defs = ["someFunction"] ...)

That's a cool idea! The closest thing today is probably neon-serde, a cool community library that lets you generate bindings from lightweight Rust annotations:

Another thing I'd love to explore is using TypeScript declarations (e.g. from a .d.ts file) to auto-generate bindings.

If you're interested in playing with any of these ideas feel free to hop onto our community Slack and chat!

Thank you for building it! I haven't kicked the tires yet, but as a node developer, this feels like the future: we can move fast with node when it makes sense, and get rust level performance when we need to. Best of both worlds!

Did you consider compiling to WASM/WASI, and if yes, what were the reason to go with native DLLs?

I assume this question was directed at me (author of the post).

I've considered it. But honestly, I don't know a lot about WASM (I should explore it more), so I haven't compiled it to WASM. Maybe some members of the community can provide more insights.

It is definitely something I'll try to check at some point later.

In case it was directed at me, I’ll say yes, it’s on my mind, but my first goal was to have the full power of the entire OS facilities, the full Cargo ecosystem, and full native performance with the safety of Rust. That said, I think wasm has still only just begun to take off and it’s on my radar.

I just wanted to say: Thank you! Neon is great!

NAPI-RS is another option to Neon. It’s faster and has a new feature in beta that automatically does the type wrapping for you.

Thanks for the suggestion! NAPI-RS does look cleaner than Neon. I wonder what's the difference though. Do you know maybe? It looks like NAPI-RS Docs does not list any meaningful differences.

I’ve been using NAPI-RS and I like it a lot so far. The feature that caused me to switch was callbacks - specifically, I wanted to pass a JS callback to Rust, and have Rust call it repeatedly. I couldn’t figure out how to do that with Neon, but Napi has support and docs for it.

One difference is that NAPI-RS uses Node’s N-API for calling Node, and Neon has a mix of N-API and v8 backends. I’m not sure if this is the main reason behind the speed difference or not.

> And yet, I rarely hear people suggest or even evaluate the solution of embedding a native module that most of the times will be faster.

Because there's rarely a real benefit to this approach.

I have yet to found use case where C/C++/Rust library is useful AND spawning process to do heavy work is not a viable option AND JS-only library won't work.

I can think basically only about communication with external resource like database.

We're doing exactly this in our language libraries for Tangram. Our core machine learning code is written in Rust and we use a native add-on so people can make predictions directly from their javascript apps. In Python, this is incredibly common: NumPy, LightGBM, XGBoost are written in C/C++ and exposed to Python.

A really common use case that comes to mind immediately: parsing and AST transforms. Quite common in build tooling, quite noticeable performance difference as evidenced by SWC. (ESBuild doesn’t expose its AST, but it’s also quite a lot faster than comparable plain JS at what it does expose in its JS interface).

I wrote about use case where "spawning process to do heavy work is not a viable option". ESBuild DOES spawn process to do heavy work.


You’re correct. I didn’t cite ESBuild as a use case, I mentioned it to give credit where it was due.

The performance difference between webpack and esbuild is like night and day

I can think basically only about communication with external resource like database.

And when it is a database, all the benefits of a compiled language turn into just a fancy no-brainer wrapper which still sucks because of “impedance mismatch”. It would be nice if that was true jit-integrated rust or at least C ffi, which one could call directly from js. But I don’t think that v8 will get such capability in observable future.

Small improvement: The call to unwrap() in fibonacci_api() can be replaced by `?` because JsResult is just a fancy std::result::Result

You are right. I'm a messy person when it comes to `?` and `.unwrap()`

> handle.value(&mut cx) as i32

What happens when the user in JS land passes in an incorrect data type? Does it segfault? Does it truncate the first 32 bits?

If they don't pass a number at all, looks like cx.argument() will return a Result::Err (which in the example will panic because of the unwrap(), but can instead be bubbled up as another commenter pointed out here: https://news.ycombinator.com/item?id=28977697)

JsNumber::value() then returns an f64 to encapsulate all possible JS numbers, and `as i32` is just casting that float into an int. The exact behavior for this cast is that a) any fractional component will be dropped, and b) if the number is larger than the destination type, the destination's maximum value will be used. Both of these behaviors could of course be controlled by adding logic, like an explicit call to .round()

As @brundolf said above me, it will return Error because I explicitly downcast it to `JsNumber` and unwrap the result. So from JS side you'll get an error.

Obviously, you should handle such cases properly.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact