Menu
Coddy logo textTech

트레이트 경계 문법

Coddy Rust 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 40번째.

지금까지 여러분은 트레이트(trait)를 정의하고 이를 구조체(struct)에 구현하는 방법을 배웠습니다. 또한 유연한 코드를 작성하기 위해 제네릭(generics)을 사용하는 방법도 살펴보았습니다. 이제 이 개념들을 결합할 차례입니다. 즉, 트레이트를 사용하여 제네릭 함수가 허용할 수 있는 타입을 제한(constrain)하는 방법을 알아보겠습니다.

매개변수에 대해 특정 메서드를 호출해야 하는 제네릭 함수를 생각해 봅시다. 아무런 제약 조건이 없다면, Rust는 타입 T가 해당 메서드를 가지고 있다는 것을 보장할 수 없습니다:

fn print_info<T>(item: T) {
    println!("{}", item.summarize());  // 오류! T에 summarize()가 없을 수도 있습니다
}

해결책은 트레이트 바운드(trait bound)입니다. 제네릭 매개변수 뒤에 : TraitName을 추가함으로써, Rust에게 T가 해당 트레이트를 구현해야 한다고 알려줍니다:

trait Summary {
    fn summarize(&self) -> String;
}

fn print_info<T: Summary>(item: T) {
    println!("{}", item.summarize());  // 이제 Rust는 이 메서드가 존재한다는 것을 압니다
}

<T: Summary> 구문은 "T는 Summary를 구현하는 모든 타입"으로 읽힙니다. 이제 이 함수는 필요한 동작을 가진 타입만 허용합니다. 만약 Summary를 구현하지 않은 타입을 전달하려고 하면, 컴파일러가 명확한 오류와 함께 이를 거부할 것입니다.

이 패턴은 특정 기능에 여전히 접근하면서도 제네릭 코드를 작성할 수 있게 해주기 때문에 강력합니다. 당신의 함수는 유연하게 유지됩니다. 즉, 어떤 타입과도 작동하지만, 오직 당신이 필요로 하는 동작을 제공하는 타입들하고만 작동합니다.

challenge icon

챌린지

쉬움

트레이트 바운드(trait bounds)를 사용하여 적절한 형식을 갖춘 메시지만 전송할 수 있도록 보장하는 알림 시스템을 만들어 봅시다! 특정 트레이트를 구현하는 모든 타입을 허용하는 제네릭 함수를 생성하여, 트레이트 바운드가 제네릭 매개변수를 어떻게 제한하는지 실습해 볼 것입니다.

코드는 두 개의 파일로 구성됩니다:

  • notification.rs: &self를 매개변수로 받고 String을 반환하는 message 메서드를 가진 공개(public) Notify 트레이트를 정의하세요. 그런 다음 이 트레이트를 구현하는 두 개의 공개 구조체를 만드세요:
    • Email — 공개 subject 필드(String)를 가집니다. message 메서드는 "Email: {subject}"를 반환해야 합니다.
    • SMS — 공개 content 필드(String)를 가집니다. message 메서드는 "SMS: {content}"를 반환해야 합니다.
    마지막으로, Notify 트레이트를 구현하는 모든 타입 T를 허용하는 send_notification이라는 공개 제네릭 함수를 만드세요. 이 함수는 해당 아이템의 message()를 호출한 결과를 출력해야 합니다.
  • main.rs: notification 모듈을 가져와서 제공된 입력을 사용하여 EmailSMS 인스턴스를 모두 생성하세요. 각 인스턴스에 대해 send_notification을 호출하여 제네릭 함수가 Notify를 구현하는 모든 타입과 함께 작동함을 증명하세요.

여기서 핵심은 send_notification이 수신하는 구체적인 타입을 알지 못한다는 점입니다. 단지 해당 타입이 메시지를 생성할 수 있다는 것만 알 뿐입니다. 트레이트 바운드 T: Notify가 이러한 기능을 보장합니다.

출력 결과는 두 알림이 모두 전송되는 것을 보여주어야 합니다:

Email: {subject}
SMS: {content}

예를 들어, 입력값이 Meeting TomorrowOn my way!인 경우:

Email: Meeting Tomorrow
SMS: On my way!

이메일 제목과 SMS 내용이라는 두 개의 입력을 받게 됩니다.

치트 시트

트레이트 바운드(trait bound)는 제네릭 타입 매개변수가 특정 트레이트를 구현하는 타입만 허용하도록 제한합니다.

트레이트 바운드 문법은 제네릭 매개변수 뒤에 : TraitName을 사용합니다:

fn function_name<T: TraitName>(item: T) {
    // 이제 item에서 TraitName의 메서드를 호출할 수 있습니다
}

트레이트 바운드를 사용한 예시:

trait Summary {
    fn summarize(&self) -> String;
}

fn print_info<T: Summary>(item: T) {
    println!("{}", item.summarize());
}

<T: Summary> 표기법은 "T는 Summary를 구현하는 모든 타입"을 의미합니다. 이를 통해 함수는 제네릭을 유지하면서 트레이트 메서드를 호출할 수 있습니다.

직접 해보기

mod notification;

use notification::{Email, SMS, send_notification};

fn main() {
    // 입력 읽기
    let mut subject = String::new();
    std::io::stdin().read_line(&mut subject).expect("Failed to read line");
    let subject = subject.trim().to_string();
    
    let mut content = String::new();
    std::io::stdin().read_line(&mut content).expect("Failed to read line");
    let content = content.trim().to_string();
    
    // TODO: 제목을 포함한 Email 인스턴스 생성
    
    // TODO: 내용을 포함한 SMS 인스턴스 생성
    
    // TODO: email을 매개변수로 send_notification 호출
    
    // TODO: sms를 매개변수로 send_notification 호출
}
quiz icon실력 점검

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

Object Oriented Programming의 모든 레슨