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

Ottobre 6, 2025
🎮 Da Inktober a Hacktoberfest: come la creatività può evolvere nel codice

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 […]

Febbraio 7, 2026
Claude Code: Assaggiare il Pair Programming con con un collega artificiale

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 […]

veronicaschembri
Copyright © Veronica Schembri

Privacy Policy
Cookie Policy
💬