Can I use Vec<[f32; 2]> as rust wasm function return value

I am trying to use rust in web developing now, But some complex data structure types, I don't know how to return. (do not want to use into_serde because I think it will cause extra overhead)

for example:

#[wasm_bindgen]
pub fn test_f32_2_vec() -> Vec<[f32; 2]> {
    let example = vec![[1.0, 2.0], [3.0, 4.0]];
    return example;
}

and the result:

error[E0277]: the trait bound `std::boxed::Box<[[f32; 2]]>: wasm_bindgen::convert::IntoWasmAbi` is not satisfied
  --> src/wasm.rs:55:1
   |
55 | #[wasm_bindgen]
   | ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::convert::IntoWasmAbi` is not implemented for `std::boxed::Box<[[f32; 2]]>`
   |
   = help: the following implementations were found:
             <std::boxed::Box<[f32]> as wasm_bindgen::convert::IntoWasmAbi>
             <std::boxed::Box<[f64]> as wasm_bindgen::convert::IntoWasmAbi>
             <std::boxed::Box<[i16]> as wasm_bindgen::convert::IntoWasmAbi>
             <std::boxed::Box<[i32]> as wasm_bindgen::convert::IntoWasmAbi>
           and 9 others
   = note: required because of the requirements on the impl of `wasm_bindgen::convert::IntoWasmAbi` for `std::vec::Vec<[f32; 2]>`

error: aborting due to previous error

I did make some attempts, but still didn’t convert correctly.

So I want to know if this is feasible or if I have to serialize it?

Thank you very much!

You cannot get JS's normal array objects such as [[1, 2], [3, 4]] from wasm because that is on JS's heap.
You only can send data from wasm to JS is either a scalar value (such as f64) or by accessing wasm linear memory from JS.
For example, Rust Vec<f32> is accessed from JS by

const float32Memory = new Float32Array(wasm.memory.buffer);
// ptr is (vec.as_ptr() as usize) in Rust
const array = float32Memory.subarray(ptr / 4, ptr / 4 + len);

You can see this from generated JS files.
This is why IntoWasmAbi is limited to the selected primitive values.

That being said, one way to accomplish the result with minimal overhead is to reinterpret the slice of [f32; 2] as a slice of f32 of twice the length. From JS, it is accessed by calculating index such as array[i * 2 + 0] or you may write a helper function.

Doing this by a reinterpreting cast:

fn convert(v: Box<[[f32; 2]]>) -> Box<[f32]> {
    unsafe {
        let len = v.len();
        let slice_ptr = Box::into_raw(v);
        let ptr = slice_ptr as *mut [f32; 2];
        let new_slice_ptr = std::slice::from_raw_parts_mut(ptr as *mut f32, len * 2) as *mut _;
        Box::from_raw(new_slice_ptr)
    }
}

I think the above code is okay (i.e. not an UB) but I'm not sure.

Because this kind of unsafe thing is difficult to get right so if you want to be safer you can copy values to a newly allocated vector:

let mut w = vec![0f32; v.len() * 2];
for (a, b) in w.chunks_exact_mut(2).zip(v) {
    a[0] = b[0];
    a[1] = b[1];
}

The overhead is still much less than the overhead of serialization.

1 Like