디스패치는 특정 함수(메서드)를 호출하기 위해 선택하는 메커니즘입니다. Rust에서는 정적 및 동적 디스패치라는 두 가지 접근 방식이 있습니다.
문제의 역사:
객체 지향 프로그래밍(OOP) 언어에서는 일반적으로 동적 메서드 호출을 위해 vtable(가상 테이블)을 사용합니다. Rust에서는 trait 객체에 대해 비슷한 방식으로 구현됩니다. 즉, 특정 trait를 구현하는 타입 객체에 대한 참조입니다. 정적 디스패치는 제너릭 및 trait bounds를 사용할 때 발생합니다.
문제:
종종 유연성(하나의 인터페이스를 통해 다양한 타입의 객체와 작업할 수 있는 가능성)과 성능(정적 디스패치는 메서드를 인라인화할 수 있음) 중에서 선택해야 합니다. 잘못된 선택은 지나치게 복잡한 제너릭 또는 성능 손실로 이어질 수 있습니다.
해결책:
정적 디스패치는 제너릭 매개변수를 통해 달성됩니다. 이 경우 컴파일러는 각 타입에 대해 별도의 코드를 생성합니다. 동적 디스패치는 함수가 &dyn Trait 또는 Box<dyn Trait> 타입의 인수를 사용할 때 발생함으로, Rust는 전통적인 OOP 언어와 마찬가지로 주소의 vtable에서 메서드를 호출합니다.
코드 예시:
trait Shape { fn area(&self) -> f64; } impl Shape for Circle { fn area(&self) -> f64 { 3.1415 * self.radius * self.radius } } fn print_area(shape: &dyn Shape) { // 동적 디스패치 println!("area = {}", shape.area()); } // 또는 정적으로: fn print_area_static<S: Shape>(shape: &S) { println!("area = {}", shape.area()); }
주요 특징:
dyn Trait는 vtable을 사용합니다 (동적 디스패치)Box<dyn Sized>를 만들 수 있나요?
아니요. dyn Trait는 정의상 unsized이며, 항상 Box, Arc 또는 참조를 사용해야 합니다. 그러나 "Box<dyn Sized>"는 의미가 없습니다. Sized trait는 trait 객체를 가질 수 없습니다.
제너릭 메서드가 있는 trait에 대해 dyn Trait을 사용할 수 있나요?
아니요. 제너릭 메서드가 있는 object-safe trait을 만들 수 없습니다 (혼동하기 쉽습니다!). 컴포지트 타입은 object-safe하지 않습니다:
trait MyTrait { fn foo<T>(&self, x: T); } let x: &dyn MyTrait = ... // 컴파일 오류!
Self 값을 시그니처에서 사용하는 trait에 대해 dyn Trait을 만들 수 있나요?
안 됩니다. 메서드가 Self를 반환하면 (이 미묘한 점을 이해하지 못하는 분들이 많습니다: object safety는 시그니처에 Self가 없어야 합니다; self는 인수에서만 가능하고 반환 값에서는 불가능합니다).
모든 곳에서 인터페이스의 범용성 때문에 dyn Trait을 사용했습니다. 심지어 제너릭으로 대체할 수 있는 tight loop 안에서도 사용했습니다.
장점:
단점:
정적 디스패치는 내부 로직에 적용하고, dyn Trait은 모듈 경계에서만 사용되었습니다.
장점:
단점: