How to Handle Strings Between Rust and JavaScript in WASM

Web
Use `wasm-bindgen` to automatically handle the conversion between Rust `String` and JavaScript `String`, as it manages the necessary memory allocation and encoding for you.

Use wasm-bindgen to automatically handle the conversion between Rust String and JavaScript String, as it manages the necessary memory allocation and encoding for you. For manual control or custom formats, you must explicitly manage the memory buffer and use UTF-8 encoding, ensuring you free the memory on the Rust side after JavaScript reads it.

The standard approach relies on #[wasm_bindgen] attributes which create a transparent bridge. When you pass a String from Rust to JS, the runtime allocates memory, copies the UTF-8 bytes, and returns a pointer that JS converts back to a native string. Conversely, JS strings are copied into a Rust buffer and converted to a String.

Here is a practical example using wasm-bindgen:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: String) -> String {
    // Rust receives the JS string as a native String
    format!("Hello, {} from Rust!", name)
}

#[wasm_bindgen]
pub fn get_length(s: &str) -> usize {
    // You can also accept &str for zero-copy reads if the lifetime is managed
    s.len()
}

On the JavaScript side, you simply call these functions as if they were native:

import { greet, get_length } from './pkg';

const message = greet("Alice");
console.log(message); // "Hello, Alice from Rust!"

const len = get_length("Hello");
console.log(len); // 5

If you cannot use wasm-bindgen and must handle memory manually, you need to export the string bytes and their length, then reconstruct the string in JS using TextDecoder.

use std::ffi::CStr;
use std::os::raw::c_char;
use std::ptr;

// Allocate a null-terminated string in linear memory
#[no_mangle]
pub extern "C" fn rust_string_to_c_str(rust_str: &str) -> *const c_char {
    // In a real scenario, you would allocate this in a custom allocator
    // or use a global buffer to avoid stack overflow.
    // This is a simplified conceptual example.
    let c_str = rust_str.as_ptr() as *const c_char;
    c_str
}

However, manual memory management is error-prone. If you do go this route, ensure you implement a corresponding free function in Rust that JS must call to prevent memory leaks in the WebAssembly heap.

#[no_mangle]
pub extern "C" fn free_string(ptr: *const c_char) {
    // Logic to deallocate the memory pointed to by ptr
    // This is critical; failing to call this leaks memory in the WASM instance.
    unsafe {
        // Example using a custom allocator or global pool
        // libc::free(ptr as *mut libc::c_void); 
    }
}

In summary, always prefer wasm-bindgen for its safety and ease of use. Only resort to manual *const c_char or raw byte buffers if you have specific performance constraints or are integrating with a non-standard JS environment where automatic bindings are unavailable.