Zero-cost abstractions는 Rust의 핵심 아이디어로, 추상화(예: 제네릭 타입, 이터레이터, 트레잇)가 수동으로 작성한 유사 코드에 비해 시간 또는 메모리의 비용을 발생시키지 않아야 한다는 것을 의미합니다. 즉, 최적화된 고수준 코드는 저수준 코드와 동일하게 효율적으로 컴파일됩니다.
Rust는 모노모피제이션 메커니즘을 통해 이를 달성합니다: 제네릭 코드는 각 특정 타입에 대해 별도로 컴파일되며, 동적 호출이 없습니다. 트레잇/제네릭을 사용하여 작성된 이터레이터와 클로저는 최적화 후 컴파일러에 의해 직접적인 강타입 코드로 전개되어 모든 불필요한 래퍼가 제거됩니다.
zero-cost 이터레이터의 예:
let v = vec![1,2,3]; let sum: i32 = v.iter().map(|x| x * 2).sum();
이 코드 조각은 최적화 후 거의 수동 루프로 변환됩니다:
let mut sum = 0; for x in &v { sum += x * 2; }
Rust에서 zero-cost abstraction이 trait 객체(예: &dyn Trait)를 사용할 때 런타임 오버헤드가 발생하지 않음을 의미하나요?
답변: 아니요! 런타임 오버헤드는 vtable을 통한 동적 메서드 선택 시 발생합니다 - dyn Trait 대신 제네릭 함수를 사용할 때입니다. Zero-cost는 오직 정적(모노모르파이즈된) 제네릭 추상화에서만 가능합니다.
예:
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(); } // 첫 번째 호출은 모노모르파이즈되고, 두 번째는 vtable을 통해 호출됩니다.
이야기
이야기
이야기
외부 라이브러리에서 Box<dyn Trait>로 반환되는 제네릭 구조를 생성했습니다. 제네릭 구현에도 불구하고 런타임으로 추상화를 꺼내어 zero-cost를 잃었습니다.