트레이트 벡터
Coddy Rust 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 46번째.
Box<dyn Trait>를 통해 단일 변수가 서로 다른 타입을 가질 수 있는 것을 확인했습니다. 진정한 위력은 이것을 컬렉션과 결합하여 단일 벡터에 여러 다른 타입을 저장할 때 발휘됩니다.
일반적인 벡터는 모든 요소가 동일한 타입이어야 합니다. 고양이를 포함하는 Vec<Dog>를 가질 수 없습니다. 하지만 트레이트 객체(trait objects)를 사용하면, 공유된 트레이트를 구현하는 어떤 것이든 담을 수 있는 벡터를 생성할 수 있습니다:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
struct Bird;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
impl Speak for Bird {
fn speak(&self) -> String { String::from("Tweet!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Bird),
];
}
Vec<Box<dyn Speak>> 타입은 "박스화된 트레이트 객체들의 벡터"를 의미합니다. 각 요소는 Speak를 구현하는 무언가를 가리키는 Box이며, 실제 타입은 서로 다를 수 있습니다. 벡터에 값을 추가할 때 각 값을 Box::new()로 감쌉니다.
이 패턴은 UI 컴포넌트, 게임 엔티티 또는 플러그인 시스템과 같이 서로 관련되어 있지만 서로 다른 객체들의 컬렉션을 관리해야 하는 유연한 시스템을 구축하는 데 필수적입니다.
챌린지
쉬움다양한 유형의 알림을 처리할 수 있는 알림 시스템을 만들어 봅시다! 트레이트 객체(trait objects)를 사용하여 이메일, SMS 메시지, 푸시 알림 등 다양한 알림 유형을 하나의 벡터에 저장하는 컬렉션을 만들게 됩니다.
코드는 두 개의 파일로 구성됩니다:
notifications.rs:&self를 매개변수로 받고 전송되는 알림을 설명하는String을 반환하는send메서드를 가진 공개(public)Notify트레이트를 정의합니다. 그런 다음 세 개의 공개 구조체를 만듭니다:Email— 공개recipient필드(String)를 가집니다.send메서드는"Email to: {recipient}"를 반환해야 합니다.Sms— 공개phone필드(String)를 가집니다.send메서드는"SMS to: {phone}"를 반환해야 합니다.Push— 공개device필드(String)를 가집니다.send메서드는"Push to: {device}"를 반환해야 합니다.
main.rs: notifications 모듈을 가져오고, 세 가지 알림 유형을 모두 담을 수 있는Vec<Box<dyn Notify>>타입의 벡터를 생성합니다. 제공된 입력을 사용하여 각 알림 유형을 하나씩 생성하고, 벡터에 순서대로(Email, Sms, Push) 추가한 다음, 벡터의 첫 번째 요소에 대해send()를 호출한 결과를 출력합니다.
여기서 핵심 개념은 Email, Sms, Push가 완전히 다른 구조체임에도 불구하고, Notify 트레이트를 공유하기 때문에 동일한 벡터에 함께 존재할 수 있다는 것입니다. 각 요소는 트레이트 객체를 생성하기 위해 Box::new()로 감싸집니다.
출력은 첫 번째 알림을 보여주어야 합니다:
Email to: {recipient}예를 들어, 입력값이 "alice@example.com", "555-1234", "iPhone-12"인 경우:
Email to: alice@example.com이메일 수신자, 전화번호, 기기 이름이라는 세 가지 입력을 받게 됩니다.
치트 시트
Vec<Box<dyn Trait>>를 사용하면 모든 타입이 동일한 트레이트를 구현하는 한, 단일 벡터에 여러 다른 타입을 저장할 수 있습니다.
일반적인 벡터는 모든 요소가 동일한 타입이어야 하지만, 트레이트 객체(trait objects)를 사용하면 이종 컬렉션(heterogeneous collections)이 가능해집니다:
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 animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
];
}
벡터에 추가할 때 각 요소는 Box::new()로 감싸집니다. 공유된 트레이트를 구현하기만 한다면 실제 타입은 서로 다를 수 있습니다.
직접 해보기
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// 입력 읽기
let mut recipient = String::new();
std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
let recipient = recipient.trim().to_string();
let mut phone = String::new();
std::io::stdin().read_line(&mut phone).expect("Failed to read line");
let phone = phone.trim().to_string();
let mut device = String::new();
std::io::stdin().read_line(&mut device).expect("Failed to read line");
let device = device.trim().to_string();
// TODO: 다양한 알림 유형을 저장할 Vec<Box<dyn Notify>> 생성
// TODO: 입력을 사용하여 Email, Sms, Push 인스턴스 생성
// TODO: Box::new()를 사용하여 순서대로(Email, Sms, Push) 벡터에 추가
// TODO: 첫 번째 요소에서 send()를 호출한 결과 출력
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.