Zero-cost abstractions to kluczowa idea Rust, oznaczająca, że abstrakcje (np. typy ogólne, iteratory, traits) nie powinny generować kosztów czasowych ani pamięciowych w porównaniu do ręcznego pisania podobnego kodu. Oznacza to, że kod wysokiego poziomu po optymalizacji kompiluje się tak samo efektywnie, jak kod niskiego poziomu.
Rust osiąga to dzięki mechanizmowi monomorfizacji: kod ogólny jest kompilowany dla każdego konkretnego typu osobno, bez dynamicznych wywołań. Iteratory i zamknięcia pisane na traits/generics po optymalizacji są rozwijane przez kompilator do bezpośredniego, sztywno typowanego kodu, w którym usunięte są wszystkie zbędne opakowania.
Przykład zero-cost iteratora:
let v = vec![1,2,3]; let sum: i32 = v.iter().map(|x| x * 2).sum();
Ten fragment po optymalizacji przekształca się w zasadzie w ręczną pętlę:
let mut sum = 0; for x in &v { sum += x * 2; }
Czy oznacza to, że zero-cost abstraction w Rust gwarantuje brak overheadu czasowego przy używaniu obiektów trait (np. &dyn Trait)?
Odpowiedź: Nie! Overhead czasu wykonania pojawia się przy dynamicznym wyborze metody poprzez vtable — gdy używane jest dyn Trait zamiast funkcji ogólnych. Zero-cost osiągane jest tylko przy statycznych (monomorfizowanych) abstrakcjach ogólnych.
Przykład:
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(); } // Pierwsze wywołanie jest monomorfizowane, drugie — przez vtable
Historia
Historia
Historia
W zewnętrznej bibliotece utworzono strukturę genericy, którą zwracano jako Box<dyn Trait>. Pomimo realizacji ogólnej stracono zero-cost, ponieważ przeniesiono abstrakcję do czasu wykonania.