I log sono tornati: in questo post vedremo come combinare tra loro Rust, WebAssembly e React per realizzare un nuovo MVP.
Può anche essere considerato un Proof of concept, ma lo continueremo a chiamare Minimum Viable Product giusto perché MVP mi pare un termine migliore.
Prima che tu decida di continuare nella lettura di questo post, devi però conoscere alcune cose.
Sarò estremamente schietto con te.
Sono sempre molto attento al modo attraverso cui scrivo in questo blog.
È mia premura comunicare informazioni complesse al meglio delle mie capacità, semplificando concetti difficili in paragrafi sintetici e utili.
Tuttavia, esiste un format che viene meno a queste considerazioni basilari.
Sono i Log.
I Log costituiscono il lavoro grezzo che precede una guida.
Anzi, sono molto di più.
Rappresentano il lavoro immenso che spesso si cela dietro un traguardo apparentemente facile.
Sono il sudore, la rabbia, la disperazione, la desolazione, il fastidio, e alcune volte il lieto fine, di un percorso arduo… quello del conseguimento di un risultato.
Per tale motivo i Log sono anche un flusso di coscienza, in cui accanto alle meticolose descrizioni dei singoli passi compiuti, troverai riflessioni, errori e incomprensioni.
La fine è a volte semplicemente il lascito per un futuro me più in gamba, e certamente capace di superare eventuali blocchi limitanti.
Come si traduce tutto questo per te?
Se deciderai di procedere nella lettura, troverai termini che potresti non capire, decisamente poco chiari, possibili errori logici, ritrattamenti e molto altro.
A te la scelta.
Ti aspetto sotto perché so che, in fondo, se stai leggendo queste stesse parole, la decisione l’hai già presa.
MVP: WebAssembly, Rust e React
Questa è la fase iniziale di un progetto per la realizzazione di un MVP che implementa codice Rust, per l’efficienza di esecuzione, in un’applicazione React, per la flessibilità e rapidità di realizzazione, grazie a WebAssembly.
Il contesto di riferimento è quello del Privacy Preserving Machine Learning, anche se vagamente a esso riconducibile.
Rust
Ho installato Rust qualche giorno fa. È un linguaggio di programmazione che ho conosciuto di recente, ma con il quale non ho mai programmato.
Pare abbia un curva di apprendimento ripida, simile a quella di C/C++.
Ha tuttavia il grande vantaggio di poter ottimizzare il codice in termini computazionali, una gestione più sicura della memoria e una vasta collezione di librerie mantenute dalla community.
Lo abbiamo scelto perché Facebook ha sviluppato una libreria di Private Set Intersection molto efficiente.
Intendiamo far funzionare quella libreria in un browser web.
Per questo progetto è tuttavia richiesta una funziona instabile di Rust, ergo per cui dobbiamo passare alla versione nightly.
Lo facciamo con il comando:
rustup update nightly
Che eseguiamo in ambiente linux, in WSL 2.
Ora diamo il secondo comando:
rustup target add wasm32-unknown-unknown --toolchain nightly
In pratica stiamo installando un pacchetto per compilare su quasi ogni macchina e eseguire su quasi ogni macchina. (unknown-unknown)
cargo install --git https://github.com/alexcrichton/wasm-gc
Ora installiamo un altra libreria e abbiamo il GO per installare rustify
yarn add rustify
Lo facciamo all’interno di un progetto react, appena inizializzato con:
npx create-react-app react-psi
Ottimo.
What’s next?
Io direi che per iniziare, dato che il miglior modo di procedere in un progetto in cui stai imparando a fare qualcosa di nuovo è procedere a piccoli passi, potremmo creare due contatori.
Da 0 a 100mld, scritti rispettivamente in plan javascript e in Rust.
Poi confrontiamo i tempi di esecuzione per capire il motivo per cui ci stiamo complicando così tanto la vita.
Passiamo a react
React
React è un framework js per lo sviluppo di applicazioni web.
Definiamo allora una funzione contatore triggerata da un pulsante.
Nel frattempo eliminiamo anche il codice boilerplate.
Dunque, js impiega 400ms circa a raggiungere il miliardo, e 9 secondi ai 10 miliardi.
Per i 100 miliardi non ho intenzione di aspettare più di 10 secondi, quindi per il momento diciamo impieghi molto.
Ora configuriamo Rust.
Prima però inizializzo la repositioy latrimenti git impazzisce con 5k file di cui deve tener traccia per chissà quale motivo.
Riavviato anche Visual Studio Code e ci siamo.
Dobbiamo definire un nuovo file counter.rs
#[no_mangle] pub fn do_loop() -> u32 { let mut i: u32 = 0; let max = 1000000000000; loop { i += 1; if i >= max { return i; } } }
Ora prendiamo il wrapper di rustify e importiamo il file.
Abbiamo fatto cose molto turche, e questo è il risultato:
failed TypeError: WebAssembly.instantiate(): Argument 0 must be a buffer sourc
Risolviamolo.
Flash forward di 8 ore e, dopo una serena dormita, eccoci tornati a realizzare l’impossibile.
Vediamo come correggere il problema.
Dobbiamo capire se l’importazione della funzione di rust non vada a buon fine nel formato errato. Potrebbe essere necessario convertire la funzione in wasm, e poi importarla in react.
Procediamo.
Molto bene.
Avevamo una guida aperta, nelle tab di chrome, relativa a questo stesso task.
È un po lunga, ma penso sia necessario procedere così da avere react predisposto a gestire qualsivoglia funzione di Rust.
Inizializziamo allora una nuova cartella per l’applicazione Rust con:
cargo new rust-utils
Questo comando definisce un file Cargo.toml, da cui verifichiamo le dipendenze e specifichiamo i dettagli del progetto.
Abbiamo bisogno di una libreria chiamata wasm-bindgen, che può compilare il nostro codice in WebAssembly.
Spostiamo quindi il nostro counter all’interno del nuovo progetto, e inseriamo quello che mi pare un decoratore ma che in realtà si chiama macro.
Convertiamo tutto in wasm
WebAssembly: Rust to WASM
Abbiamo bisogno di una libreria chiamata wasm-pack, per creare un’interfaccia tra JS e TS, e Rust.
WebAssembly sarà allora lo spazio in cui il codice Rust, opportunamente convertito, potrà essere eseguito.
Lanciamo allora il comando
cargo install wasm-pack
Subito dopo:
wasm-pack build
Crea una cartella pkg, perché non abbiamo specificato alcun nome nel comando precedente.
Per il momento va bene così.
Aspetta un secondo, non mi sono accorto di un errore.
Nella console pare qualcosa sia andato male.
failed to execute wasm-opt
:
È un bug. Aggiungiamo queste righe al file Cargo.toml e presguiamo.
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-Oz", "--enable-mutable-globals"]
WebPack
Dovremmo esserci.
Ora non ci resta che impostare React affinché carichi e gestisca WebAssembly.
Procediamo.
Aggiungiamo la cartella di build come dipendenza in react, grazie al fatto che sia stato generato anche un file package.json
"wasm": "file:./rust-utils/pkg"
Installiamo la dipendenza.
La importiamo in modo asincrono usando la sintassi con promise.
Ma riceviamo un errore:
./node_modules/wasm/rust_utils_bg.wasm
Module parse failed: magic header not detected
File was processed with these loaders:
* ./node_modules/file-loader/dist/cjs.js
You may need an additional loader to handle the result of these loaders.
Error: magic header not detected
Dobbiamo configurare Webpack per gestire WASM.
Ci serve wasm-loader
Aggiungiamolo come dipendenza dev e.. puf! Lo avevamo già installato. Non so quando.
Ora, non abbiamo un file webpack.
Questo significa due cose:
- dobbiamo crearne uno
- dobbiamo dire a npm o yarn di averlo creato e usarlo per avviare il progetto.
Iniziamo con il creare il file.
Un semplice module exports, con due regole per il parsing dei file wasm.
A queste però ne aggiungiamo una per evitare che i file WASM sia gestiti sia dal file-loader che dal wasm-loader.
Benissimo, mi sono accorto che non avevamo installato wasm-loader come dipendeza dev, ma con un solo trattino avevamo semplicemente fatto stampare la versione di yarn
in pratica anziché
yarn add –dev wasm-loader
ho dato il comandp
yarn add -dev wasm-loader
Comunque, abbiamo fatto un po’ di casino. Puliamo tutto e reinstalliamo da zero le dipendenze.
Il problema non è legato alle dipendenze ma a webpack, più precisaimente carichiamo i file wasm con il lodeader di default anziché wasm-loader.
Tuttavia non è facile fare l’ovverride delle configurazioni di webpack.
Procediamo con più insistenza.
Dopo aver tentato di modificare senza successo la configurazione di webapck con una libreria esterna, abbiamo deciso di procedere con l’espulsione delle configurazioni così da avere più autonomia. Effettuiamo allora le modifiche ripulendo il package.json dalle dipendenze inutilizzate.
E ci siamo riusciti!!!
Il modulo è stato correttamente importato.
Ora, procediamo a usare la nostra funzione counter.
Et voila.
Per contare fino a 10 mld javascript impiega 10 secondi circa, mentre Rust 1ms.
Ora capisci perché abbiamo fatto questa fatica.
Cosa ci resta da fare?
Importare la libreria Private-ID di facebook e far funzionare il protocollo di private set intersection.
Per il momento è tutto.
Per aspera, ad astra.
Un caldo abbraccio, Andrea