Zero-cost abstractions è un'idea chiave di Rust, che significa che le astrazioni (ad esempio, tipi generici, iteratori, traits) non dovrebbero comportare costi in termini di tempo o memoria rispetto alla scrittura manuale di codice equivalente. In altre parole, il codice di alto livello dopo l'ottimizzazione viene compilato con la stessa efficienza del codice di basso livello.
Rust raggiunge questo tramite il meccanismo della monomorfizzazione: il codice generico viene compilato per ogni tipo specifico separatamente, senza chiamate dinamiche. Gli iteratori e le chiusure scritte su traits/generics vengono ottimizzati dal compilatore in codice tipizzato staticamente diretto, dove tutte le sovrastrutture vengono rimosse.
Esempio di iteratore zero-cost:
let v = vec![1,2,3]; let sum: i32 = v.iter().map(|x| x * 2).sum();
Questo frammento dopo l'ottimizzazione si trasforma quasi in un ciclo manuale:
let mut sum = 0; for x in &v { sum += x * 2; }
Significa che l'astrazione zero-cost in Rust non porterà overhead runtime quando si utilizzano oggetti trait (ad esempio, &dyn Trait)?
Risposta: No! L'overhead runtime appare quando si sceglie dinamicamente un metodo tramite vtable — quando si utilizza dyn Trait invece di funzioni generiche. Lo zero-cost si ottiene solo con astrazioni generiche statiche (monomorfizzate).
Esempio:
trait Speaker { fn speak(&self); } fn say_twice<T: Speaker>(v: T) { v.speak(); v.speak(); } fn say_twice_dyn(v: &dyn Speaker) { v.speak(); v.speak(); } // La prima chiamata viene monomorfizzata, la seconda — tramite vtable
Storia
Storia
Storia
In una libreria di terze parti è stata creata una struttura generica, che veniva restituita come Box<dyn Trait>. Nonostante l'implementazione generica, abbiamo perso lo zero-cost, poiché abbiamo spostato l'astrazione in runtime.