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

Ability to send a Rust type to JS #286

Closed
matklad opened this issue Dec 18, 2017 · 13 comments
Closed

Ability to send a Rust type to JS #286

matklad opened this issue Dec 18, 2017 · 13 comments

Comments

@matklad
Copy link
Contributor

matklad commented Dec 18, 2017

Hi! I'd love to be able to create some value of type Foo in Rust, return it to JavaScript as an opaque object, and then get back a reference to it. I imagine API like

struct JsBox<T: Send+Sync> { ... }

impl<T> JsBox<T> {
  fn new<S: Scope<'j>>(scope: &mut S, value: T) -> Handle<'j, JsBox<T>> { ... }
}

impl<T> Deref for JsBox<T> {
   type Target = T;
}

Looks like currently the only way to move a Rust value to JavaScript is to wrap it into a JsClass, but it's often not convenient. For example, JsClass always has an init method, but often it does not makes sense (or outright impossible) to give JavaScript an ability to create an instance of the type.

I am not sure that the my description of the problem makes sense, so there's a TL;DR version. I want to

  1. create a value of some type T in Rust.
  2. return this value to JavaScript as an opaque thing.
  3. receive this opaque thing as a parameter and extract a &T from it.
@maciejhirsz
Copy link
Contributor

You could use Box::into_raw, then cast that to usize and return it as JsNumber, then once you get the number back you cast it back to *mut T or *const T and make it a regular reference with &*. This will obviously leak unless at some point in the future you re-create the Box and drop it, but then you also must be sure that JS does not have any references to it left or you can cause use after free.

@matklad
Copy link
Contributor Author

matklad commented Dec 19, 2017

Hm, can JsNumer handle usize? It's an f64, right? The safe solution along this lines would be to have a global HashMap<u32, T> registry of T values on Rust side. However, it also will leak memory and I really want to avoid it, because my types hold onto a lot of stuff. In theory, it's possible to add some acquire/release protocol on JavaScript side, but, at this point, just hacking it via declare_types is simpler :)

@maciejhirsz
Copy link
Contributor

You have 52 bits of precision on f64, so unless your hardware has more than 4.5 petabytes of RAM you should be fine. Alternatively you can make it a pair of two 32bit integers.

Also, instead of hashmap you could just store your stuff on Vec<T> and return the index :p.

@softprops
Copy link

https://github.com/GabrielCastro/neon-serde may be of interest to you

@matklad
Copy link
Contributor Author

matklad commented Dec 31, 2017

@softprops I already do use neon-sreder, however I am interested in round-tripping a Rust struct as is, without serialization, which is not applicable in my particular case.

@dherman
Copy link
Collaborator

dherman commented Mar 9, 2018

@matklad Would you be up for writing this up as an RFC? I think it would help understand the motivation and design better.

@matklad
Copy link
Contributor Author

matklad commented Aug 7, 2018

I am unlikely to write an RFC, but this thing from NAPi is exactly what I need:
https://nodejs.org/api/n-api.html#n_api_napi_create_external

@cztomsik
Copy link

@matklad well that is cool but how do you use it from rust? napi crate seems to be year old and without any further updates

is there a simple way to call napi_create_external_arraybuffer()?

@joshbenaron
Copy link

Hey, neon-serde is no longer maintained and not compatible with neon v0.8. What can I do?

@kjvalencik
Copy link
Member

@joshbenaron This isn't a great issue to comment on since it's about sending references to Rust data structures and not serialization. You may be interested in this proposal: #701

With that said, I've done a decent amount of benchmarking and because of the high cost of going between Node/V8 and native code (FFI), JSON serialization almost* always outperforms transcoding directly between Rust and JS.

*The one exception is if the structures contain binary data in the form of Buffer or ArrayBuffer.

@joepie91
Copy link

joepie91 commented Oct 3, 2023

Is this issue supposed to be closed? I'm not really seeing a solution to this problem in the previous replies; only a couple workarounds that seem to have tradeoffs around memory leaks and isolation in particular.

@kjvalencik
Copy link
Member

@joepie91 The original question is solved with JsBox. It is safe and does not leak.

Is there a particular use case you have that's not covered? (JsBuffer::external / JsArrayBuffer::external also exists, but is feature gated since Electron disables it)

@joepie91
Copy link

joepie91 commented Oct 4, 2023

Ah, yes, JsBox looks like it solves the issue - but I hadn't run across it yet, since it didn't seem to be mentioned in any of the replies here :)

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants