From Zero to Crablo - Il mio viaggio con Rust

Perché Rust

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:

  1. Ogni valore ha un solo proprietario (owner)
  2. Può esistere un solo owner alla volta
  3. 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!

enum Tile {
    Wall,   // Variante semplice
    Floor,  // Variante semplice
}

enum Messaggio {
    Testo(String),                   // Contiene una stringa
    Coordinate(i32, i32),            // Contiene due numeri
    Colore { r: u8, g: u8, b: u8 },  // Contiene una struct
}Code language: JavaScript (javascript)

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?

StrutturaTipoEsempio
Coda (Queue)FIFO - First In, First OutFila al supermercato: chi arriva prima, esce prima
Stack (Pila)LIFO - Last In, First OutPila 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.

A · · ·        Distanza Euclidea (linea retta): ~4.2
· · · ·        Distanza Manhattan (strade): 3 + 3 = 6
· · · ·
· · · B        Formula: |x1-x2| + |y1-y2|

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

Il codice completo è su GitHub.

Grazie a Francesco Ciulla per il bootcamp!


Prossimi passi? Voglio provare ad aggiungere:

  • Generazione procedurale della mappa
  • Diversi tipi di mostri
  • Power-ups

Stay tuned! 🦀

Related Post

Aprile 21, 2026
Ti sei perso tra gli .env? Ecco come ho messo ordine con 1Password

Dal dev locale al deploy in Docker, passando per GitHub Actions. Uno smoke test pratico, con tutti gli inciampi che ho trovato strada facendo. Un mese fa vi ho raccontato come ho messo fine al caos delle chiavi SSH con 1Password (prima puntata qui). Era la parte facile: una volta capito come funziona l'SSH Agent, […]

Aprile 7, 2025
Mentre LLaMA 4 viaggia tra le galassie, io chunko ricette in cucina (con RAG e Streamlit)

Preparatevi a un viaggio nel futuro dell'intelligenza artificiale! Proprio quando pensavo di aver capito come funzionano i modelli linguistici, ecco che Meta decide di sconvolgere tutto con Llama 4. È come se avessi appena imparato a guidare una bicicletta e improvvisamente mi ritrovassi al volante di una navicella spaziale! Meta ha presentato non uno, non […]

veronicaschembri
Copyright © Veronica Schembri

Privacy Policy
Cookie Policy
💬