Typescript allows us to code with types, but WebAssembly only supports primitive types.
Doubly frustrating, a WebAssembly supported language like Zig can easily represent the same type.
Are we screwed? Do we have to pass each field as a function parameter? That would work, but we can do better.
The trick is remembering, in a system language like Zig, a struct is simply a memory layout. All we need todo is write the TypeScript object into WebAssembly memory, and Zig will be able to read it natively.
If we line up all our ducks in a row on the Zig side, we can interpret the pointer directly as a pointer to a Person
!
Holup, didn't we say WebAssembly only supports primitive types? How is it possible to receive a Person pointer? Recall pointers are just memory addresses. The WebAssembly memory address space is 32-bit; thus, pointers are i32
. Therefore *Person
is the same as i32
!
What black magic is this encodePerson
? How does it take in a TypeScript person and produce a WebAssembly memory address?
We need to allocate memory and then write the values in the layout Zig expects. Most of this was already covered in a previous post when we showed how to send strings from Javascript to WebAssembly.
The memory layout is specified by the C ABI. Here is how Bob, with a GPA of 3.6, would be laid out.
The address of the person 0x0000AA00
is returned by the allocator. Same for the address of name 0x0000BB00
.
Once we understand the memory layout, encodePerson
is straightforward.
Since encodePerson
allocated memory, we need to clean this up on the Zig side.
Aside: Zig
defer
can be thought of as Javascripttry..finally
. Just note thatdefer
s are executed last in, first out.
Sending a Person
from Zig to Typescript is the same process but reverse. Allocate memory for Person
in Zig, pass over the pointer, decode Person
out of memory in Typescript, and deallocate memory.
See full code sample for passing Person
in either direction.
Do you want to learn the latest web tech while building esports? You're in luck, Battlefy is hiring.