Nel linguaggio Rust, la gestione della memoria è tradizionalmente considerata uno dei problemi più complessi della programmazione a basso livello. Prima dell'arrivo di Rust, molti linguaggi richiedevano una gestione manuale della memoria (come C/C++), portando a perdite di memoria e corruzione dei dati. Rust affronta il problema in modo diverso: collezioni come Vec<T> utilizzano una strategia di gestione della memoria automatica e sicura, controllando i momenti di allocazione, ridistribuzione (ridimensionamento) e rilascio della memoria attraverso un sistema di possesso e prestito.
Problema consiste nel fatto che la maggior parte dei linguaggi astrae troppo i dettagli dell'allocatore (GC) o rende il programmatore responsabile di tutto (malloc/free). Nel caso degli array dinamici, è estremamente importante monitorare perdite di memoria e fuoriuscite dai limiti dell'array, oltre a non violare il possesso.
Soluzione in Rust: automazione tramite astrazioni sicure. Vec<T> allocca memoria sull'heap, aumenta le dimensioni dinamicamente (di solito con una crescita esponenziale) e libera tutto al termine della visibilità (RAII).
Esempio di codice:
fn main() { let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.push(3); // L'aggiunta provoca un aumento delle dimensioni e una ridistribuzione della memoria println!("Vector: {:?}", v); // Alla fine di main, la memoria viene liberata automaticamente }
Caratteristiche chiave:
Vec<T> allocca memoria in anticipo e la ridistribuisce quando necessarioQual è la complessità della crescita dell'array quando si aggiungono elementi a Vec?
Di solito, la complessità di push è ammortizzata O(1), tuttavia quando l'array si riempie, viene allocata una nuova area di memoria (di circa il doppio delle dimensioni), e tutti gli elementi vengono copiati. Questo momento è l'unica eccezione in cui l'operazione diventa O(n).
Cosa succede se si tenta di accedere a un elemento fuori dall'intervallo tramite v[index]?
L'uso delle parentesi quadre provoca un panico in caso di overflow. È necessario utilizzare il metodo .get(), che restituisce Option e consente di gestire l'errore in modo sicuro.
let element = v.get(10); // None, se l'indice non esiste
È possibile utilizzare un riferimento a un elemento Vec dopo un possibile ridimensionamento del vettore?
No, dopo il ridimensionamento del vettore (ad esempio, tramite push in caso di overflow), tutta la memoria può essere spostata, e i riferimenti precedenti diventano non validi — si verifica un errore di compilazione (o comportamento indefinito nel blocco unsafe, se li usi manualmente).
Un sviluppatore implementa una cache di messaggi basata su Vec<T> e restituisce riferimenti agli elementi. Dopo una nuova inserzione, si verifica una ridistribuzione della memoria, e tutti i riferimenti esistenti diventano "pendenti". E si verifica il crash dell'applicazione.
Vantaggi:
Svantaggi:
Si utilizza o l'identificazione interna degli elementi (indici/chiavi + controllo di validità), oppure vengono restituiti solo copie/valori immutabili, non è consentito mantenere riferimenti a lungo termine agli elementi Vec.
Vantaggi:
Svantaggi: