동적 디스패치
Coddy Rust 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 61개 중 45번째.
<T: Summary>와 같은 트레이트 제약과 함께 제네릭을 사용하면 Rust는 컴파일 시점에 정확한 타입을 결정합니다. 이를 정적 디스패치라고 합니다. 컴파일러가 사용하는 각 구체 타입에 대해 특수화된 코드를 생성하기 때문입니다. 빠르지만 한 가지 제한이 있습니다. 변수는 하나의 특정 타입만 보유할 수 있습니다.
런타임에 서로 다른 타입을 저장할 수 있는 단일 변수가 필요하다면 어떻게 해야 할까요? 이때 트레이트 객체가 등장합니다. Box<dyn Trait>를 사용하면 해당 트레이트를 구현하는 모든 타입을 저장할 수 있습니다.
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
fn main() {
let animal: Box<dyn Speak> = Box::new(Dog);
println!("{}", animal.speak()); // "Woof!"
let animal: Box<dyn Speak> = Box::new(Cat);
println!("{}", animal.speak()); // "Meow!"
}
dyn 키워드는 동적 디스패치를 나타냅니다. 즉, Rust는 어떤 메서드를 호출할지 컴파일 시점이 아니라 런타임에 조회합니다. 트레이트 객체는 크기가 알려져 있지 않기 때문에 Box가 필요합니다. Box는 고정된 크기의 포인터를 제공합니다.
이렇게 생각해 보세요. 제네릭은 "타입 T와 함께 작동합니다"라고 말하는 반면, 트레이트 객체는 "이 작업을 수행할 수 있는 무엇이든과 함께 작동합니다"라고 말합니다. 그 대가로 같은 변수를 통해 다양한 타입을 처리할 수 있는 유연성을 얻는 대신 약간의 런타임 비용이 발생합니다.
챌린지
쉬움동적 디스패치의 강력한 기능을 보여 주는 차량 대여 시스템을 만들어 봅시다! 대여 가능한 모든 차량이 수행할 수 있는 작업을 정의하는 트레이트를 만든 다음, Box<dyn Trait>를 사용하여 서로 다른 차량 유형을 동일한 변수에 저장합니다.
코드를 두 개의 파일로 구성합니다:
vehicle.rs:&self를 받고String을 반환하는rental_info메서드를 가진 publicRentable트레이트를 정의합니다. 그런 다음 두 개의 public 구조체를 만듭니다:Car: publicmodel필드(String)를 가집니다. 해당rental_info는Car: {model}을 반환해야 합니다.Bike: publicbrand필드(String)를 가집니다. 해당rental_info는Bike: {brand}를 반환해야 합니다.
main.rs: vehicle 모듈을 가져오고 입력값을 사용하여 동적 디스패치를 보여 줍니다. 먼저Car를 담는Box<dyn Rentable>변수를 만들고 대여 정보를 출력한 다음, 동일한 변수에Bike를 담도록 다시 할당하고 대여 정보를 다시 출력합니다.
여기서 핵심은 Box<dyn Rentable> 유형의 단일 변수가 Car 또는 Bike 중 하나를 담을 수 있다는 점입니다. Rust는 런타임에 호출할 rental_info 메서드를 알아냅니다. 이것이 바로 동적 디스패치가 작동하는 방식입니다!
출력에는 두 차량의 정보가 모두 표시되어야 합니다:
Car: {model}
Bike: {brand}예를 들어 입력값이 Tesla Model 3 및 Trek인 경우:
Car: Tesla Model 3
Bike: Trek두 개의 입력값, 즉 차량의 모델과 자전거의 브랜드를 받습니다.
직접 해보기
mod vehicle;
use vehicle::{Rentable, Car, Bike};
fn main() {
// 입력 읽기
let mut car_model = String::new();
std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
let car_model = car_model.trim().to_string();
let mut bike_brand = String::new();
std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
let bike_brand = bike_brand.trim().to_string();
// TODO: Car을 담는 Box<dyn Rentable> 변수 생성
// rental_info() 메서드를 사용하여 대여 정보 출력
// TODO: 같은 변수를 Bike를 담도록 재할당
// 대여 정보를 다시 출력
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 Rust 컴파일러