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

Vectors not working in structs #439

Open
David-OConnor opened this issue Jul 10, 2018 · 14 comments
Open

Vectors not working in structs #439

David-OConnor opened this issue Jul 10, 2018 · 14 comments

Comments

@David-OConnor
Copy link
Member

David-OConnor commented Jul 10, 2018

I'm receiving the following error:
the traitstd::marker::Copyis not implemented forstd::vec::Vec

for this struct:

#[wasm_bindgen]
pub struct Mesh{
    pub tris: Vec<u32>,
}

If I change tris' type to u32, the error goes away.

This page lists Vectors and slices of supported integer types as supported.

@alexcrichton
Copy link
Contributor

Thanks for the report! Currently though public struct fields are a special case in that they only work with Copy types. This should definitely be better documented!

@David-OConnor
Copy link
Member Author

Thanks; removing pub on the field fixed it

@David-OConnor
Copy link
Member Author

David-OConnor commented Jul 12, 2018

Related Q: How do you extract a value from a struct passed via bindgen to JS? The object I see when printing to console.log() contains a ptr field of a large integer, and a very deep recursive structure containing values like , free, constructor, bind, call etc. Eg, how can I make mesh.tris work, eg returning a number[] ?

This page in the official guide shows example struct passing from Rust to JS, but doesn't show accessing a value contained in one.

edit: Getter functions, like in the example on that guide appear to work. Is there a way around this? Perhaps auto-generating getter funcs for all struct fields?

@alexcrichton
Copy link
Contributor

@David-OConnor ah that's got its own separate bug for defining JS getters in Rust

@David-OConnor
Copy link
Member Author

@alexcrichton Perhaps the solution, assuming there's an obstacle to exposing fields directly, is to serialize the struct, pass it to JS, then deserialize into an object. This assumes no methods.

@alexcrichton
Copy link
Contributor

Oh currently the main obstacle in a sense is that it's not clear what to do if the field isn't Copy because JS gets just a snapshot and mutating that isn't clear you're not mutating the original Rust value. For example in JS if you did mesh.tris.push(3) it wouldn't necessarily be reflected back in the tris field in Rust.

@David-OConnor
Copy link
Member Author

David-OConnor commented Jul 13, 2018

That makes sense. One way to handle this is to send one-way structs to JS, that don't call back to the Rust code. I got it working like this, using serde:

Rust:

#[derive(Serialize)]
#[wasm_bindgen]
pub struct MyStruct {
    // All fields here are are serializable by Serde, or are structs that derive Serialize themselves.
}

#[wasm_bindgen]
pub fn get_mystruct() -> String {
    let mystruct = MyStruct {..};

    serde_json::to_string(&mystruct).unwrap()
}

JS:

const rust = import("./from_rust");
rust.then(
    r =>
    {
        let myStruct = JSON.parse(get_mystruct());
    })

No methods, but can be fed into a constructor for a JS object that has methods, or just used as-is, optionally by declaring it with a Typescript interface.

@alexcrichton Do you think this would be useful in a more streamlined, official way, eg that handles the serialization/deserialization automatically? This seems like it could be a common use-case. Eg make Rust code that does something complicated or interesting, then call a Rust func from JS, which passes the result in whichever data structures are convenient... At least when I heard of WASM and bindgen, this was the first use-case that came to mind.

@alexcrichton
Copy link
Contributor

@David-OConnor oh we already have those apis actually!

wasm-bindgen/src/lib.rs

Lines 153 to 206 in 1a84901

/// Creates a new `JsValue` from the JSON serialization of the object `t`
/// provided.
///
/// This function will serialize the provided value `t` to a JSON string,
/// send the JSON string to JS, parse it into a JS object, and then return
/// a handle to the JS object. This is unlikely to be super speedy so it's
/// not recommended for large payloads, but it's a nice to have in some
/// situations!
///
/// Usage of this API requires activating the `serde-serialize` feature of
/// the `wasm-bindgen` crate.
///
/// # Errors
///
/// Returns any error encountered when serializing `T` into JSON.
#[cfg(feature = "serde-serialize")]
pub fn from_serde<T>(t: &T) -> serde_json::Result<JsValue>
where
T: serde::ser::Serialize + ?Sized,
{
let s = serde_json::to_string(t)?;
unsafe {
Ok(JsValue {
idx: __wbindgen_json_parse(s.as_ptr(), s.len()),
})
}
}
/// Invokes `JSON.stringify` on this value and then parses the resulting
/// JSON into an arbitrary Rust value.
///
/// This function will first call `JSON.stringify` on the `JsValue` itself.
/// The resulting string is then passed into Rust which then parses it as
/// JSON into the resulting value.
///
/// Usage of this API requires activating the `serde-serialize` feature of
/// the `wasm-bindgen` crate.
///
/// # Errors
///
/// Returns any error encountered when parsing the JSON into a `T`.
#[cfg(feature = "serde-serialize")]
pub fn into_serde<T>(&self) -> serde_json::Result<T>
where
T: for<'a> serde::de::Deserialize<'a>,
{
unsafe {
let mut ptr = ptr::null_mut();
let len = __wbindgen_json_serialize(self.idx, &mut ptr);
let s = Vec::from_raw_parts(ptr, len, len);
let s = String::from_utf8_unchecked(s);
serde_json::from_str(&s)
}
}

@David-OConnor
Copy link
Member Author

@alexcrichton Sick!

@eminence
Copy link
Contributor

You also cannot store a pub String in a struct, presumably for the same reason outlined in this issue. Strings are pretty fundamental types. Could we expand the documentation to mention this limitation? https://rustwasm.github.io/docs/wasm-bindgen/reference/types/string.html

@benma
Copy link

benma commented Jul 1, 2023

This ByteString struct can't be returned from a function because the Vec is not Copy:

#[wasm_bindgen]
pub struct ByteString {
    pub bytes: Vec<u8>,
}

But a clone directly in a getter works:

#[wasm_bindgen]
impl ByteString {
    pub fn get(&self) -> Vec<u8> {
        self.bytes.clone()
    }
}

This adds a seemingly useless indrection - now every Vec that is returned anywhere needs to be wrapped in such a struct and accessed via a getter. Is there a reason why this could not work directly? Would be very appreciated.

Same for String.

@daxpedda
Copy link
Collaborator

daxpedda commented Jul 3, 2023

#439 (comment)

Oh currently the main obstacle in a sense is that it's not clear what to do if the field isn't Copy because JS gets just a snapshot and mutating that isn't clear you're not mutating the original Rust value. For example in JS if you did mesh.tris.push(3) it wouldn't necessarily be reflected back in the tris field in Rust.

This would require some design work and figuring out what to do here.

@brainstorm
Copy link

brainstorm commented Jan 8, 2024

@daxpedda Would generating getters for all struct members as @David-OConnor suggested be a good initial compromise (usability for perf) or were you thinking on other (better) design patterns from the get-go?

I'm willing to give either a try after fixing my cargo issues with wasm-bindgen.

@daxpedda
Copy link
Collaborator

daxpedda commented Jan 8, 2024

It would have to be done with the help of an attribute, because doing it by default is probably a bad idea.

Personally I'm not much in favor of doing this, as this can be done outside of wasm-bindgen.
I'm generally very hesitant to keep increasing the scope of this project.

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

No branches or pull requests

6 participants