Introduzione alla Programmazione in Rust
Questa e' la home page del corso "Introduzione alla Programmazione in Rust".
Qui troverete l'orario delle lezioni ed il materiale didattico usato durante il corso. Per avere qualche informazione in piu' sui contenuti del corso, si puo' consultare la pagina web della precedente edizione del corso: anno 2022/2023.
Lezioni:
- Prima Lezione: 2025/03/03, Aula 3, 14:30 -> 17:30 (video)
- Seconda Lezione: 2025/03/11, Aula 4, 14:30 -> 17:30 (video)
- Terza Lezione: 2025/03/31, Aula 4, 13:30 -> 16:30 (video prima parte e video seconda parte )
- Tipi di dato strutturati, custom ed algebrici in Rust
- Altri esercizi ed esempi sui tipi di dati composti e custom in Rust
- Quarta Lezione 2025/04/01, Maffi Aula 3, 14:30 -> 17:30 (video prima parte e video seconda parte)
- Ancora sui tipi di dato algebrici in Rust
- Astrazioni a costo 0: introduzione ai trait ed a funzioni / tipi di dato generici
- A proposito di lifetime
- Quinta Lezione 2025/04/10, boyl Aula 1, 14:00 -> 17:00 (video prima parte e video seconda parte)
- Sesta Lezione 2025/04/14, Maffi Aula 14, 14:30 -> 17:30 (video prima parte e video seconda parte)
- Rust avanzato: funzioni, chiusure e thread
- Applicazioni bare-metal (32 bit con MultiBoot o 64 bit con UEFI) in Rust; cenni a cargo; cenni ad unsafe
- Settima Lezione: 2025/04/22, Aula 10, 14:30 -> 16:30
- Note finali (iterator e simili, moduli, come scomporre un programma in piu' compilation unit, ...)
- Informazioni su esame e progetti
- Saluti di fine corso
Esempi di codice:
- Prima di tutto: di' ciao!
- A proposito di safety: il linguaggio C ovviamente non e' memory safe ne' type safe (esempio di accesso fuori da un array o di printf "sbagliata"); il linguaggio C++ puo' essere type safe e memory safe (esempio di accesso fuori da un vettore C++) ma fa check solo a tempo di esecuzione. Il linguaggio Rust invece cerca di fare il maggior numero possibile ti test a tempo di compilazione (esempio su array e su funzioni di stampa in Rust)
- Dichiarazione di variabili in Rust: esempio in cui qualche annotazione sui tipi e' necessaria
- Esempio di funzione
- Esempio di espressione condizionale "if"
- A proposito della semantica di trasferimento (o movimento): esempio di allocazione di una struttura dati, associata ad una singola variabile "p"; il "possesso" della struttura dati puo' passare dalla variabile "p" ad un'altra variabile "p1", ma dopo il passaggio "p" non puo' piu' essere usata per accedere alla struttura dati! Questo vale anche dopo che "p1" viene distrutta... Ma il possesso della struttura dati puo' essere restituito a "p". Per finire, e' possibile anche dare la struttura dati "in prestito"!
- I tipi di dati composti in Rust sono array e tuple; ecco alcuni esempi al riguardo:
- I tipi di dati algebrici si basano su due operazioni fra tipi di dati: prodotto e somma. Ecco alcuni esempi sulle strutture (
struct),che implementano il prodotto fra tipi di dato:
- Ecco invece un esempio di enumerazione, che implementa la somma fra tipi di dati (altra versione di questo esempio). Si noti che le varianti del tipo di dato somma in Rust sono delle strutture; gli esempi precedenti usano empty struct e tuple struct per avere una sintassi simile a quella dei costruttori come funzioni, ma si puo' usare anche la classica sintassi c-like per le strutture.
- Come visto, i tipi dati custom sono destrutturabili tramite pattern matching. Ecco un esempio di pattern matching usato invece nella definizione di variabili
- Esempio su funzioni associate e metodi
- I trait possono essere usati per definire delle proprieta' comuni ad un insieme di tipi di dato. Per esempio, un trait puo' indicare un metodo che i tipi di dato devono implementare. E' anche possibile specificare un'implementazione di default per metodi o funzioni associate.
- Esempio di struttura generica (parametrizzata rispetto a due type variable T e V). Tipi di dato e funzioni generici sono molto utilizzati in Rust; per esempio si consideri il tipo Option. Ecco anche un esempio di funzioni generiche. Si noti che la funzione "
formatme()" pone dei vincoli sui due tipi U e T (che devono implementare il trait "std::fmt::Display"). Senza tali vincoli, non potrebbe invocare la macro "format!()" (come mostrato da questo esempio).
- Esempio di trait dinamico
- Altri esempi riguardo ai trait object (trait dinamici):
- Alcuni esempi sui lifetime. Si noti che specificare esplicitamente i tempi di vita dei riferimenti non e' sempre necessario, perche' in casi ovvi Rust li puo' inferire. Conoscere i tempi di vita dei riferimenti e' pero' fondamentale per evitare che un riferimento sopravviva alla variabile che riferisce. Specificare esplicitamente i tempi di vita e' necessario quando (per esempio) una funzione riceve piu' riferimenti come parametri e ritorna un riferimento. Si vedano anche questo esempio e questo esempio
- Gli smart pointer sono usati da Rust per implementare il paradigma RAII, che permette di deallocare opportunamente la memoria al momento giusto, sensa bisogno di garbage collector.
Box permette al compilatore di inserire la deallocazione della memoria a tempo di compilazione. E' possibile avere un solo puntatore alla memoria allocata, anche se possono esistere piu' riferimenti a tale puntatore, come in questo esempio. Si noti pero' che puo' esistere un solo riferimento mutabile (a puntatori per i quali non esistono altri riferimenti); quindi, questo esempio non compila
Rc permette al runtime del linguaggio di effettuare la deallocazione della memoria usando un reference counter. Usando questo tipo di smart pointer, e' possibile avere piu' puntatori (non mutabili) che puntano alla memoria allocata
RefCell permette di muovere a tempo di esecuzione anche i controlli sui riferimenti (mai prendere un riferimento mutabile quando sono gia' presenti altri riferimenti). Usando il metodo borrow(), e' possibile prendere riferimenti immutabili alla memoria allocata, mentre usando il metodo borrow_mut() e' possibile prendere riferimenti mutabili. Il controllo sui riferimenti e' fatto a runtime
Ecco anche un esempio che combina Rc e RefCell per ottenere piu' puntatori (non riferimenti!) a memoria allocata dinamicamente che puo' essere anche mutabile. Per finire, ecco un esempio in cui RefCell genera un'eccezione a runtime
- Alcuni esempi sulle liste, che mostrano come utilizzare gli smart pointer. Prima di tutto, un esempio di lista immutabile, che usa
Rc; poi, un esempio di lista "tradizionale" (notare l'utilizzo di Option per sopperire al fatto che non esistono puntatori NULL). L'utilizzo "banale" di puntatori Rc pero' fallisce clamorosamente in caso di liste con doppio puntatore... In questo caso, bisogna usare i weak reference (in questo caso, la sintassi appare un po' verbosa, ma puo' essere semplificata anche tramite use)
- Le funzioni in Rust sono entita' denotabili, memorizzabili ed esprimibili; ecco un esempio che mostra alcune di queste proprieta'. Pero' una funzione non puo' catturare variabili non locali (questo perche' una funzione non ha un ambiente associato). Una chiusura invece contiene un ambiente e puo' quindi catturare una variabile non locale. Riguardo al meccanismo utilizzato per catturare le variabili:
- I thread in Rust sono definibili usando una chiusura senza argomenti come corpo del thread. Non e' possibile usare i parametri della chiusura per passare argomenti al thread, ma si possono usare variabili catturate; si noti che tali argomenti dei thread devono essere sempre catturati per movimento/copia, altrimenti il programma non compila. Questo impedisce di condividere variabili fra vari thread, o peggio. Ancora una volta gli smart pointer possono aiutare (versione semplificata)
- Esempio di comunicazione tramite canale ed esempio di condition variable
- Alcuni esempi su strutture dati piu' complesse definite nella libreria standard di Rust:
- Esempio su vettori (da non confondere con gli array, gia' visti
- Array e vettori possono essere gestiti in modo uniforme usando gli slice
- Esempio sulle stringhe. Si noti come l'operatore
+ riceva il secondo argomento per riferimento ed il primo per valore (e quindi "consumi" il primo argomento, rendendolo non piu' utilizzabile). La macro format!() non ha questo problema
- Utilizzo della funzione
parse sulle stringhe (si ricordi come sia necessario aggiungere annotazioni sui tipi). Questa funzione puo' fallire, quindi ritorna valori di tipo Result, che possono complicare il codice che la utilizza. Spesso questo porta ad abusare di unwrap(), ma l'operatore ? permette di risolvere il problema. Usando dynamic object e' possibile evitare di citare specifici errori.
- Esempio riguardo il parsing di argomenti passati a linea di comando, che mostra un esempio di iterator. Su strutture dati di questo tipo si puo' iterare tramite il costrutto
for o tramite un ciclo while che invoca il metodo next(). Alternativamente, si puo' accedere all'ennesimo elemento della struttura (ma si noti che questo consuma gli elementi precedenti)
- Esempi riguardo la visibilita' dei simboli in Rust
Programmazione di sistema:
Esame:
L'esame sara' basato, a scelta dello studente, su una serie di domande orali riguardanti gli argomenti trattati nel corso oppure sulla discussione di un piccolo progetto sviluppato dallo studente.
Nel secondo caso (studenti che intendono sostenere l'esame basandosi sulla discussione di un progetto), il progetto puo' riguardare un qualsiasi argomento visto a lezione; alcuni possibili esempi sono:
- Convertire una semplice applicazione C/C++ (per esempio, una parte di un semplice simulatore ad eventi) in Rust, verificandone poi le performance
- Implementare qualche semplice struttura dati (liste a puntatore semplice o doppio, alberi bilanciati, heap, hash o simili) in un linguaggio a scelta ed in Rust, confrontando poi le performance e gli aspetti di safety
- Sperimentare con programmazione di sistema in Rust
- In modo analogo, sperimentare con l'utilizzo di Rust nei sistemi embedded o per lo sviluppo di applicazioni web
- Fare qualche esperimento (usando codice di test mooolto semplice - linked list o simile) con le tecniche descritte in uno dei seguenti articoli:
- Se uno studente e' interessato all'utilizzo di Rust in altri ambiti, puo' proporre un progetto basato su programmi/librerie Rust di suo interesse
- Lo sviluppo in Rust di applicazioni/esempi/progetti gia' sviluppati in altri linguaggi per altri esami e' un'ulteriore possibilita'
Si raccomanda comunque di contattare il docente prima di cominciare a lavorare sul progetto, per valutarne la difficolta' e la fattibilita'.