Since rust uses utf-8 strings and js uses UCS-2, we need to use the TextDecoder object to convert from one to the other. In addition we are going to be storing strings in the shared memory as an ArrayBuffer which is just an array of unsigned 8bit integers. This means that we need a way to convert it from numbers to letters.

const TextDecoder = typeof self === 'object' && self.TextDecoder
    ? self.TextDecoder
    : require('util').TextDecoder;

const TextEncoder = typeof self === 'object' && self.TextEncoder
    ? self.TextEncoder
    : require('util').TextEncoder;

These two getter methods provide a way to access the wasm.memory.buffer value as a js typed array. When we are working with rust strings we want the array to be of the u8 type because it will represent utf8 otherwise we want it to be u32.

let cachedUint8Memory = null;
function getUint8Memory() {
    if (cachedUint8Memory === null ||
        cachedUint8Memory.buffer !== wasm.memory.buffer)
        cachedUint8Memory = new Uint8Array(wasm.memory.buffer);
    return cachedUint8Memory;
}
let cachedUint32Memory = null;
function getUint32Memory() {
    if (cachedUint32Memory === null ||
        cachedUint32Memory.buffer !== wasm.memory.buffer)
        cachedUint32Memory = new Uint32Array(wasm.memory.buffer);
    return cachedUint32Memory;
}

In getStringFromWasm we are pulling the bytes out of memory by slicing off the portion that starts at ptr and ends at prt+len. We then pass that buffer into the TextDecoder to return it as a string. in passStringToWasm we are taking in a string and then encoding it as utf8, then allocating space for it in the shared memory by calling __wbindgen_malloc which will return the address it starts at. Finally we are going to call the set method on our memory ArrayBuffer, this method takes an array and an optional offset. To clarify the first argument of set is going to be encoded string, the second argument says where to start that writing. once we have written the string to our shared memory, we will return a 2 item array where 0 is the pointer and 1 is the length.

function getStringFromWasm(ptr, len) {
    return cachedDecoder.decode(getUint8Memory().slice(ptr, ptr + len));
}
function passStringToWasm(arg) {
    const buf = cachedEncoder.encode(arg);
    const ptr = wasm.__wbindgen_malloc(buf.length);
    getUint8Memory().set(buf, ptr);
    return [ptr, buf.length];
}

Rust utilize some additional memory outside of the main WASM memory system. This memory is called a linear memory stack that allows it to store information for use outside of the main WASM stack machine. globalArgumentPtr function is going to get the address in shared memory of this special section of memory. getGlobalArgument and setGlobalArgument get a specific position in this or set the value of a specific position in this memory.

let cachedGlobalArgumentPtr = null;
function globalArgumentPtr() {
    if (cachedGlobalArgumentPtr === null)
        cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
    return cachedGlobalArgumentPtr;
}

function getGlobalArgument(arg) {
    const idx = globalArgumentPtr() / 4 + arg;
    return getUint32Memory()[idx];
}

function setGlobalArgument(arg, i) {
    const idx = globalArgumentPtr() / 4 + i;
    getUint32Memory()[idx] = arg;
}

This little guy simply captures any rust panics and then converts the error message into a js Error.

export function __wbindgen_throw(ptr, len) {
    throw new Error(getStringFromWasm(ptr, len));
}

Now lets look the functions we defined in rust, first with generate_greeting. The first thing this does is call our wasm function. This will put the return position where our string starts and put the length if it into position 0 of the liner memory stack. Next we get the length out of memory and pass these two values to our get string helper function, lastly we remove the string from the WASM context’s memory (remember it is being returned so the WASM context does care about it any more).

export function generate_greeting() {
    const ret = wasm.generate_greeting();
    const len = getGlobalArgument(0);
    const realRet = getStringFromWasm(ret, len);
    wasm.__wbindgen_free(ret, len * 1);
    return realRet;
}

When looking over generate_custom_greeting, I want to first point out that the body of the try block should look exactly like the body of generate_greeting. The first thing we need to do put our string into the WASM context, remember this function returns the pointer and length. Now we need to set the global argument to the length of our string, that way rust can pull it in and re-construct the string. Once we then pass our values to WASM and just before returning we free up our string from memory since the rust function is all done with it.

export function generate_custom_greeting(arg0) {
    const [ptr0, len0] = passStringToWasm(arg0);
    setGlobalArgument(len0, 0);
    try {
        const ret = wasm.generate_custom_greeting(ptr0);
        const len = getGlobalArgument(0);
        const realRet = getStringFromWasm(ret, len);
        wasm.__wbindgen_free(ret, len * 1);
        return realRet;
    } finally {
        wasm.__wbindgen_free(ptr0, len0 * 1);
    }
}

If you are seeing more than this in the version you created, you can remove that extra stuff by adding the following to your Cargo.toml file.

[profile.dev]
lto = true
[profile.release]
lto = true

By adding that you are turning on “Link Type Optimizations” which should remove any dead code from your program.