Il tuo primo modulo
Cos’è un modulo Numax?
Un modulo Numax è un file .wasm con una funzione esportata chiamata run.
Quando esegui nx run your_module.wasm, il runtime carica il file,
collega la host API, e chiama run() una volta. Storage, networking, sync - tutto opzionale, tutto nelle tue mani.
Qualsiasi linguaggio che compila in WASM può essere un modulo Numax. Questa pagina mostra Rust (con e senza SDK), C, C++, e un’anteprima di come saranno Go e Python.
Cosa ti serve
Numax già compilato dal Quickstart. Se non l’hai fatto:
git clone https://github.com/GianIac/numaxcd numaxcargo build --releaseexport NX=./target/release/nxRust - con nx-sdk
Il modo raccomandato. L’SDK avvolge tutte le import raw dell’host in funzioni Rust normali.
Step 1 - Crea il crate
cargo new --lib my_modulecd my_moduleApri Cargo.toml e sostituisci tutto con:
[package]name = "my_module"version = "0.1.0"edition = "2024"
[lib]crate-type = ["cdylib"]
[dependencies]nx-sdk = { path = "../crates/nx-sdk" }
[profile.release]lto = trueopt-level = "z"codegen-units = 1panic = "abort"
[workspace]Due cose da notare:
crate-type = ["cdylib"]- dice a Rust di produrre un file.wasminvece di una libreria Rust normale. Senza questo,cargo buildproduce un.rlibche Numax non può caricare.nx-sdk = { path = "../crates/nx-sdk" }- l’SDK guest. Avvolge le import raw dell’host (db_get,host_log_v2,gcounter_inc, …) in funzioni Rust normali, così non devi mai toccare FFI direttamente.
Step 2 - Scrivi il modulo
Apri src/lib.rs e sostituisci tutto con:
use nx_sdk::log;
#[unsafe(no_mangle)]pub extern "C" fn run() { log("Ciao dal mio primo modulo Numax!");}Riga per riga:
use nx_sdk::log- importa la funzionelogdall’SDK. Internamente chiamahost_log_v2sull’host Numax, che stampa sul terminale.#[unsafe(no_mangle)]- dice al compilatore Rust di mantenere il nome della funzione così com’è nel.wasmcompilato. Senza questo, Rust manglerebbe il nome (es._ZN9my_module3runE) e Numax non troverebbe l’entry point.pub extern "C"- esporta la funzione con la C ABI così WASM la espone correttamente.run()- il nome che Numax cerca. Puoi mettere qualsiasi logica dentro.
Step 3 - Compila ed esegui
cargo build --release --target wasm32-unknown-unknowncd ..export WASM=my_module/target/wasm32-unknown-unknown/release/my_module.wasm$NX run $WASMwasm32-unknown-unknown è il target Rust per WASM puro - niente OS, niente WASI, solo un
binario .wasm. Numax fornisce le funzioni host a runtime.
Output:
[guest] Ciao dal mio primo modulo Numax!Il prefisso [guest] viene aggiunto dall’host per distinguere i log del modulo dai log del runtime.
Rust - log formattati e storage locale
Log formattati
La funzione log() accetta una &str semplice. Per output formattato usa nx_log! - funziona
esattamente come println! ma passa per l’host:
use nx_sdk::{log, nx_log};
#[unsafe(no_mangle)]pub extern "C" fn run() { log("Modulo avviato."); nx_log!("1 + 1 = {}", 1 + 1);}Storage locale
Leggi e scrivi dati persistenti con nx_sdk::db. I dati scritti qui vivono nel datastore
locale del nodo (sled su disco) e non vengono replicati - ogni nodo ha la sua copia.
use nx_sdk::{db, nx_log};
#[unsafe(no_mangle)]pub extern "C" fn run() { // Scrivi un valore db::set("my_key", b"ciao numax").unwrap();
// Rileggilo match db::get("my_key") { Ok(Some(bytes)) => nx_log!("Letto: {}", String::from_utf8_lossy(&bytes)), Ok(None) => nx_log!("Chiave non trovata."), Err(e) => nx_log!("Errore: {:?}", e), }
// Cancellalo db::delete("my_key").unwrap();}Il datastore persiste tra un’esecuzione e l’altra. Per ripartire da zero, elimina la cartella
del datastore (default: ./nx-data). Per dati che devono essere consistenti su più nodi,
usa nx_sdk::crdt::* invece di db::*.
Esempio completo: hello_sdk
C
Niente SDK. Importi le funzioni host manualmente tramite il namespace nx usando
__attribute__((import_module)) e __attribute__((import_name)).
Le stringhe vengono passate come coppie puntatore + lunghezza.
Ti serve clang con supporto al target WASM. Su macOS: brew install llvm. Su Linux: apt install clang.
__attribute__((import_module("nx")))__attribute__((import_name("host_log_v2")))extern int host_log_v2(const char* ptr, int len);
__attribute__((import_module("nx")))__attribute__((import_name("db_set")))extern int db_set( const char* key_ptr, int key_len, const char* val_ptr, int val_len);
__attribute__((export_name("run")))void run() { const char msg[] = "Hello from C guest!"; host_log_v2(msg, sizeof(msg) - 1);
const char* key = "hello"; const char* val = "numax"; db_set(key, 5, val, 5);
const char done[] = "db_set ok"; host_log_v2(done, sizeof(done) - 1);}Build:
clang \ --target=wasm32-wasip1 \ -O3 \ -nostdlib \ -Wl,--no-entry \ -Wl,--export=run \ -Wl,--allow-undefined \ -o guest.wasm \ src/guest.cEsegui:
$NX run ./guest.wasmOutput:
[guest] Hello from C guest![guest] db_set okEsempio completo: guest_c
C++
Come C, ma C++ manglerebbe i nomi delle funzioni internamente. Usa export_name("run") per
mantenere il simbolo WASM esportato stabile indipendentemente da quello che fa il compilatore.
__attribute__((import_module("nx")))__attribute__((import_name("host_log_v2")))extern int host_log_v2(const char* ptr, int len);
__attribute__((import_module("nx")))__attribute__((import_name("db_set")))extern int db_set( const char* key_ptr, int key_len, const char* val_ptr, int val_len);
__attribute__((export_name("run")))void run() { const char msg[] = "Hello from C++ guest!"; host_log_v2(msg, sizeof(msg) - 1);
const char key[] = "hello"; const char val[] = "numax-cpp"; db_set(key, sizeof(key) - 1, val, sizeof(val) - 1);
const char done[] = "db_set ok"; host_log_v2(done, sizeof(done) - 1);}Build:
clang++ \ --target=wasm32-wasip1 \ -O3 \ -nostdlib \ -Wl,--no-entry \ -Wl,--export=run \ -Wl,--allow-undefined \ -o guest.wasm \ src/guest.cppOutput:
[guest] Hello from C++ guest![guest] db_set okEsempio completo: guest_cpp
Go (anteprima)
Go può compilare in WASM tramite GOARCH=wasm GOOS=wasip1. Lo stesso contratto si applica:
esporta una funzione chiamata run, importa le funzioni host dal namespace nx.
//go:build wasm
package main
import "unsafe"
//go:wasmimport nx host_log_v2func hostLog(ptr *byte, len int32) int32
func logStr(s string) { b := []byte(s) hostLog(&b[0], int32(len(b)))}
//go:export runfunc run() { logStr("Hello from Go guest!")}
func main() {}Build:
GOARCH=wasm GOOS=wasip1 go build -o guest.wasm main.go$NX run ./guest.wasmEsempio Go completo in arrivo.
Python (anteprima)
Python può targetizzare WASM tramite py2wasm.
Il contratto host è lo stesso: una funzione run esportata, import dell’host dal namespace nx.
# Concettuale - esempio completo in arrivo
def run(): log("Hello from Python guest!")Esempio Python completo in arrivo.
Altri esempi in arrivo
Go, Python, AssemblyScript, Zig - se compila in WASM, gira su Numax. Altri esempi sono in arrivo.
Nel frattempo, sfoglia tutto quello già disponibile nella directory degli esempi.
Ricompila dopo ogni modifica
# Rustcargo build --release --target wasm32-unknown-unknown
# C / C++./build.sh # oppure build.bat su Windows
$NX run $WASMAltri esempi in arrivo
Go, Python, AssemblyScript, Zig - se compila in WASM, gira su Numax. Altri esempi sono in arrivo.
Se vuoi richiedere un esempio o contribuirne uno direttamente, apri una issue o una PR - è un piacere.
Nel frattempo, sfoglia tutto quello già disponibile nella directory degli esempi.
Prossimi passi
- Rendilo distribuito - Quickstart: 5 minuti
- Esplora l’SDK completo:
nx_sdk::crdt,nx_sdk::net,nx_sdk::system,nx_sdk::time - Sfoglia la directory degli esempi