WebAssembly Multilanguage Magic: Harnessing Rust, Go, and TypeScript

WebAssembly Multilanguage Magic: Harnessing Rust, Go, and TypeScript

WebAssembly
Rust
Go
TypeScript
Programming
Integration
2025-03-02

Introduction

WebAssembly (Wasm) has opened the door for running compiled languages like Rust and Go at near-native speed in web browsers and beyond. This post explores how to create a simple math-focused Wasm module in Rust and a string-helper Wasm module in Go, then load and use these modules in a TypeScript environment (with an example using Deno). I love how Wasm allows picking the best tool for each task—Rust for efficient math operations and Go for handy string utilities—then wiring everything together in TypeScript.

Why WebAssembly?

WebAssembly is a low-level bytecode format designed to run in web browsers with performance close to native machine code. This means we can write performance-critical tasks in languages like Rust or Go, compile to Wasm, and then call those tasks from JavaScript or TypeScript. The result is often a faster and more robust application, mixing the convenience of high-level scripting with the speed and safety of compiled code.

Getting Started with Rust for WebAssembly

First, we’ll create a Rust project that implements a couple of math functions—factorial and fibonacci—to demonstrate the power of Rust in Wasm. Ensure rustup, cargo, and wasm-pack are installed.

cargo new --lib rust_math_wasm cd rust_math_wasm

Update your Cargo.toml to include wasm-bindgen and set cdylib as the crate type:

[package] name = "rust_math_wasm" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2.91"

Implementing Basic Math Functions in Rust

Let’s add our math code to src/lib.rs. The #[wasm_bindgen] attribute exposes these functions to JavaScript/TypeScript.

use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn factorial(n: u32) -> u32 { if n == 0 || n == 1 { 1 } else { n * factorial(n - 1) } } #[wasm_bindgen] pub fn fibonacci(n: u32) -> u32 { if n <= 1 { n } else { fibonacci(n - 1) + fibonacci(n - 2) } }

Compiling the Rust Module to WebAssembly

We’ll use wasm-pack to bundle everything for a web target:

wasm-pack build --target web

The output will go into a pkg directory with the generated .wasm file and JavaScript/TypeScript typings.

Setting Up a Go WebAssembly Module

Next, let’s create a Go module for string helpers—reversing strings and counting substring occurrences. We’ll rely on Go’s built-in support for Wasm. Make sure you have Go 1.18+ or higher installed for Wasm support and create a new folder:

mkdir go_string_helpers cd go_string_helpers

Add the following code to main.go:

package main import ( "strings" "syscall/js" ) func reverseString(this js.Value, args []js.Value) interface{} { if len(args) == 0 { return nil } str := args[0].String() runes := []rune(str) for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { runes[i], runes[j] = runes[j], runes[i] } return js.ValueOf(string(runes)) } func countOccurrences(this js.Value, args []js.Value) interface{} { if len(args) < 2 { return 0 } str := args[0].String() substr := args[1].String() return js.ValueOf(strings.Count(str, substr)) } func main() { js.Global().Set("reverseString", js.FuncOf(reverseString)) js.Global().Set("countOccurrences", js.FuncOf(countOccurrences)) <-make(chan bool) }

Compiling the Go Wasm Module

With main.go in place, we compile to Wasm by setting GOOS=js and GOARCH=wasm:

GOOS=js GOARCH=wasm go build -o main.wasm

You’ll see a main.wasm file appear in your directory. This file is your Go-based Wasm module.

Using Rust and Go Wasm in TypeScript (Deno Example)

Now that we have our Rust math Wasm module and Go string helper Wasm module, let’s integrate them into a TypeScript program. Here’s a quick example using Deno, but you can adapt it for Node or the browser.

mkdir ts_wasm_demo cd ts_wasm_demo

Create a file called main.ts:

// main.ts import init, { factorial, fibonacci, } from "./rust_math_wasm/pkg/rust_math_wasm.js"; // Use Deno's built-in Go WASM support declare global { interface Window { reverseString(str: string): string; countOccurrences(str: string, substr: string): number; } } // loadGoWasm sets up Go's global functions async function loadGoWasm() { const go = new (globalThis as any).Go(); const result = await WebAssembly.instantiateStreaming( fetch("go_string_helpers/main.wasm"), go.importObject ); go.run(result.instance); } async function main() { // Initialize the Rust Wasm package await init(); // Load and run the Go Wasm module await loadGoWasm(); // Testing Rust math functions console.log("Factorial of 5:", factorial(5)); console.log("10th Fibonacci number:", fibonacci(10)); // Testing Go string helper functions console.log("Reversed 'Hello':", globalThis.reverseString("Hello")); console.log( "Occurrences of 'l' in 'Hello':", globalThis.countOccurrences("Hello", "l") ); } main();

Structure your folders so main.ts can accessrust_math_wasm/pkg/rust_math_wasm.js and go_string_helpers/main.wasm using these paths.

Run the script:

deno run --allow-net --allow-read main.ts

Best Practices

While it’s exciting to combine different languages in a single WebAssembly-based project, it’s important to consider the trade-offs. Loading multiple Wasm modules (each compiled from a different language) can introduce extra complexity, both in build pipelines and in runtime overhead. Here are a few practices worth keeping in mind:

  • Minimize Language Proliferation: If multiple languages overlap in functionality, it might be simpler to unify around one that addresses your “hot” or performance-critical code.
  • Assess Overhead: Each Wasm module may load additional runtime support, so be strategic about how many modules you truly need.
  • Keep Builds Consistent: Standardize build scripts and tooling for cross-language projects—this can help smooth out your CI/CD pipeline.
  • Consider One Language for Core Logic: If your focus is performance-critical logic (like heavy math or cryptography), pick a single language known for its compiled-speed and memory safety (e.g., Rust). Then use other languages for specialized tasks only when it makes the most sense.

Ultimately, there’s no one-size-fits-all solution. Evaluate your team’s expertise, project size, and performance requirements before integrating multiple language Wasm modules into a single codebase.

Conclusion

You’ve just taken a tour of how to write two separate Wasm modules—one in Rust for math operations and another in Go for string helpers—and call them from TypeScript. This approach might seem contrived as an example, but it effectively showcases the flexibility WebAssembly brings to the table.

In reality, you might choose to unify on a single language if the bulk of your code is performance-critical, or keep a hybrid approach if you want to mix specialized libraries from multiple ecosystems. Real-world use cases include:

  • Combining Rust-based cryptography libraries with Go’s concurrency features in a security-focused application.
  • Using Rust for data transformation and Go for server-side WebAssembly tasks in cloud functions.
  • Integrating advanced math or statistical modules written in Fortran or C (compiled to Wasm) alongside a modern language frontend.

By understanding the fundamentals of compiling different languages to Wasm, you can harness the best of each ecosystem in a single app. We covered everything from building simple math and string helper Wasm modules, to orchestrating them in TypeScript. For deeper dives and further references, keep exploring the resources and academic links below. WebAssembly is still evolving, so remain curious and enjoy the ride!

Further Reading

Below are a few curated resources and references to help you continue exploring WebAssembly with Rust, Go, and TypeScript.

Key Resources

Writing WebAssembly in Rust

A comprehensive tutorial on setting up Rust for WebAssembly.

WebAssembly with Go

Official wiki on using WebAssembly in Go projects.

AssemblyScript (TypeScript for WebAssembly)

Documentation on writing AssemblyScript, a TypeScript-like language for WebAssembly.