Executando Rust no Wippy
Crie um componente Rust WebAssembly, registre-o no Wippy e exponha-o por entradas de função, CLI e HTTP.
Classificação: Tutorial executável com uma toolchain externa de componentes Rust. A página fornece WIT, implementação Rust, registro Wippy, fluxo de hash de integridade, comandos, resultados esperados e verificações de falha.
O Que Vamos Construir
Um componente Rust com quatro funções exportadas:
- greet — Recebe um nome e retorna uma saudação
- add — Soma dois inteiros
- fibonacci — Calcula o n-ésimo número de Fibonacci
- list-files — Lista arquivos em um diretório montado
A aplicação Wippy registra esses exports como funções chamáveis, comandos CLI e um endpoint HTTP.
Pré-requisitos
- Runtime Wippy
v0.3.32a. - Toolchain Rust com o target
wasm32-wasip1. - Uma toolchain C funcional. No Linux,
cargo-componenttambém exige bibliotecas de desenvolvimento OpenSSL. - cargo-component 0.21.1, a versão usada neste tutorial.
rustup target add wasm32-wasip1
cargo install cargo-component --version 0.21.1 --locked
Crie o scaffold gerado do componente e os diretórios Wippy:
mkdir rust-wasm-demo
cd rust-wasm-demo
cargo component new --lib demo
mkdir -p app/src/demo/wasm
No PowerShell:
New-Item -ItemType Directory -Path rust-wasm-demo
Set-Location rust-wasm-demo
cargo component new --lib demo
New-Item -ItemType Directory -Path app\src\demo\wasm -Force
cargo component new grava um Cargo.toml, src/lib.rs e arquivo WIT compatíveis e,
mais tarde, gera novamente src/bindings.rs. Mantenha a versão gerada de
wit-bindgen-rt pareada com o cargo-component instalado; a ferramenta descreve essa
interface como experimental e não garante compatibilidade do código gerado entre versões.
Estrutura do Projeto
rust-wasm-demo/
├── demo/ # Rust component
│ ├── Cargo.toml
│ ├── wit/
│ │ └── world.wit # WIT interface
│ └── src/
│ ├── bindings.rs # generated by cargo-component
│ └── lib.rs # implementation
└── app/ # Wippy application
├── wippy.lock
└── src/
├── _index.yaml # Infrastructure
└── demo/
├── _index.yaml # CLI processes
└── wasm/
├── _index.yaml # WASM entries
└── demo_component.wasm # Compiled binary
Passo 1: Criar a Interface WIT
WebAssembly Interface Types (WIT) define o contrato entre o host e o componente guest.
Crie demo/wit/world.wit:
package component:demo;
world demo {
export greet: func(name: string) -> string;
export add: func(a: s32, b: s32) -> s32;
export fibonacci: func(n: u32) -> u64;
export list-files: func(path: string) -> string;
}
Cada export se torna uma função que o Wippy pode chamar.
Passo 2: Implementar em Rust
Mantenha o demo/Cargo.toml gerado. Os metadados de pacote devem apontar para
component:demo, correspondendo ao pacote WIT, e o tipo de crate da biblioteca deve
continuar como cdylib.
Crie demo/src/lib.rs:
#[allow(warnings)]
mod bindings;
use bindings::Guest;
struct Component;
impl Guest for Component {
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn fibonacci(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let next = a + b;
a = b;
b = next;
}
b
}
fn list_files(path: String) -> String {
let mut result = String::new();
match std::fs::read_dir(&path) {
Ok(entries) => {
for entry in entries {
match entry {
Ok(e) => {
let name = e.file_name().to_string_lossy().to_string();
let meta = e.metadata();
let (kind, size) = match meta {
Ok(m) => {
let kind = if m.is_dir() { "dir" } else { "file" };
(kind, m.len())
}
Err(_) => ("?", 0),
};
let line = format!("{:<6} {:>8} {}", kind, size, name);
println!("{}", line);
result.push_str(&line);
result.push('\n');
}
Err(e) => {
let line = format!("error: {}", e);
eprintln!("{}", line);
result.push_str(&line);
result.push('\n');
}
}
}
}
Err(e) => {
let line = format!("cannot read {}: {}", path, e);
eprintln!("{}", line);
result.push_str(&line);
result.push('\n');
}
}
result
}
}
bindings::export!(Component with_types_in bindings);
O módulo bindings é gerado pelo cargo-component a partir da definição WIT.
Passo 3: Compilar o Componente
cd demo
cargo component build --release
Isso produz target/wasm32-wasip1/release/demo.wasm. Copie para sua aplicação Wippy:
mkdir -p ../app/src/demo/wasm
cp target/wasm32-wasip1/release/demo.wasm ../app/src/demo/wasm/demo_component.wasm
No PowerShell:
New-Item -ItemType Directory -Path ..\app\src\demo\wasm -Force
Copy-Item -LiteralPath target\wasm32-wasip1\release\demo.wasm `
-Destination ..\app\src\demo\wasm\demo_component.wasm
Obtenha o hash SHA-256 para verificação de integridade:
sha256sum ../app/src/demo/wasm/demo_component.wasm
No PowerShell, use:
(Get-FileHash ..\app\src\demo\wasm\demo_component.wasm -Algorithm SHA256).Hash.ToLowerInvariant()
Copie os 64 caracteres hexadecimais minúsculos para cada YOUR_HASH_HERE abaixo. O
campo final deve ter o formato sha256:<64-hex-characters>; esse é o hash do binário
copiado, não do código Rust nem do caminho original de compilação.
Passo 4: Aplicação Wippy
Infraestrutura
Crie app/src/_index.yaml:
version: "1.0"
namespace: demo
entries:
- name: gateway
kind: http.service
meta:
comment: HTTP server
addr: ":8090"
lifecycle:
auto_start: true
- name: api
kind: http.router
meta:
comment: Public API router
server: demo:gateway
prefix: /
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: terminal
kind: terminal.host
lifecycle:
auto_start: true
- name: policy
kind: security.policy
meta:
comment: Grants access to mounted filesystems and WASM functions
policy:
actions:
- fs.get
- funcs.call
resources: "*"
effect: allow
Montar um sistema de arquivos em um módulo WASM e chamar uma função WASM são ações protegidas. A política as concede; as entradas que precisam delas a referenciam.
Funções WASM
Crie app/src/demo/wasm/_index.yaml:
version: "1.0"
namespace: demo.wasm
entries:
- name: assets
kind: fs.directory
meta:
comment: Filesystem with WASM binaries
directory: ./src/demo/wasm
- name: greet_function
kind: function.wasm
meta:
comment: Greet function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: greet
pool:
type: inline
- name: add_function
kind: function.wasm
meta:
comment: Add function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: add
pool:
type: inline
- name: fibonacci_function
kind: function.wasm
meta:
comment: Fibonacci function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: fibonacci
pool:
type: inline
Pontos-chave:
- Uma única entrada
fs.directoryfornece o binário WASM. - Múltiplas funções referenciam o mesmo binário com valores de
methoddiferentes. - O campo
hashverifica a integridade do binário no carregamento. - O pool
inlineserializa chamadas por uma instância aquecida. Ele redefine o estado de execução por chamada entre chamadas síncronas; use outro tipo de pool quando precisar de workers concorrentes.
Funções com WASI
A função list-files acessa o sistema de arquivos, então precisa de imports WASI:
- name: list_files_function
kind: function.wasm
meta:
comment: Filesystem listing with WASI mounts
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: list-files
imports:
- wasi:cli
- wasi:io
- wasi:clocks
- wasi:filesystem
wasi:
mounts:
- fs: demo.wasm:assets
guest: /data
pool:
type: inline
A seção wasi.mounts mapeia uma entrada de sistema de arquivos do Wippy para um caminho do guest. Dentro do módulo WASM, /data aponta para o diretório demo.wasm:assets.
Comandos CLI
Crie app/src/demo/_index.yaml:
version: "1.0"
namespace: demo.cli
entries:
- name: wasm_cli_policy
kind: security.policy
policy:
actions:
- fs.get
resources:
- demo.wasm:assets
effect: allow
- name: ls
kind: process.wasm
meta:
comment: List files from mounted WASI filesystem
command:
name: ls
short: List files from mounted directory
security:
actor: {id: demo.cli:ls}
policies: [demo:policy]
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: list-files
imports:
- wasi:cli
- wasi:io
- wasi:clocks
- wasi:filesystem
wasi:
mounts:
- fs: demo.wasm:assets
guest: /data
O bloco meta.command registra o processo como um comando CLI nomeado. O comando greet não precisa de imports WASI já que usa apenas operações com strings. O comando ls precisa de acesso ao sistema de arquivos, então também carrega o contexto de segurança que concede a montagem.
Endpoint HTTP
Adicione ao app/src/demo/wasm/_index.yaml:
- name: http_greet
kind: function.wasm
meta:
comment: Greet exposed via wasi-http transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: greet
transport: wasi-http
pool:
type: inline
- name: http_greet_endpoint
kind: http.endpoint
meta:
comment: HTTP POST endpoint for WASM greet
router: demo:api
method: POST
path: /greet
func: http_greet
O transporte wasi-http mapeia o contexto de requisição/resposta HTTP para argumentos e resultados WASM.
Passo 5: Inicializar e Executar
cd app
wippy init
Executar Comandos CLI
# List available commands
wippy run list
Available commands:
greet Greet someone via WASM (demo.cli:greet)
ls List files from mounted directory (demo.cli:ls)
Run with: wippy run <command>
Os argumentos após o nome do comando são passados para a função exportada como parâmetros string, de modo que cada comando recebe exatamente os argumentos que sua assinatura WIT declara:
# Run greet
wippy run greet World
Hello, World!
# Run ls to list mounted directory
wippy run ls /data
O comando deve imprimir pelo menos demo_component.wasm com o tamanho do arquivo e
encerrar com status 0. O Wippy não imprime payloads de retorno arbitrários de
process.wasm; por isso o exemplo CLI usa a função Rust que grava no stdout WASI.
Executar como Serviço
wippy run
Isso inicia o servidor HTTP na porta 8090. O transporte wasi-http passa o corpo da requisição como o único argumento string da função:
curl -X POST http://localhost:8090/greet -d 'World'
Hello, World!
Chamar a partir de Lua
Funções WASM são chamadas da mesma forma que funções Lua. O processo chamador precisa de funcs.call no destino, o que demo:policy concede:
local funcs = require("funcs")
local greeting, err = funcs.call("demo.wasm:greet_function", "World")
-- greeting: "Hello, World!"
local sum, err = funcs.call("demo.wasm:add_function", 6, 7)
-- sum: 13
local fib, err = funcs.call("demo.wasm:fibonacci_function", 10)
-- fib: 55
Solução de Problemas e Limpeza
- Se
cargo componentfor desconhecido, instale-o e execute novamentecargo component build;cargo buildcomum não gera os mesmos bindings e a mesma saída de componente desta configuração. - A ausência de
src/bindings.rsantes da primeira compilação é esperada. Se o arquivo continuar ausente apóscargo component build, o pacote WIT ou os metadados do componente não foram resolvidos; corrija esse erro de compilação antes de copiar um binário. WASM hash mismatchsignifica que o binário mudou depois do cálculo do digest ou que algum placeholder permanece. Copie novamente o binário release, recalcule o digest e atualize todas as entradas que o referenciam.- Um erro de instanciação de imports significa que o componente importa um perfil de host
omitido pela entrada. Mantenha os imports
wasi:cli,wasi:io,wasi:clocksewasi:filesystemdocumentados nos exemplos de sistema de arquivos. cannot read /datasignifica que o caminho guest emwasi.mountsou sua entrada de sistema de arquivos não corresponde ao registro.- Interrompa o runtime HTTP com Ctrl+C. A saída da compilação Rust permanece em
demo/target/; remova esse diretório e o arquivo.wasmcopiado para limpar os artefatos gerados.
Próximos Passos
- Visão Geral WASM — Visão geral do runtime WebAssembly
- Funções WASM — Referência de configuração de funções
- Processos WASM — Referência de configuração de processos
- Funções Host — Imports WASI disponíveis
- Referência CLI — Documentação de comandos CLI