Sono una principiante assoluta in Rust. Ho deciso di imparare questo linguaggio seguendo il bootcamp di Francesco Ciulla, diviso in due parti: le basi del linguaggio e la costruzione di un vero gioco.
Questo post racconta la mia esperienza e i concetti che mi hanno colpita di più.
Parte 1: Le Fondamenta di Rust
Stack e Heap: Dove Vivono i Dati?
Una delle prime cose che mi ha aperto la mente è capire dove Rust memorizza i dati.
Lo Stack è come una pila di piatti: veloce, ordinato, ma limitato.
I dati hanno dimensione fissa e nota a compile time
Funziona con LIFO (Last In, First Out): l'ultimo piatto messo è il primo che togli (se vuoi scoprirne di piú leggi il mio articolo dove spiego lo stack con Magic The Gathering!)
Quando una funzione termina, i suoi dati vengono automaticamente rimossi
Esempio: numeri interi, booleani, tuple di tipi semplici
Lo Heap è come un magazzino: più spazio, ma serve un "indirizzo" per trovare le cose.
I dati possono crescere o cambiare dimensione
Devi allocare memoria e poi liberarla
Più lento dello stack perché devi seguire puntatori
Esempio: String, Vec, qualsiasi dato dinamico
STACK (veloce, ordinato) HEAP (flessibile, più lento)
┌─────────────────────┐ ┌─────────────────────────┐
│ let x = 42; │ │ "Ciao mondo!" │
│ let y = true; │ ┌────►│ (contenuto della String)│
│ let s = String ─────┼────┘ └─────────────────────────┘
│ (puntatore) │
└─────────────────────┘Code language:JavaScript(javascript)
Rust gestisce tutto questo automaticamente, senza garbage collector. Come? Con l'Ownership.
Le 3 Regole d'Oro dell'Ownership
Questo è IL concetto che rende Rust unico:
Ogni valore ha un solo proprietario (owner)
Può esistere un solo owner alla volta
Quando l'owner esce dallo scope, il valore viene eliminato
let s1 = String::from("ciao");
let s2 = s1; // s1 viene "spostato" in s2// println!("{}", s1); // ERRORE! s1 non è più valido
println!("{}", s2); // OK!Code language:JavaScript(javascript)
All'inizio sembra limitante, ma è proprio questo sistema che garantisce zero memory leak e zero data race a compile time.
Enum: Molto Più Potenti di Quanto Pensi
Gli enum in Rust non sono semplici liste di costanti come in altri linguaggi. Possono contenere dati!
Con match puoi gestire ogni variante in modo esaustivo — il compilatore ti avvisa se dimentichi un caso!
Parte 2: Costruire Crablo
Dopo le basi, Francesco ci ha guidato nella costruzione di Crablo, un clone semplificato di Diablo.
Step 1: Setup Iniziale
Struttura base del progetto con macroquad e la state machine del gioco (Menu → Playing → GameOver).
Step 2: Griglia Isometrica
La magia della vista isometrica: trasformare coordinate 2D in una prospettiva pseudo-3D con una semplice formula matematica.
Step 3: Il Nostro Eroe
Uno stickman disegnato con cerchi e linee. Semplice ma efficace!
Step 4: Input e Target
Click del mouse → conversione coordinate schermo → coordinate griglia. Il gioco inizia a rispondere.
Step 5: Pathfinding BFS
L'algoritmo Breadth-First Search trova il percorso più breve evitando i muri.
Step 6: Movimento
Il player si muove lungo il percorso con un sistema di cooldown per controllare la velocità.
Step 7: I Mostri
Stickman con le corna! Stessa grafica del player ma con un flag enemy: bool.
Step 8: Combattimento
Player attacca i mostri, testo fluttuante del danno che sale e scompare. Ho scoperto retain_mut() per aggiornare e filtrare in un colpo solo.
Step 9: AI dei Mostri
I mostri usano lo stesso BFS per inseguire il player. La distanza Manhattan determina se attaccare (distanza = 1) o avvicinarsi.
Step 10: Gold, Score e Vittoria
Sistema completo: raccolta oro (+100), uccisioni (+50), vittoria quando tutti i mostri sono morti.
Il Risultato Finale
Cosa Mi Ha Colpita di Più
Gli Hint del Compilatore
Rust ha i messaggi di errore più utili che abbia mai visto. Non ti dice solo "c'è un errore", ma ti spiega perché e spesso ti suggerisce la soluzione:
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:4:20
|
2 | let s1 = String::from("ciao");
| -- move occurs because `s1` has type `String`3 | let s2 = s1;
| -- value moved here
4 | println!("{}", s1);
| ^^ value borrowed here after move
|
help: consider cloning the value
|
3 | let s2 = s1.clone();
| ++++++++Code language:JavaScript(javascript)
Le Closure
Funzioni anonime con una sintassi elegante:
// Invece di scrivere una funzione separata...self.monsters.iter().map(|m| (m.x, m.y))
// ^^^^^^^^^^^^^^// closure: prende un mostro, ritorna le sue coordinateCode language:PHP(php)
La sintassi |parametri| espressione è simile alle arrow function di JavaScript ((x) => x * 2), ma le closure di Rust possono anche catturare variabili dall'ambiente circostante.
Sono ovunque in Rust: map(), filter(), retain(), position()…
Dead Loop (e come evitarlo)
Durante l'implementazione del BFS, ho imparato cos'è un dead loop (loop infinito). Senza tracciare le celle già visitate, l'algoritmo continuerebbe a rimbalzare tra celle adiacenti all'infinito:
A → B → A → B → A → B → ...
La soluzione? Una matrice visited che segna ogni cella già esplorata:
let mut visited = [[false; MAP]; MAP];
// Prima di aggiungere una cella alla coda:if !visited[ny][nx] {
visited[ny][nx] = true; // Mai più rivisitata!
queue.push_back((nx, ny));
}Code language:JavaScript(javascript)
Semplice ma fondamentale: ogni cella viene processata una sola volta.
FIFO vs LIFO: Coda vs Stack
Quando ho implementato il BFS, mi sono chiesta: perché usare una coda e non uno stack?
Struttura
Tipo
Esempio
Coda (Queue)
FIFO - First In, First Out
Fila al supermercato: chi arriva prima, esce prima
Stack (Pila)
LIFO - Last In, First Out
Pila di piatti: prendi sempre quello in cima
BFS usa FIFO perché deve esplorare "a onde concentriche":
1 1 1
1 2 2 2 1
1 2 3 3 3 2 1
1 2 2 2 1 ← Prima TUTTI i vicini a distanza 1,
1 1 1 poi TUTTI quelli a distanza 2, ecc.
S
Se usassi uno stack (LIFO), otterresti DFS (Depth-First Search): va "in profondità" seguendo un percorso fino in fondo prima di tornare indietro. Funziona, ma non garantisce il percorso più breve!
Distanza Manhattan
Un altro concetto interessante: perché si chiama "Manhattan"?
Immagina le strade di New York: sono una griglia. Non puoi camminare in diagonale attraverso i palazzi — devi seguire le strade, solo orizzontale o verticale.
Nel gioco la uso per sapere se un mostro è adiacente al player (distanza = 1 → può attaccare!).
HUD: Head-Up Display
Ho scoperto che "HUD" non è solo gergo da videogiochi — viene dall'aviazione! I piloti di caccia hanno display trasparenti sul parabrezza che mostrano dati di volo (altitudine, velocità, bersagli) senza dover guardare in basso.
Nei videogiochi è lo stesso concetto: informazioni vitali (HP, munizioni, minimappa) che "galleggiano" sopra il mondo di gioco, sempre visibili.
// Il nostro HUD mostra HP e punteggio in basso a sinistra
draw_text(&format!("HP: {}", self.hp), 20., screen_height() - 40., 30., BLACK);
draw_text(&format!("SCORE: {}", self.score), 20., screen_height() - 70., 30., BLACK);Code language:PHP(php)
Gli Enum (di nuovo!)
Nel gioco li ho usati per:
Stati del gioco: Menu, Playing, GameOver
Tipi di tile: Wall, Floor
La potenza di match con gli enum rende il codice chiaro e il compilatore ti protegge dai casi dimenticati.
Conclusioni
In due sessioni di bootcamp sono passata da zero a un gioco funzionante. Rust non è facile, ma:
Il compilatore è il tuo migliore amico (anche se sembra severo)
I concetti di ownership e borrowing diventano naturali con la pratica
La community e le risorse (come questo bootcamp) rendono l'apprendimento accessibile
Ogni ottobre, la community tech celebra Hacktoberfest — un evento globale che invita sviluppatori di ogni livello a contribuire a progetti open source su GitHub. L’obiettivo è semplice ma potente: imparare, condividere e collaborare, un commit alla volta. Per anni, ottobre per me significava tutt’altro: Inktober, un mese di sfide artistiche, tracciati vettoriali e curve […]
Da quando uso Claude Code, il mio modo di sviluppare è cambiato. Non tanto perché scrive codice al posto mio, ma perché è diventato il mio pair programming partner: qualcuno con cui discutere scelte architetturali, validare pattern, ragionare su trade-off. Quando lavoro da sola, è facile restare incastrata nelle mie assunzioni. Avere Claude che fa […]