Exporting a function to JS (original) (raw)
- Introduction
- 1. Examples
- 1.1. Hello, World!
- 1.2. Using console.log
- 1.3. Small Wasm files
- 1.4. Without a Bundler
- 1.5. Synchronous Instantiation
- 1.6. Importing functions from JS
- 1.7. Working with char
- 1.8. js-sys: WebAssembly in WebAssembly
- 1.9. web-sys: DOM hello world
- 1.10. web-sys: Closures
- 1.11. web-sys: performance.now
- 1.12. web-sys: using fetch
- 1.13. web-sys: Weather report
- 1.14. web-sys: canvas hello world
- 1.15. web-sys: canvas Julia set
- 1.16. web-sys: WebAudio
- 1.17. web-sys: WebGL
- 1.18. web-sys: WebSockets
- 1.19. web-sys: WebRTC DataChannel
- 1.20. web-sys: requestAnimationFrame
- 1.21. web-sys: A Simple Paint Program
- 1.22. web-sys: Wasm in Web Worker
- 1.23. Parallel Raytracing
- 1.24. Wasm Audio Worklet
- 1.25. web-sys: A TODO MVC App
- 2. Reference
- 2.1. Deployment
- 2.2. JS snippets
- 2.3. Static JS Objects
- 2.4. Passing Rust Closures to JS
- 2.5. Receiving JS Closures in Rust
- 2.6. Promises and Futures
- 2.7. Iterating over JS Values
- 2.8. Arbitrary Data with Serde
- 2.9. Accessing Properties of Untyped JS Values
- 2.10. Working with Duck-Typed Interfaces
- 2.11. Command Line Interface
- 2.12. Optimizing for Size
- 2.13. Supported Rust Targets
- 2.14. Supported Browsers
- 2.15. Support for Weak References
- 2.16. Support for Reference Types
- 2.17. Supported Types
- 2.18. #[wasm_bindgen] Attributes
- 2.18.1. On JavaScript Imports
- 2.18.1.1. catch
- 2.18.1.2. constructor
- 2.18.1.3. extends
- 2.18.1.4. getter and setter
- 2.18.1.5. final
- 2.18.1.6. indexing_getter, indexing_setter, and indexing_deleter
- 2.18.1.7. js_class = "Blah"
- 2.18.1.8. js_name
- 2.18.1.9. js_namespace
- 2.18.1.10. method
- 2.18.1.11. module = "blah"
- 2.18.1.12. raw_module = "blah"
- 2.18.1.13. no_deref
- 2.18.1.14. static_method_of = Blah
- 2.18.1.15. structural
- 2.18.1.16. typescript_type
- 2.18.1.17. variadic
- 2.18.1.18. vendor_prefix
- 2.18.1.1. catch
- 2.18.2. On Rust Exports
- 2.18.2.1. constructor
- 2.18.2.2. js_name = Blah
- 2.18.2.3. js_class = Blah
- 2.18.2.4. readonly
- 2.18.2.5. skip
- 2.18.2.6. skip_jsdoc
- 2.18.2.7. start
- 2.18.2.8. main
- 2.18.2.9. typescript_custom_section
- 2.18.2.10. getter and setter
- 2.18.2.11. inspectable
- 2.18.2.12. skip_typescript
- 2.18.2.13. getter_with_clone
- 2.18.2.14. unchecked_return_type and unchecked_param_type
- 2.18.2.15. return_description and param_description
- 2.18.2.1. constructor
- 3. web-sys
- 4. Testing with wasm-bindgen-test
- 5. Contributing to wasm-bindgen
- 5.2. Internal Design
- 5.3. js-sys
- 5.4. web-sys
- 5.5. Publishing
- 5.6. Team
The `wasm-bindgen` Guide
Alright now that we've got a good grasp on JS objects and how they're working, let's take a look at another feature of wasm-bindgen
: exporting functionality with types that are richer than just numbers.
The basic idea around exporting functionality with more flavorful types is that the Wasm exports won't actually be called directly. Instead the generatedfoo.js
module will have shims for all exported functions in the Wasm module.
The most interesting conversion here happens with strings so let's take a look at that.
# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn greet(a: &str) -> String {
format!("Hello, {}!", a)
}
#}
Here we'd like to define an ES module that looks like
// foo.d.ts
export function greet(a: string): string;
To see what's going on, let's take a look at the generated shim
import * as wasm from './foo_bg';
function passStringToWasm(arg) {
const buf = new TextEncoder('utf-8').encode(arg);
const len = buf.length;
const ptr = wasm.__wbindgen_malloc(len, 1);
let array = new Uint8Array(wasm.memory.buffer);
array.set(buf, ptr);
return [ptr, len];
}
function getStringFromWasm(ptr, len) {
const mem = new Uint8Array(wasm.memory.buffer);
const slice = mem.slice(ptr, ptr + len);
const ret = new TextDecoder('utf-8').decode(slice);
return ret;
}
export function greet(arg0) {
const [ptr0, len0] = passStringToWasm(arg0);
try {
const ret = wasm.greet(ptr0, len0);
const ptr = wasm.__wbindgen_boxed_str_ptr(ret);
const len = wasm.__wbindgen_boxed_str_len(ret);
const realRet = getStringFromWasm(ptr, len);
wasm.__wbindgen_boxed_str_free(ret);
return realRet;
} finally {
wasm.__wbindgen_free(ptr0, len0, 1);
}
}
Phew, that's quite a lot! We can sort of see though if we look closely what's happening:
- Strings are passed to Wasm via two arguments, a pointer and a length. Right now we have to copy the string onto the Wasm heap which means we'll be using
TextEncoder
to actually do the encoding. Once this is done we use an internal function inwasm-bindgen
to allocate space for the string to go, and then we'll pass that ptr/length to Wasm later on. - Returning strings from Wasm is a little tricky as we need to return a ptr/len pair, but Wasm currently only supports one return value (multiple return valuesis being standardized). To work around this in the meantime, we're actually returning a pointer to a ptr/len pair, and then using functions to access the various fields.
- Some cleanup ends up happening in wasm. The
__wbindgen_boxed_str_free
function is used to free the return value ofgreet
after it's been decoded onto the JS heap (usingTextDecoder
). The__wbindgen_free
is then used to free the space we allocated to pass the string argument once the function call is done.
Next let's take a look at the Rust side of things as well. Here we'll be looking at a mostly abbreviated and/or "simplified" in the sense of this is what it compiles down to:
# #![allow(unused_variables)]
#fn main() {
pub extern "C" fn greet(a: &str) -> String {
format!("Hello, {}!", a)
}
#[export_name = "greet"]
pub extern "C" fn __wasm_bindgen_generated_greet(
arg0_ptr: *const u8,
arg0_len: usize,
) -> *mut String {
let arg0 = unsafe {
let slice = ::std::slice::from_raw_parts(arg0_ptr, arg0_len);
::std::str::from_utf8_unchecked(slice)
};
let _ret = greet(arg0);
Box::into_raw(Box::new(_ret))
}
#}
Here we can see again that our greet
function is unmodified and has a wrapper to call it. This wrapper will take the ptr/len argument and convert it to a string slice, while the return value is boxed up into just a pointer and is then returned up to was for reading via the __wbindgen_boxed_str_*
functions.
So in general exporting a function involves a shim both in JS and in Rust with each side translating to or from Wasm arguments to the native types of each language. The wasm-bindgen
tool manages hooking up all these shims while the#[wasm_bindgen]
macro takes care of the Rust shim as well.
Most arguments have a relatively clear way to convert them, bit if you've got any questions just let me know!