4
votes

I want to generate a vector of bytes (Vec<u8> in Rust) and access it with JS as an Array or Uint8Array and send it to a WebSocket or to IndexedDB.

I found How can I pass an array from JavaScript to Rust that has been compiled with Emscripten?, which is the exact opposite of what I would like to do, but very relevant. Other than that, I am aware of the array type in Emscripten, but I am not aware how to correctly use it.

My best guess on how to get this working was to try returning the vector as_mut_ptr, and use the pointer on the Module.HEAPU8.

main.rs

#[no_mangle]
pub fn bytes() -> *mut u8 {
    vec![1, 2, 3].as_mut_ptr()
}

fn main() {}

Part of index.html

var Module = {
    wasmBinaryFile: "site.wasm",
    onRuntimeInitialized: main,
};
function main() {
    let ptr = Module._bytes();
    console.log(ptr);
    console.log(Module.HEAPU8.slice(ptr, ptr + 10));
    console.log(Module.HEAPU8.subarray(ptr, ptr + 100));
    let arr = Module.cwrap('bytes', 'array', []);
    console.log(arr());
}

The results of the console ended up looking like this:

5260296  site:11:13
Uint8Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]  site:12:13
Uint8Array [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90 more… ]  site:13:13
5260296  site:15:13

The first issue is that both values represent empty arrays, and second the two separate calls are pointing to the same memory location. I have absolutely no idea how to access pointed to data on the heap, along with the length of the vector.

The two pointers pointing to the same memory location could be because Rust drops the Vec<u8> write when it's lifetime ends, (end of the bytes) function.

Sorry if I missed some basics of Wasm and Emscripten, I only built my first Wasm hello world today.

2
I'm not familiar with the Javascript side of Wasm and neither very much with rust, but I'm afraid there are not too many experts on both around here, either. Here are a few ideas that may get you on track: 1. Do you need to create the array in rust? It may be possible to pass a Javascript array as (mutable) reference to rust and modify it there. 2. You may find more information how to do this in C/C++, which may give you an idea how to do the same in rust.MB-F

2 Answers

1
votes

The same rules as with writing Rust apply here. That means the function has to return an owned value; currently it returns a pointer to data that is dropped when the function returns.

One would return Vec<u8> which consists of (ptr, length, capacity) and is too big to return to C.

There are two similar solutions to this:

  1. return Box<Vec<u8>> and define another function that extracts the pointer from it.

  2. define your own Vec that is accessible from C.

I am using the latter here.

0
votes

Alright, so after taking the ideas from @sebk (Thanks a ton for the pointers). This is what I came up with.

It actually works well, so I'll describe it quickly. We need a representation that we can access an array from javacript, so mainly we need a pointer and the length of the array (represented in the JsVec). In wasm you can only pass integers/floats, so we need to return a raw pointer, the Box has into_raw so we can return a raw pointer to our JsVec and get the information. In order to prevent Rust from dropping our vector, we need to forget about the vector, using mem::forget.

In the javascript world it is as simple as accessing the data on the heap via the pointer and the Module.HEAPU32 value.

The next issue is the dropping of the vector, so we use the raw pointer and create a Box from it which gets automatically dropped, from my understanding it drops the JsVec object, but not the vec or the content. This is the main area where it could go wrong, so is this going to memory leak? Or will dropping the JsVec be sufficient.

Thanks again for helping me out.

Edit:

Yay! I seem to have gotten it working (gist is updated). I took this reddit comment's advice and constructed a vector from JsBytes (renamed) struct so as to make sure the vector itself is dropped!

This works and the gist works in my browser.