{"id":19562691,"url":"https://github.com/jamsocket/wasmbox","last_synced_at":"2025-04-27T00:31:52.387Z","repository":{"id":57697595,"uuid":"496038975","full_name":"jamsocket/wasmbox","owner":"jamsocket","description":"Turns running Rust code into a serializable data structure.","archived":false,"fork":false,"pushed_at":"2022-11-08T12:20:37.000Z","size":3897,"stargazers_count":24,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-24T11:46:48.104Z","etag":null,"topics":["wasm","webassembly"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamsocket.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-05-25T01:15:22.000Z","updated_at":"2025-04-12T21:02:31.000Z","dependencies_parsed_at":"2023-01-22T05:03:34.094Z","dependency_job_id":null,"html_url":"https://github.com/jamsocket/wasmbox","commit_stats":null,"previous_names":["jamsocket/wasmbox","drifting-in-space/wasmbox"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamsocket%2Fwasmbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamsocket%2Fwasmbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamsocket%2Fwasmbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamsocket%2Fwasmbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamsocket","download_url":"https://codeload.github.com/jamsocket/wasmbox/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251072279,"owners_count":21532004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["wasm","webassembly"],"created_at":"2024-11-11T05:15:22.774Z","updated_at":"2025-04-27T00:31:51.679Z","avatar_url":"https://github.com/jamsocket.png","language":"Rust","readme":"# WasmBox\n\n[![GitHub Repo stars](https://img.shields.io/github/stars/drifting-in-space/wasmbox?style=social)](https://github.com/drifting-in-space/wasmbox)\n[![crates.io](https://img.shields.io/crates/v/wasmbox.svg)](https://crates.io/crates/wasmbox)\n[![docs.rs](https://img.shields.io/badge/docs-release-brightgreen)](https://docs.rs/wasmbox/)\n[![Rust](https://github.com/drifting-in-space/wasmbox/actions/workflows/rust.yml/badge.svg)](https://github.com/drifting-in-space/wasmbox/actions/workflows/rust.yml)\n\nWasmBox turns running Rust code into a serializable data structure.\n\nIt does this by compiling it to WebAssembly and running it in a sandbox. To snapshot the running code, it serializes the sandbox's linear memory, which contains the entire heap of the program.\n\n**WasmBox is new and experimental.** Before relying on it in production code, feel free to open an issue and we can discuss 🙂.\n\n## Interface\n\nWasmBox has two components: the host environment and the guest module. The host environment is the program that interacts with the WasmBox from the outside. The guest module is the program that runs *inside* the WasmBox. The guest module is a separate Rust compiler artifact, compiled to target `wasm32-wasi`.\n\nThe two components interact through bidirectional, typed communication provided by WasmBox. Both synchronous and asynchronous interfaces are provided for developing the guest module.\n\nTo use the asynchronous interface, create a function with the signature `async fn run(ctx: WasmBoxContext\u003cString, String\u003e`, and decorate it with the `#[wasmbox]` annotation.\n\nThe following example implements a trivial stateful WasmBox guest module which stores counter state internally. It waits for input from the host environment. When it recieves the inputs `\"up\"` or `\"down\"` from the host environment, it modifies the counter state internally and publishes it back to the host environment.\n\n```rust,no_run\nuse wasmbox::prelude::*;\n\n#[wasmbox]\nasync fn run(ctx: WasmBoxContext\u003cString, String\u003e) {\n    let mut c = 0;\n    loop {\n        let message = ctx.next().await;\n        match message.as_ref() {\n            \"up\" =\u003e c += 1,\n            \"down\" =\u003e c -= 1,\n            _ =\u003e continue,\n        }\n        ctx.send(format!(\"value={}\", c));\n    }\n}\n```\n\nThe `\u003cString, String\u003e` attributes of `WasmBoxContext` are the types of data passed into and out of the WasmBox, respectively. `ctx.next()` returns a value of the first type, and `ctx.send()` expects a value of the second type. If you are writing your own host environment, you can use any [(de)serializable](https://serde.rs/) type here, **as long as the pair of types is the same on both the host environment and the guest module**. Since the guest module is loaded in dynamically at runtime, this can't be enforced by the compiler, so it's up to you to ensure.\n\nThe demonstration host environment provided by `wasmbox-cli` only supports `\u003cString, String\u003e`, so that's what we use here.\n\n#### Compiling guest modules\n\nGuest modules should have the following in their `Cargo.toml`:\n\n```text\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n```\n\nThey should be compiled with the target `wasm32-wasi` like so:\n\n```text\ncargo build --release --target=wasm32-wasi\n```\n\nYou might have to install the `wasm32-wasi` target (e.g. using `rustup`).\n\nLook for a `.wasm` file under `target/wasm32-wasi/release`.\n\n### Host environment\n\nThe host environment is always the same (synchronous) interface, regardless of whether the guest module is using the asynchronous or synchronous interface.\n\nConstructing a host environment (`WasmBoxHost`) requires two things: the module to load, and a callback to use for receiving messages from the guest module. The module can either be passed in as a `.wasm` file, or as a pre-compiled module.\n\nSee `wasmbox-cli` for an example of implementing a host environment.\n\n```rust,no_run\nuse wasmbox_host::WasmBoxHost;\nuse anyhow::Result;\n\nfn main() -\u003e Result\u003c()\u003e {\n    let mut mybox: WasmBoxHost\u003cString, String\u003e = WasmBoxHost::from_wasm_file(\"path/to/some/module.wasm\",\n        |st: String| println!(\"guest module says: {}\", st))?;\n\n    // Send some messages into the box:\n    mybox.message(\u0026\"The guest module will receive this message.\".into());\n    mybox.message(\u0026\"And this one.\".into());\n\n    // Turn the state into a serializable object.\n    let state = mybox.snapshot_state()?;\n    \n    // Or, serialize directly to disk:\n    mybox.snapshot_to_file(\"snapshot.bin\")?;\n\n    // We can interact more with the box:\n    mybox.message(\u0026\"Pretend this message has a side-effect on the box's state.\".into());\n\n    // And then restore the state, undoing the last side-effect.\n    mybox.restore_snapshot(\u0026state)?;\n\n    // Or, restore directly from disk:\n    mybox.restore_snapshot_from_file(\"snapshot.bin\")?;\n\n    Ok(())\n}\n```\n\n### Synchronous Guest Interface\n\nRather than writing an async function to implement a guest, you can implement a `trait` and use the `#[wasmbox_sync]` macro.\n\nEach WasmBox is constructed with a call to `init`. Each message from the host is passed through a call to the trait's `message` function. To pass messages back to the host, a boxed `callback` function is provided in `init`.\n\nBoth the `init` function and `message` functions are allowed to call the callback, and may do so multiple times.\nIn order to call the callback from `message`, you can store it in the type itself.\n\n```rust,no_run\nuse wasmbox::prelude::*;\n\n#[wasmbox_sync]\nstruct Counter {\n    count: u32,\n    callback: Box\u003cdyn Fn(String) + Send + Sync\u003e,\n}\n\nimpl WasmBox for Counter {\n    type Input = String;\n    type Output = String;\n\n    fn init(callback: Box\u003cdyn Fn(Self::Output) + Send + Sync\u003e) -\u003e Self\n    where\n        Self: Sized,\n    {\n        Counter { count: 0, callback }\n    }\n\n    fn message(\u0026mut self, input: Self::Input) {\n        match input.as_ref() {\n            \"up\" =\u003e self.count += 1,\n            \"down\" =\u003e self.count -= 1,\n            _ =\u003e return\n        }\n\n        (self.callback)(format!(\"value={}\", self.count));\n    }\n}\n```\n\n## CLI Tool\n\nA CLI tool is provided for loading and interacting with guest modules. It relays messages to and from the guest module over `stdin` and `stdout`. It only supports guest modules that have the types `\u003cString, String\u003e`, since `stdin` and `stdout` deal with string data.\n\nEach line is treated as a separate message and relayed to the guest module, except for two special commands. `!!snapshot` takes a snapshot of the guest module and saves it to disk, printing the name of the resulting file. `!!restore \u003cfilename\u003e` restores the guest module state from one of these snapshots.\n\n## Safety\n\nThis module uses `unsafe` a lot, in particular within the WASM code. The host also uses unsafe when loading a pre-compiled module, which can lead to arbitrary code execution. Pre-compiled modules are safe **only** if you can be sure that they were created by wasmtime/cranelift.\n\n## Limitations\n\n- It's likely to be slower than native code, because it uses WebAssembly.\n- To provide a deterministic environment, access to anything outside the sandbox is blocked. The system clock is mocked to create a deterministic (but monotonically increasing) clock. Random entropy is not random, but comes from a seeded pseudo-random number generator.\n- To avoid unnecessary repetition, the state does not include the program module itself; it is up to the caller to ensure that the same WASM module that created a snapshot is running when the snapshot is restored.\n- Currently, the `WasmBoxHost` environment owns *everything* about the WebAssembly environment, including things which could be shared between instances. This is inefficient if you want to run many instances of the same module, for instance.\n- Probably lots of other things.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamsocket%2Fwasmbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamsocket%2Fwasmbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamsocket%2Fwasmbox/lists"}