# tinywasm: a WebAssembly runtime that fits in a no_std binary

> tinywasm is an interpreter for WebAssembly written in safe Rust, small enough to run on embedded targets and inside no_std binaries. Its selling points are footprint and predictability, and the cost is that you give up the wider ecosystem and some of the newest proposals.

**explodingcamera/tinywasm** — A tiny WebAssembly runtime written in safe Rust

- Repository: https://github.com/explodingcamera/tinywasm
- Website: https://tinywasm.dev
- Stars: 597 · Forks: 26
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/explodingcamera-tinywasm

## tinywasm is an interpreter, not a JIT

The project describes itself as a tiny WebAssembly runtime written in safe Rust, and the Cargo manifest lists interpreter first in its keywords. That single word tells you most of what you need to know about the execution model. There is no compilation of guest code to native machine code, so there is no per-module code cache, no page permissions to manage and no architecture specific prologue to get wrong. The trade is straightforward: you give up the throughput of a JIT and you get a runtime that behaves the same way on every target Rust supports.

The library is published on crates.io as tinywasm, and the workspace version is 0.11.0. The default branch is not main but next, which tells you something about how the project is developed: the branch you clone is the one the badges track and the one the automated test workflow runs against.

The interesting part for most readers is the footprint. The README claims the crate is small by design while still passing the full WebAssembly 3.0 core test suite, and it states that it can itself compile to WebAssembly, which is the kind of property that matters when your runtime needs to run inside a sandbox that is already running wasm.

## Two external dependencies keep the tree small

The README is specific about what pulls weight. The two main external components are wasmparser, which handles WebAssembly parsing and validation, and postcard, which handles the .twasm archive format. Everything else in the runtime is written in the project itself.

That is a narrow dependency surface for a wasm runtime, and narrow dependencies have a direct effect on how much of the Rust ecosystem you inherit, including its security advisory history. The Cargo manifest confirms the same picture at the workspace level, pinning wasmparser at version 0.261 and postcard appearing through the workspace dependency list for archive support.

Safety is handled the same way, by construction rather than by audit. The project states that the runtime is written entirely in safe Rust, with one documented exception: the optional simd-x86 feature uses unsafe internally for selected x86 SIMD intrinsics. So the default build has no unsafe blocks of its own, and the SIMD path is opt-in and clearly labelled.

## The first call looks like any other Rust API

Adding the runtime to a project is a single dependency line, and the version in the README matches the released version:

```toml
[dependencies]
tinywasm = "0.11.0"
```

The usage example in the README is short enough to read as a whole. It parses a module from bytes, creates a store, instantiates the module against that store, then looks up an exported function and calls it with typed parameters:

```rust
use tinywasm::{ModuleInstance, Store};

// Load a module from bytes
let wasm = include_bytes!("../examples/wasm/add.wasm");
let module = tinywasm::parse_bytes(wasm)?;

// Create a new store
let mut store = Store::default();

// Instantiate the module
let instance = ModuleInstance::instantiate(&mut store, &module, None)?;

// Call an exported function with typed parameters
let func = instance.func::<(i32, i32), i32>(&store, "add")?;
let result = func.call(&mut store, (1, 2))?;

assert_eq!(result, 3);
```

The typed signature in instance.func is the part worth pausing on. A guest function is looked up with its parameter and return types given as Rust generics, so a mismatch between what the module exports and what the host expects is a compile time error rather than a runtime surprise. The examples folder in the repository goes further than this one, with separate files for archives, exceptions, garbage collection, linking, references, resumable calls, host state, threads and WASI p1.

## Feature flags decide how much of the runtime you carry

The Cargo feature list is where the design intent becomes clearest, because it shows what the runtime looks like at different sizes. The full feature enables archive, debug, parallel-parser, parser, state and validate, and it is on by default. std is also on by default, and it is what brings parsing from files and streams, shared memory handles, and blocking atomic wait and notify across stores.

Turn the defaults off and the crate supports no_std + alloc, depending only on libm. That is the configuration for an embedded target, and the repository topics list embedded and no-std among its categories for exactly this reason.

Two features stand out for production use. send makes stores and store-local handles movable across threads, which you need if you want to run modules on a thread pool. portable-atomic supports targets without a native atomic compare-and-swap, which is a real portability concern for the embedded cases the crate targets. Then there are the escape hatches: nightly-tail-calls uses Rust's unstable explicit tail calls for interpreter dispatch and requires a nightly toolchain, and guest-debug exposes module-internal inspection APIs by index.

Engine and engine::Config are the tuning surface. The README says they configure non-default fuel accounting, stack sizing and GC collection thresholds, and a ResourceLimiter can allow, reject or trap memory and table allocation or growth requests as well as GC object allocations. Fuel accounting in particular is the mechanism you reach for when you want to bound execution rather than observe it.

## Precompiled .twasm archives trade validation for startup time

tinywasm can serialize a parsed module into its own version-specific .twasm format, and the benefit is stated plainly: loading an archive skips WebAssembly parsing, validation and optimization. For an application that only ever loads archives, the parser and validator can be removed from the binary by enabling only the archive feature, and the README says that depending on the target and release profile this can produce binaries smaller than 300 KB.

That is the most concrete size claim in the documentation, and it is tied to a specific configuration rather than to the crate in general.

The important part is the warning that follows. Loading a .twasm archive checks the archive header and encoding, but it does not run WebAssembly validation and does not verify the runtime invariants. The instruction is unambiguous: load archives only from trusted sources. For untrusted input, parse a WebAssembly binary with validation enabled. So the size win comes with a trust requirement, and the two paths are not interchangeable.

## Validation on its own does not bound what a module can do

The section on untrusted input is the part of the README that deserves a careful read before you run third party code. Validation is enabled by default through the validate feature, and the guidance is to keep that feature on and leave ParserOptions::validation enabled for modules from untrusted sources. The stated reason is that without validation, parsing can produce modules that violate runtime assumptions and may panic during instantiation or execution.

The next paragraph is the one people skim past. Validation does not limit parsing or execution resources. A perfectly valid module can still allocate until it exhausts memory, loop until it exhausts your time budget, or grow a table without bound. The README's guidance for hosts running untrusted code is to also configure parser::ParseLimits, stack and ResourceLimiter limits, and fuel- or time-budgeted execution as needed.

In other words, the runtime does not give you a sandbox for free. You get a correct interpreter, and you get the building blocks for containment, but the containment is configuration you have to write, and three separate mechanisms have to be set for one threat model to be covered.

## Proposal support stops at phase 4, with a few exceptions

TinyWasm generally implements non-JavaScript core proposals at phase 4 or later, with some implemented earlier. The support table in the README records the version each landed in: import and export of mutable globals, multi-value, non-trapping float-to-int conversions and sign-extension operators all arrived in 0.2.0, bulk memory operations in 0.4.0, reference types in 0.7.0 and fixed-width SIMD in 0.9.0. Tail calls are the next row and the table is cut off in the version of the README available here.

This is a sensible policy for a runtime that has to stay small, and it also means a real boundary for adopters. A guest module compiled for a proposal that is still below phase 4 will not run, and there is no headroom for experimental features. The repository includes a wasm-testsuite dependency, and the claim that the runtime passes the full WebAssembly 3.0 core test suite is the objective measure behind the proposal table.

Worth noting for comparison: the alternative to writing a runtime is to embed one, and the cost profile of that route is a much larger dependency, a larger binary, and typically unsafe code for memory management and JIT compilation. That is the difference tinywasm is drawing.

## Recent tags, an active branch, and a dual licence

Maintenance information is straightforward here. The repository is not archived, the last push was on 2026-10-07, and releases are frequent: v0.11.0 on 2026-09-19, preceded by v0.11.0-beta.0 on 2026-09-16 and v0.10.0 on 2026-07-24. The presence of a beta tag published three days before the final release also suggests the project uses prereleases to let adopters test an upcoming breaking version.

The workspace requires a recent toolchain. The manifest sets edition 2024 and rust-version 1.98, with a rust-toolchain.toml at the repository root pinning what the project develops against. That is a constraint worth checking against your own build environment, particularly for the embedded targets where you may be on a pinned older toolchain.

The licence is MIT OR Apache-2.0, and the repository ships both LICENSE-MIT and LICENSE-APACHE files. That dual scheme is the usual Rust convention: pick one per dependency tree, and the choice propagates to anyone who depends on you. It is permissive in both cases, with no copyleft obligation and no source disclosure requirement, so embedding the runtime in a proprietary product is not a licensing problem. The CHANGELOG.md and CONTRIBUTING.md at the root give you the upgrade and contribution path in the usual form.

## Conclusion

Adopt tinywasm when you need to run WebAssembly inside a constrained binary, on a target without an operating system, or with a hard rule against unsafe Rust, and you can live with a smaller proposal set than a general purpose engine. Do not adopt it as a drop-in for a full WASI environment, for untrusted code without extra limits, or when you need the newest WebAssembly proposals, since the support table stops at phase 4 core proposals and archives bypass validation entirely. Before you commit, check the feature list against what you actually need: with default features off the crate supports no_std + alloc and depends only on libm, and a binary that only loads .twasm archives can drop the parser and validator entirely.

## FAQ

### How do I add tinywasm to a Rust project?

Add the dependency to your Cargo.toml, for example `tinywasm = "0.11.0"`. The version in the README matches the current release, and the crate is published on crates.io.

### Can tinywasm run on a target without an operating system?

Yes. With default features disabled the crate supports no_std + alloc and depends only on libm. The repository lists embedded and no-std among its categories, and there is a portable-atomic feature for targets without a native atomic compare-and-swap.

### Is it safe to run untrusted WebAssembly with tinywasm?

Keep the validate feature enabled and leave ParserOptions::validation on, since parsing without validation can produce modules that violate runtime assumptions and may panic. The README also states that validation does not limit resources, so configure parser::ParseLimits, stack and ResourceLimiter limits, and fuel or time budgets as well.

### What are .twasm archives and when should I use them?

A .twasm archive is a serialized parsed module in a version-specific format, and loading one skips WebAssembly parsing, validation and optimization. The README warns that loading an archive checks the header and encoding but runs no WebAssembly validation, so archives should be loaded only from trusted sources.

### What licence does tinywasm use?

The workspace manifest states MIT OR Apache-2.0, and the repository ships both LICENSE-MIT and LICENSE-APACHE. Both are permissive licences with no source disclosure requirement.

## Sources

- [explodingcamera/tinywasm on GitHub](https://github.com/explodingcamera/tinywasm)
- [License: Apache-2.0](https://github.com/explodingcamera/tinywasm/blob/next/LICENSE)
- [Project website](https://tinywasm.dev)
- [README](https://github.com/explodingcamera/tinywasm/blob/next/README.md)
- [Releases](https://github.com/explodingcamera/tinywasm/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/explodingcamera-tinywasm
