I've developped a library in Rust to parse RTF content. I've initially targeted x86 architectures. So I have use lots of string slices (&str
) to reference to the original source without copying its content. For instance, a Token
looks like :
enum Token<'a> {
PlainText(&'a str),
OpeningBracket,
ClosingBracket,
...
}
I want to update my library to also target WebAssembly. I have taken a look into the wasm-bingen
crate, an the Rust Wasm book, and it looks like wasm_bindgen
doesn't support references nor lifetimes.
Is there an elegant solution for me to adapt my codebase to support WebAssembly without sacrificing the initial performances ?
I have conditional compilation, but I still have the lifetime issue :
#[cfg(not(target_arch = "wasm32"))]
pub type StrRef<'a> = &'a str;
#[cfg(target_arch = "wasm32")]
pub type StrRef = String;
How can I expose this Token
enum, for instance, to be used in a JS environement ?
There are more problems here than you've found.
JavaScript does not have a borrow checker, so there’s no zero-cost way to use long-lived references across the FFI boundary, because nothing can ensure they remain valid when used. You cannot return a type with a lifetime to JS, at all.
You can’t share strings without copying, either. JavaScript String
s are opaque — you cannot access their bytes, only their characters — but likely to be encoded in UTF-16. A Rust &str
by definition points to text encoded in UTF-8. So, there is not and cannot be a way to freely share string buffers between JavaScript and Rust.
(You can avoid copying the text of a string by using a js_sys::JsString
which points to the JS String object in the JS heap, but then you have to do individual bridged operations to access text from that string, so it's unlikely to be faster.)
Your Token
type won't work at all. You can't export an enum with fields, because JS doesn't have enums / discriminated union types. You’ll need to find a way to express your tokens as JS objects.
Given all of these issues, I would recommend that instead of trying to use conditional compilation to adjust type definitions, you create separate types and functions dedicated to providing the JS-compatible API that makes the most sense for JS. Approach this as wrapping your existing library, not as modifying it to be compatible.
In fact, you should consider making this JS binding wrapper a separate crate that depends on your library. If you insert #[wasm_bindgen]
exports in your main Rust library, this means that every Rust-to-WebAssembly build including your library will include all those exports to JS, even if your library is being used solely by other Rust code within that one Wasm module. (This could even cause a name conflict.) Keeping it separate means that the JS exports are only present when they are needed.