Menu
Coddy logo textTech

트레이트 벡터

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 컴포넌트, 게임 엔티티 또는 플러그인 시스템과 같이 서로 관련되어 있지만 서로 다른 객체들의 컬렉션을 관리해야 하는 유연한 시스템을 구축하는 데 필수적입니다.

challenge icon

챌린지

쉬움

다양한 유형의 알림을 처리할 수 있는 알림 시스템을 만들어 봅시다! 트레이트 객체(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()를 호출한 결과 출력
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨