Today we are releasing BrowserPod 3.0, with full support for running any Rust application in the browser, many fixes for Node.js, and initial Python support.
Our Rust support goes beyond what can normally be achieved with the existing Wasm targets in terms of standard library features and third-party crate compatibility. Programs can access the filesystem, make network requests, run subprocesses, and interact with concurrently running applications. All of this without changing a single line of code!
In the demo below, you can see the preview version of Yarn 6 (written in Rust) installing an NPM project:
What is BrowserPod
BrowserPod is an in-browser code sandbox. Its goal is to make it possible to run any Linux application in modern browsers by compiling the application to WebAssembly and providing the full Linux syscall interface.
It provides an efficient, locally persistent virtual filesystem, outbound internet access for downloading packages or calling APIs, and inbound connections for exposing local development servers. BrowserPod supports real parallelism by running each thread or process on an independent Worker, while providing a consistent view of the system to all the running programs. For all purposes, it can be considered an OS kernel for the Web platform, implemented in WebAssembly.
This set of features makes BrowserPod uniquely suited for safe in-browser agentic code execution, web-based IDEs and development environments, interactive docs and live demos, educational platforms, and other applications that benefit from sandboxed execution inside a web app.
Why Rust now?
BrowserPod’s ambition is to run an entire Linux userspace in the browser. For the most part, this used to mean compiling a bunch of C/C++ projects.
Most dynamic languages, such as Python, JavaScript or Ruby run on top of interpreters and runtimes that are also, most usually, written in C/C++, and so having a C/C++ toolchain (in our case, Cheerp) would get you very far.
Nowadays, this is less and less true. Popular languages like Rust and Go are compiled ahead of time like C/C++, and have their own toolchains.
Many build tools for Node.js in particular are being written (or rewritten) in Rust.
For these reasons, Rust was always part of our roadmap for BrowserPod, but we decided to prioritize it over Python and Ruby thanks to a concrete use case from a member of our community on Discord.
One maintainer of the Yarn package manager expressed interested in building an interactive documentation page, featuring a real yarn build, and running on BrowserPod. But contrarily to previous versions, which were JavaScript, the upcoming Yarn 6 release is built in Rust.
This is one of the use cases where we think that BrowserPod can really shine, and so we started working on it right away! It took us a week to get to a first prototype and we could immediately see the potential, but there were more moving parts that we had expected.
What does it mean to “support” Rust
In previous releases of BrowserPod we focused on Node.js. Our users did not have to worry about compiling for the BrowserPod target, since we provide a pre-compiled Node.js build ourselves.
Rust programs behave very differently, since they need to be compiled ahead of time using rustc. As things stand today, the user needs to compile the program offline and add the resulting binary to the Pod. Interestingly, rustc itself is written in Rust. In principle, we could allow users to compile their Rust programs directly in the browser. We need a few additional features to achieve this objective, but it will happen in the near future.
Effectively “supporting Rust” today boils down to providing a Rust toolchain that can produce Wasm binaries for the BrowserPod target.
Existing Rust WebAssembly targets
Fortunately, we could start from solid ground. Rust already supports a variety of Wasm targets, namely:
wasm32-unknown-unknown: This target is the most barebones and platform agnostic. By itself, it makes no assumptions about the environment it runs in, but crates like
wasm-bindgenand
web-sysmake it possible to communicate with the JS and Web environment. This target makes sense if you are deliberately targeting the Web from the start. Many standard library features are missing.
wasm32-unknown-emscripten: This target is intended for standalone programs targeting the Web. It can directly interact with JavaScript code, and already wraps many Web APIs for ease of use. It also emulates some POSIX APIs to an extent, but many Rust standard library features are not implemented. In general, supporting this target requires extensive rewriting.
wasm32-wasip1/
wasm32-wasip2/
wasm32-wasip3: These are the WASI
targets. They can run in principle in any environment, although their main use is for WebAssembly outside of the browser. As such, they don’t assume the presence of JS, but they provide applications with I/O facilities like a filesystem and networking.
wasm32-wali-linux-musl: This target is intended to run existing Linux applications in a Wasm runtime, by providing the x86-64 Linux syscall interface as imports. Its main use case is outside of the browser, and it is currently mostly an academic project.
Why not just pick WASI?
Our original plan was to implement a WASI (WebAssembly System Interface) layer on top of BrowserPod. This solution would make any program targeting WASI immediately compatible with BrowserPod, whether it’s written in Rust, Go, C, or anything else.
But when trying to compile existing Rust programs to WASI, we quickly realized that it’s not just a matter of switching build targets.
Take yarn for example. Compiling it for WASI would require:
- Platform-specific code to replace usage of
std::os::unixwithstd::os::wasi: this is doable, although the mapping is not 1:1 (e.g. no absolute symlinks in WASI). - Removal of thread usage:
wasm32-wasip3has some limited cooperative threading support (without real parallelism), but it’s very new and supported by neither the standard library nor tokio.
- Acceptance of reduced functionality:
yarnrelies on external programs for some functionality: in particulargit(to fetch git dependencies) andnode(to run lifecycle scripts) are required for basic operations.
BrowserPod already solves these problems, so we decided to skip the middleman
and implement our own Rust target: wasm32-browserpod-linux-musl.
Making our own Rust target
Defining a custom Rust target is surprisingly easy: you define your target’s properties
in a .json file:
{ "llvm-target": "wasm32-unknown-unknown", "target-pointer-width": 32, "target-c-int-width": 32, "data-layout": "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20", "arch": "wasm64", "is-like-wasm": true, "target-family": ["unix"], "os": "linux", "env": "musl", "vendor": "browserpod",
"linker-flavor": "wasm-ld", "linker": "rust-lld", ...}And then pass the path of this file as the --target argument.
A few unstable features are needed, so we have to use a nightly version of the compiler.
We also need to support our target in the Rust standard library for our combination of arch and os.
Since it’s modeled after x86 Linux, we can mostly copy from that.
And finally, we need to override a couple of foundational libraries: libc (we need to link to our own musl libc build) and linux-raw-sys (we just select the libc fallback).
A number of options to cargo and rustc are needed to tie everything together (unstable feature flags, the sysroot with the C dependencies, the cargo overrides for libc and linux-raw-sys, …); we provide wrapper scripts for them, so the user can compile with a simple cargo build --target wasm32-browserpod-linux-musl.
The hacks we did along the way
In an ideal world, this would be all we need. Unfortunately, we need to deal with the messy reality of third-party dependencies.
As mentioned, Rust already supports multiple Wasm targets.
Many libraries can compile for one or more of those targets, but often with reduced
functionality, or with the assumption that arch="wasm32" means “running in the browser’s main thread”,
and other arbitrary constraints.
To reach our goal of compiling projects without changing the code, we need to dodge all the conditional compilation that would mistakenly categorize our target as a “reduced functionality” one.
If you look closer at the json snippet of our target definition above, you will notice two interesting things:
-
target_family: ["unix"]: originally, we had["unix", "wasm"], but many crates use the wasm target family to mean “standalone web build”, “WASI”, “no filesystem”, or similar. Dropping “wasm” seems to be completely harmless. -
arch: "wasm64": this is even more surprising, but unfortunately also necessary.arch="wasm32"is often used to detect standalone web targets (for example,reqwestwill replace the whole HTTP client with a
fetch()implementation). You would think that something would break because of this, but important info like the size of pointers is defined separately, and the actual target passed to the LLVM backend is stillwasm32, so it all works out!
Of course, we would love to get rid of these hacks, but the proper solution requires awareness in the ecosystem that fully-featured Wasm targets exist, which will take time (but we have a plan).
The payoff
The original goal was to run yarn, but since our approach was completely general, we found that
a lot of programs just work.
Here is a (non-exhaustive) list of popular programs that I tried, but we expect most CLI applications to just work:
| Project | Rust SLOCs | Changes | Status |
|---|---|---|---|
| burntsushi/ripgrep | ~40k | no changes | working |
| yarnpkg/zpm | ~50k | no changes | working |
| starship/starship | ~50k | no changes | working |
| jj-vcs/jj | ~200k | no changes | working |
| openai/codex | ~1.25M | disabled some sandboxing options | working |
You can play with them in the terminal below:
Codex is fairly large and requires an API key to run, so you can also see it in action in the following video:
Or try it yourself at https://browsercode.io/agents/codex.
Using it
Rust has a single blessed package manager and build system: cargo. This makes it much easier for us to provide a good out-of-the-box developer experience compared to, say, C/C++.
Rust also has an official way to obtain and manage multiple toolchains: rustup. We can’t be distributed through rustup yet, because we are not an officially supported target, but we can install into its directories, so you can enable our toolchain for a single project in the usual way:
# manually install our toolchain (one day this could be via rustup too)curl https://rt.browserpod.io/3.0.0/rust/install.sh | bash
# override the toolchain for the current project with rustuprustup override set browserpod-3.0.0
# buildcargo build --target wasm32-browserpod-linux-musl
# uninstall via rustuprustup toolchain uninstall browserpod-3.0.0Future plans
I explained why the existing Wasm targets were not suitable for us, but I glossed
over wasm32-wali-linux-musl.
Despite being aimed at native Wasm runtimes rather than browsers, WALI (WebAssembly Linux Interface) has the same core goal as BrowserPod: compile Linux applications unmodified to Wasm. The main difference really is that BrowserPod targets the x86 version of the Linux syscalls, while WALI targets the x86-64 version. The choice for BrowserPod comes from the fact that its syscall implementations are shared with CheerpX
, which is a virtual machine for x86 Linux binaries (you may know it from WebVM
).
CheerpX is working towards supporting x86-64 binaries, so in the future we could simply adopt WALI as our target (for both Rust and C/C++).
This would have many benefits:
- it is already a supported Rust target
- we could run the same Wasm binary both inside and outside of the browser
- we could join forces in fixing the issues with third-party libraries upstream
We are very excited about this direction and will say more about it in the future!
Try it now
Head to console.browserpod.io to get an API key. You can find the documentation on browserpod.io/docs
.
If you have a Rust project you’d like to see running in a browser tab (especially one you expect to break) we’d genuinely like to hear about it. Come find us on Discord.