HTML CSS JAVASCRIPT SQL MYSQL PYTHON JAVA PHP C C++ C# PACKET TRACER CYBERSECURITY REACT TYPESCRIPT NODE.JS GIT & GITHUB DATA SCIENCE BOOTSTRAP GO KOTLIN JQUERY DJANGO VUE ANGULAR MONGODB POSTGRESQL SASS NUMPY PANDAS SCIPY DSA RUST SWIFT BASHXMLW3.CSSANGULARJSAIAWSPROGRAMMAZIONEEXCEL WORD POWERPOINT
Corso gratuito

Corso Rust in italiano

Il linguaggio di sistemi più amato: memoria sicura senza garbage collector, performance paragonabile a C/C++. Impara ownership, borrowing, struct, enum e le principali strutture dati. Con esempi pratici ed esercizi.

Introduzione a Rust

Rust è un linguaggio di programmazione di sistemi progettato per garantire sicurezza della memoria, alta performance e concorrenza sicura — senza garbage collector. È stato votato il linguaggio più amato dagli sviluppatori per otto anni consecutivi sul sondaggio di Stack Overflow.

CaratteristicaDescrizione
Sicurezza memoriaNessun null pointer, nessun dangling pointer, garantiti a compile-time
PerformanceVeloce come C/C++, zero overhead astrazioni
OwnershipSistema unico per la gestione della memoria senza GC
ConcorrenzaThread sicuri garantiti dal compilatore
EcosistemaCargo (gestore pacchetti), crates.io (repository)

Rust è usato da Mozilla (Firefox), Microsoft (Windows), Amazon (AWS), Google, Meta e Linux Kernel per le parti critiche in termini di performance e sicurezza.

💡 Il punto di forza di Rust è che il compilatore ti impedisce di scrivere codice non sicuro prima ancora che il programma giri. Questo elimina intere categorie di bug comuni in C/C++.

Installazione e primo progetto

Installazione di Rust (rustup)
# Installa rustup (gestore di versioni di Rust)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Verifica installazione
rustc --version      # es. rustc 1.77.0
cargo --version      # es. cargo 1.77.0
Creare ed eseguire un progetto
# Crea un nuovo progetto con Cargo
cargo new mio_progetto
cd mio_progetto

# Struttura creata:
# mio_progetto/
#   Cargo.toml       ← metadati e dipendenze
#   src/
#     main.rs        ← codice sorgente

# Compila ed esegui
cargo run

# Solo compilazione
cargo build

# Build ottimizzata per produzione
cargo build --release
Il tuo primo programma Rust
fn main() {
    println!("Ciao, mondo!");
    println!("2 + 3 = {}", 2 + 3);
}
💡 println! con il punto esclamativo è una macro, non una funzione. Le macro in Rust si riconoscono dal ! finale.

Sintassi base

Rust ha una sintassi familiare per chi conosce C, C++ o Java, con alcune caratteristiche proprie.

Struttura base di un programma
// Questo è un commento
/* Commento
   su più righe */

fn main() {
    // Il punto di ingresso è sempre main()
    let x = 5;           // immutabile per default
    let mut y = 10;      // mut = modificabile
    y = 20;

    println!("x = {x}");
    println!("y = {y}");

    // Blocchi: l'ultima espressione senza ; è il valore
    let z = {
        let a = 3;
        a * 2           // nessun ;  →  valore del blocco = 6
    };
    println!("z = {z}");
}
Output formattato
fn main() {
    let nome = "Alice";
    let eta = 30;
    let pi = 3.14159;

    println!("Nome: {nome}");
    println!("Età: {eta}");
    println!("Pi greco: {pi:.2}");       // 2 decimali
    println!("{:>10}", "destra");        // allineato a destra
    println!("{:0>5}", 42);              // "00042"
    println!("{:b}", 255);              // binario: 11111111
    println!("{:x}", 255);              // esadecimale: ff
}
⚠️ In Rust le variabili sono immutabili per default. Devi aggiungere mut esplicitamente se vuoi modificarle. Questo ti protegge da modifiche accidentali.

Variabili e tipi di dato

Rust è tipizzato staticamente: ogni variabile ha un tipo noto a compile-time. Spesso il compilatore lo inferisce automaticamente.

CategoriaTipoEsempio
Interi con segnoi8, i16, i32, i64, i128, isizelet x: i32 = -5;
Interi senza segnou8, u16, u32, u64, u128, usizelet n: u8 = 255;
Virgola mobilef32, f64let pi: f64 = 3.14;
Booleanoboollet ok: bool = true;
Caratterecharlet c: char = 'A';
Dichiarazione di variabili
fn main() {
    // Inferenza di tipo
    let x = 42;           // i32 (default)
    let y = 3.14;         // f64 (default)
    let attivo = true;    // bool
    let lettera = 'R';    // char

    // Tipo esplicito
    let grande: i64 = 1_000_000_000;   // _ per leggibilità
    let piccolo: u8 = 255;

    // Costanti (MAIUSCOLO per convenzione)
    const MAX_PUNTI: u32 = 100_000;

    println!("{x} {y} {attivo} {lettera}");
    println!("{grande} {piccolo} {MAX_PUNTI}");
}
Shadowing: ridichiarare una variabile
fn main() {
    let x = 5;
    let x = x + 1;          // shadowing: nuova variabile x
    let x = x * 2;
    println!("{x}");        // 12

    // Lo shadowing permette anche di cambiare tipo
    let spazio = "   ";
    let spazio = spazio.len();  // ora è usize, non &str
    println!("{spazio}");   // 3
}
🎯 Il shadowing è diverso dal mut: con shadowing crei una nuova variabile (puoi anche cambiare tipo), con mut modifichi la stessa variabile (il tipo rimane fisso).

Operatori

Operatori aritmetici e di confronto
fn main() {
    let a = 10;
    let b = 3;

    // Aritmetici
    println!("{}", a + b);   // 13
    println!("{}", a - b);   // 7
    println!("{}", a * b);   // 30
    println!("{}", a / b);   // 3  (divisione intera tra interi)
    println!("{}", a % b);   // 1  (modulo)

    let x: f64 = 10.0;
    let y: f64 = 3.0;
    println!("{:.2}", x / y); // 3.33

    // Confronto (restituiscono bool)
    println!("{}", a > b);    // true
    println!("{}", a == b);   // false
    println!("{}", a != b);   // true
    println!("{}", a >= 10);  // true
}
Operatori logici e bit a bit
fn main() {
    let vero = true;
    let falso = false;

    println!("{}", vero && falso);   // false (AND)
    println!("{}", vero || falso);   // true  (OR)
    println!("{}", !vero);           // false (NOT)

    // Operatori bit a bit
    let a: u8 = 0b1100;   // 12
    let b: u8 = 0b1010;   // 10
    println!("{:b}", a & b);   // 1000 (8)  AND
    println!("{:b}", a | b);   // 1110 (14) OR
    println!("{:b}", a ^ b);   // 0110 (6)  XOR
    println!("{:b}", a << 1);  // 11000 (24) shift sinistro
}

if / else / match

if e else
fn main() {
    let numero = 7;

    if numero < 5 {
        println!("piccolo");
    } else if numero < 10 {
        println!("medio");    // stampato
    } else {
        println!("grande");
    }

    // if come espressione (restituisce un valore)
    let descrizione = if numero % 2 == 0 { "pari" } else { "dispari" };
    println!("{numero} è {descrizione}");
}
match — la switch di Rust
fn main() {
    let voto = 8;

    let giudizio = match voto {
        10          => "Eccellente",
        8 | 9       => "Ottimo",        // più pattern con |
        6 | 7       => "Sufficiente",
        1..=5       => "Insufficiente", // range inclusivo
        _           => "Voto non valido" // default (wildcard)
    };
    println!("{giudizio}");

    // match su tuples
    let punto = (0, 1);
    match punto {
        (0, 0) => println!("Origine"),
        (x, 0) => println!("Sull'asse X: {x}"),
        (0, y) => println!("Sull'asse Y: {y}"),
        (x, y) => println!("({x}, {y})"),
    }
}
💡 Il match di Rust è esaustivo: il compilatore ti obbliga a coprire tutti i casi. Questo elimina i bug da casi non gestiti.

Cicli: loop, while, for

loop — ciclo infinito con break
fn main() {
    let mut contatore = 0;

    let risultato = loop {
        contatore += 1;
        if contatore == 10 {
            break contatore * 2;   // break restituisce un valore
        }
    };
    println!("Risultato: {risultato}");   // 20
}
while e for
fn main() {
    // while
    let mut n = 3;
    while n > 0 {
        println!("{n}...");
        n -= 1;
    }
    println!("Via!");

    // for su un range
    for i in 1..=5 {
        print!("{i} ");       // 1 2 3 4 5
    }
    println!();

    // for su una collezione
    let frutti = ["mela", "pera", "kiwi"];
    for frutto in &frutti {
        println!("{frutto}");
    }

    // con indice
    for (i, frutto) in frutti.iter().enumerate() {
        println!("{i}: {frutto}");
    }
}

Funzioni

In Rust le funzioni si dichiarano con fn. Il tipo dei parametri e del valore di ritorno è obbligatorio. L'ultima espressione senza punto e virgola è il valore di ritorno.

Funzioni: parametri e ritorno
fn somma(a: i32, b: i32) -> i32 {
    a + b       // nessun "return" né ";" → valore di ritorno
}

fn saluta(nome: &str) -> String {
    format!("Ciao, {nome}!")
}

fn minmax(v: &[i32]) -> (i32, i32) {
    let mut min = v[0];
    let mut max = v[0];
    for &n in v {
        if n < min { min = n; }
        if n > max { max = n; }
    }
    (min, max)    // restituisce una tupla
}

fn main() {
    println!("{}", somma(3, 7));          // 10
    println!("{}", saluta("Alice"));      // Ciao, Alice!

    let numeri = vec![4, 1, 9, 2, 7];
    let (min, max) = minmax(&numeri);
    println!("min={min} max={max}");     // min=1 max=9
}
Funzioni ricorsive
fn fattoriale(n: u64) -> u64 {
    if n <= 1 { 1 } else { n * fattoriale(n - 1) }
}

fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2)
    }
}

fn main() {
    println!("{}", fattoriale(10));   // 3628800
    println!("{}", fibonacci(10));    // 55
}

Stringhe

Rust ha due tipi di stringa: &str (slice di stringa, immutabile, borrowed) e String (stringa heap-allocated, modificabile, owned).

&str vs String
fn main() {
    // &str: letterale stringa, immutabile
    let s1: &str = "Ciao, mondo!";

    // String: allocata sull'heap, modificabile
    let mut s2 = String::from("Ciao");
    s2.push_str(", Rust!");    // appendi
    s2.push('!');              // appendi un char

    println!("{s1}");
    println!("{s2}");

    // Conversioni
    let s3 = s1.to_string();       // &str → String
    let s4: &str = &s2;            // String → &str (slicing)

    // Concatenazione
    let s5 = String::from("Hello, ");
    let s6 = String::from("world!");
    let s7 = s5 + &s6;             // s5 viene spostato (moved)
    println!("{s7}");
}
Metodi utili sulle stringhe
fn main() {
    let s = String::from("  Ciao, Rust!  ");

    println!("{}", s.trim());              // "Ciao, Rust!"
    println!("{}", s.to_uppercase());
    println!("{}", s.to_lowercase());
    println!("{}", s.contains("Rust"));    // true
    println!("{}", s.replace("Rust", "mondo"));
    println!("{}", s.len());               // lunghezza in byte

    // Spezzare una stringa
    let csv = "alfa,beta,gamma";
    let parti: Vec<&str> = csv.split(',').collect();
    println!("{:?}", parti);   // ["alfa", "beta", "gamma"]

    // Unire un vettore in stringa
    let unita = parti.join(" - ");
    println!("{unita}");       // alfa - beta - gamma
}

Ownership (Proprietà)

L'ownership è il meccanismo centrale di Rust per gestire la memoria senza garbage collector. Ogni valore ha un unico proprietario; quando il proprietario esce dallo scope, la memoria viene liberata automaticamente.

Tre regole fondamentali

  1. Ogni valore ha un solo proprietario
  2. Quando il proprietario esce dallo scope, il valore viene eliminato
  3. Può esserci solo un proprietario alla volta
Move: trasferimento di proprietà
fn main() {
    let s1 = String::from("ciao");
    let s2 = s1;    // s1 viene SPOSTATO in s2

    // println!("{s1}"); // ERRORE: s1 non è più valido!
    println!("{s2}");   // OK

    // Clone: copia profonda esplicita
    let s3 = String::from("mondo");
    let s4 = s3.clone();   // copia indipendente
    println!("{s3} {s4}"); // entrambi validi

    // I tipi scalari (i32, bool, f64...) si COPIANO automaticamente
    let x = 5;
    let y = x;             // copia, non move
    println!("{x} {y}");   // entrambi validi
}
Ownership nelle funzioni
fn prendi(s: String) {   // s diventa proprietario
    println!("{s}");
}                           // s viene eliminato qui

fn restituisce(s: String) -> String {
    s                      // ritorna la proprietà al chiamante
}

fn main() {
    let s1 = String::from("ciao");
    prendi(s1);
    // println!("{s1}"); // ERRORE: s1 è stato spostato nella funzione

    let s2 = String::from("mondo");
    let s3 = restituisce(s2);
    println!("{s3}");       // OK: la proprietà è tornata a s3
}
💡 Spostare la proprietà ad ogni chiamata di funzione è scomodo. Per questo esiste il borrowing: puoi prestare un valore senza cederne la proprietà.

Borrowing e riferimenti

Il borrowing permette di usare un valore senza prenderne la proprietà, tramite i riferimenti (&). I riferimenti possono essere immutabili (molti contemporaneamente) o mutabili (uno solo alla volta).

Riferimenti immutabili
fn lunghezza(s: &String) -> usize {
    s.len()    // prendiamo in prestito s, non lo possediamo
}

fn main() {
    let s = String::from("ciao mondo");

    // Passiamo un riferimento: s rimane valida dopo la chiamata
    let len = lunghezza(&s);
    println!("'{s}' ha {len} caratteri");

    // Possiamo avere molti riferimenti immutabili contemporaneamente
    let r1 = &s;
    let r2 = &s;
    println!("{r1} e {r2}");   // OK
}
Riferimenti mutabili
fn aggiungi(s: &mut String) {
    s.push_str(", mondo");
}

fn main() {
    let mut s = String::from("ciao");
    aggiungi(&mut s);
    println!("{s}");     // ciao, mondo

    // REGOLA: solo UN riferimento mutabile alla volta
    let r1 = &mut s;
    // let r2 = &mut s; // ERRORE: due riferimenti mutabili!
    println!("{r1}");

    // Non si possono mescolare immutabile e mutabile
    let s2 = String::from("hello");
    let r_imm = &s2;
    // let r_mut = &mut s2; // ERRORE: già in prestito come immutabile
    println!("{r_imm}");
}
Tipo di riferimentoQuanti contemporaneamenteModifica
&T (immutabile)IllimitatiNo
&mut T (mutabile)Uno solo
🎯 Queste regole sui riferimenti, applicate a compile-time dal borrow checker, eliminano le race condition e i dangling pointer prima ancora che il programma venga eseguito.

Array e Vettori

Array — dimensione fissa

Array (lunghezza fissa, stack)
fn main() {
    // Array: tipo [T; N], dimensione fissa nota a compile-time
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    let zeri = [0; 10];    // 10 zeri

    println!("{}", arr[0]);         // 1
    println!("{}", arr.len());      // 5
    println!("{:?}", arr);          // [1, 2, 3, 4, 5]

    // Iterazione
    for n in arr {
        print!("{n} ");
    }
    println!();
}

Vec — vettore dinamico

Vec<T> (lunghezza dinamica, heap)
fn main() {
    // Vettore: cresce e si riduce dinamicamente
    let mut v: Vec<i32> = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    println!("{:?}", v);    // [1, 2, 3]

    // Macro vec! per creare con valori iniziali
    let mut nomi = vec!["Alice", "Bob", "Charlie"];
    nomi.push("Diana");

    println!("{}", v[0]);           // 1
    println!("{}", v.len());        // 3
    v.pop();                        // rimuove l'ultimo
    println!("{:?}", v);            // [1, 2]

    // Slicing
    let slice = &v[0..2];
    println!("{:?}", slice);

    // Iterazione
    for n in &v {
        print!("{n} ");
    }
}
💡 Usa array quando la dimensione è fissa e nota a compile-time (più efficiente, allocato in stack). Usa Vec quando la dimensione può variare a runtime.

Tuple

Le tuple raggruppano valori di tipi diversi. Hanno dimensione fissa e si accede agli elementi tramite indice puntato (.0, .1, ...).

Tuple: utilizzo e destructuring
fn main() {
    let persona: (&str, i32, f64) = ("Alice", 30, 1.68);

    // Accesso per indice
    println!("Nome: {}", persona.0);   // Alice
    println!("Età: {}",  persona.1);   // 30
    println!("Alt: {}",  persona.2);   // 1.68

    // Destructuring (scomposizione)
    let (nome, eta, altezza) = persona;
    println!("{nome} ha {eta} anni ed è alta {altezza}m");

    // Funzione che restituisce più valori con tuple
    fn dividi(a: i32, b: i32) -> (i32, i32) {
        (a / b, a % b)
    }
    let (quoziente, resto) = dividi(17, 5);
    println!("{quoziente} resto {resto}");   // 3 resto 2

    // Tupla vuota (unit type)
    let vuota: () = ();
    println!("{:?}", vuota);   // ()
}

HashMap

La HashMap<K, V> memorizza coppie chiave-valore con accesso medio O(1). Va importata da std::collections.

HashMap: operazioni di base
use std::collections::HashMap;

fn main() {
    let mut voti: HashMap<String, i32> = HashMap::new();

    // Inserimento
    voti.insert(String::from("Alice"), 9);
    voti.insert(String::from("Luca"), 7);
    voti.insert(String::from("Marco"), 8);

    // Lettura
    if let Some(v) = voti.get("Alice") {
        println!("Voto di Alice: {v}");   // 9
    }

    // Contiene la chiave?
    println!("{}", voti.contains_key("Luca"));   // true

    // Iterazione
    for (nome, voto) in &voti {
        println!("{nome}: {voto}");
    }

    // Rimuovi
    voti.remove("Marco");
    println!("{}", voti.len());   // 2
}
entry API — inserisci se non esiste
use std::collections::HashMap;

fn main() {
    let testo = "ciao mondo ciao rust ciao";
    let mut conteggi: HashMap<&str, u32> = HashMap::new();

    for parola in testo.split_whitespace() {
        // entry: se la chiave esiste aggiorna, altrimenti inserisce 0
        let contatore = conteggi.entry(parola).or_insert(0);
        *contatore += 1;
    }

    println!("{:?}", conteggi);
    // {"ciao": 3, "mondo": 1, "rust": 1}
}

Struct

Le struct sono tipi di dato custom che raggruppano campi correlati. Puoi implementare metodi tramite blocchi impl.

Struct e metodi
#[derive(Debug)]
struct Rettangolo {
    larghezza: f64,
    altezza: f64,
}

impl Rettangolo {
    // Funzione associata (factory, senza self)
    fn quadrato(lato: f64) -> Rettangolo {
        Rettangolo { larghezza: lato, altezza: lato }
    }

    // Metodo (con &self)
    fn area(&self) -> f64 {
        self.larghezza * self.altezza
    }

    fn perimetro(&self) -> f64 {
        2.0 * (self.larghezza + self.altezza)
    }

    fn e_quadrato(&self) -> bool {
        self.larghezza == self.altezza
    }
}

fn main() {
    let r = Rettangolo { larghezza: 5.0, altezza: 3.0 };
    println!("Area: {}", r.area());           // 15.0
    println!("Perimetro: {}", r.perimetro()); // 16.0
    println!("{:?}", r);                       // debug print

    let q = Rettangolo::quadrato(4.0);
    println!("È quadrato: {}", q.e_quadrato()); // true
}

Enum

Gli enum di Rust sono molto più potenti di quelli di altri linguaggi: ogni variante può contenere dati. Combinati con match, gestiscono situazioni complesse in modo sicuro.

Enum con dati e match
#[derive(Debug)]
enum Forma {
    Cerchio(f64),                    // contiene il raggio
    Rettangolo(f64, f64),            // larghezza, altezza
    Triangolo { base: f64, h: f64 }, // campi nominati
}

impl Forma {
    fn area(&self) -> f64 {
        match self {
            Forma::Cerchio(r)               => std::f64::consts::PI * r * r,
            Forma::Rettangolo(l, a)         => l * a,
            Forma::Triangolo { base, h }    => 0.5 * base * h,
        }
    }
}

fn main() {
    let forme = vec![
        Forma::Cerchio(3.0),
        Forma::Rettangolo(4.0, 5.0),
        Forma::Triangolo { base: 6.0, h: 4.0 },
    ];
    for f in &forme {
        println!("{:?} → area {:.2}", f, f.area());
    }
}
Option e Result: enum della libreria standard
fn divisione(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 { None } else { Some(a / b) }
}

fn main() {
    // Option<T>: Some(valore) oppure None
    match divisione(10.0, 2.0) {
        Some(r) => println!("Risultato: {r}"),
        None    => println!("Divisione per zero!"),
    }

    // Metodi comodi su Option
    let r = divisione(10.0, 0.0).unwrap_or(0.0);
    println!("{r}");   // 0.0

    // Result<T, E>: Ok(valore) oppure Err(errore)
    let s = "42";
    match s.parse::<i32>() {
        Ok(n)  => println!("Numero: {n}"),
        Err(e) => println!("Errore: {e}"),
    }
}
💡 In Rust non esiste null. Invece di null pointer, si usa Option<T>: sei obbligato a gestire esplicitamente il caso "nessun valore", eliminando i NullPointerException a runtime.

Esercizi pratici

🏋️ Esercizio 1 — Calcolatore di media

Scrivi una funzione che prenda un Vec<f64> e restituisca Option<f64> con la media, oppure None se il vettore è vuoto.

Soluzione Esercizio 1
fn media(numeri: &[f64]) -> Option<f64> {
    if numeri.is_empty() {
        return None;
    }
    Some(numeri.iter().sum::<f64>() / numeri.len() as f64)
}

fn main() {
    let v = vec![1.0, 2.0, 3.0, 4.0, 5.0];
    match media(&v) {
        Some(m) => println!("Media: {m}"),   // 3.0
        None    => println!("Vettore vuoto"),
    }
    println!("{:?}", media(&vec![]));         // None
}
🏋️ Esercizio 2 — Contatore parole

Usa una HashMap per contare quante volte ogni parola appare in una stringa. Stampa le parole in ordine di frequenza decrescente.

Soluzione Esercizio 2
use std::collections::HashMap;

fn conta_parole(testo: &str) -> Vec<(&str, usize)> {
    let mut mappa: HashMap<&str, usize> = HashMap::new();
    for parola in testo.split_whitespace() {
        *mappa.entry(parola).or_insert(0) += 1;
    }
    let mut v: Vec<_> = mappa.into_iter().collect();
    v.sort_by(|a, b| b.1.cmp(&a.1));  // decrescente
    v
}

fn main() {
    let t = "ciao mondo ciao rust ciao mondo";
    for (parola, n) in conta_parole(t) {
        println!("{parola}: {n}");
    }
}
🏋️ Esercizio 3 — Struct Studente

Crea una struct Studente con nome, età e voti (Vec<f64>). Implementa un metodo media_voti e un metodo promosso che restituisce true se la media è ≥ 6.

Soluzione Esercizio 3
struct Studente {
    nome: String,
    eta: u8,
    voti: Vec<f64>,
}

impl Studente {
    fn new(nome: &str, eta: u8, voti: Vec<f64>) -> Self {
        Studente { nome: nome.to_string(), eta, voti }
    }

    fn media_voti(&self) -> f64 {
        if self.voti.is_empty() { return 0.0; }
        self.voti.iter().sum::<f64>() / self.voti.len() as f64
    }

    fn promosso(&self) -> bool {
        self.media_voti() >= 6.0
    }
}

fn main() {
    let s = Studente::new("Alice", 17, vec![7.0, 8.0, 6.5, 9.0]);
    println!("{}: media {:.1}, promosso: {}",
        s.nome, s.media_voti(), s.promosso());
}

E ora?

Complimenti! Hai completato il corso Rust. Ora conosci le basi del linguaggio più sicuro e performante dell'ecosistema moderno: sintassi, tipi, controllo di flusso, ownership, borrowing e le principali strutture dati.

Cosa hai imparato:
  • Variabili immutabili per default, shadowing, tipi scalari
  • if/else, match esaustivo, cicli loop/while/for
  • Funzioni, closures e riferimenti
  • Ownership e borrow checker: memoria sicura senza GC
  • Stringhe (&str vs String), array, Vec, tuple, HashMap
  • Struct con metodi, enum con dati, Option e Result

I prossimi passi in Rust: closures e iteratori, trait, generici, error handling avanzato, lifetimes e la programmazione asincrona con Tokio.

Vai al corso C++ → Torna ai Corsi 📅 Prenota una lezione