Rust 트레이트
트레이트(trait)는 타입이 구현할 수 있는 동작의 묶음을 정의해요. 다른 언어의 인터페이스와 조금 비슷해요. Rust는 트레이트로 “이 타입은 X를 할 수 있다”를 표현하고, 제네릭 페이지에서 본 트레이트 바운드도 바로 트레이트로 동작해요.
트레이트 정의하기
섹션 제목: “트레이트 정의하기”trait Speak { fn speak(&self) -> String;}이 코드는 “Speak를 구현하는 타입은 String을 돌려주는 speak 메서드를 꼭 제공해야 한다”는 뜻이에요. 트레이트 자체는 약속일 뿐이에요. 어떤 타입이 구현하기 전까지는 아무 일도 하지 않아요.
트레이트 구현하기
섹션 제목: “트레이트 구현하기”trait Speak { fn speak(&self) -> String;}
struct Dog;struct Cat;
impl Speak for Dog { fn speak(&self) -> String { String::from("멍멍!") }}
impl Speak for Cat { fn speak(&self) -> String { String::from("야옹!") }}
fn main() { let dog = Dog; let cat = Cat;
println!("{}", dog.speak()); println!("{}", cat.speak());}실행 결과:
멍멍!야옹!Dog와 Cat은 서로 관계없는 타입이지만, 둘 다 Speak를 구현했으니 둘 다 .speak()를 호출할 수 있어요. 구조체 페이지에서 본 impl 블록과 같은 패턴이에요. impl Speak for Dog는 “Dog가 Speak 트레이트를 이렇게 지킨다”고 말하는 것뿐이에요.
트레이트를 함수 매개변수로 쓰기
섹션 제목: “트레이트를 함수 매개변수로 쓰기”진짜 좋은 점은 여기 있어요. 트레이트를 구현한 타입이라면 무엇이든 받는 함수를 만들 수 있고, 정확히 어떤 타입인지는 신경 쓰지 않아도 돼요.
trait Speak { fn speak(&self) -> String;}
struct Dog;impl Speak for Dog { fn speak(&self) -> String { String::from("멍멍!") }}
fn announce(animal: &impl Speak) { println!("동물이 말해요: {}", animal.speak());}
fn main() { let dog = Dog; announce(&dog);}animal: &impl Speak는 “Speak를 구현한 타입이면 무엇이든 괜찮고, 그게 어떤 타입인지는 알 필요가 없다”는 뜻이에요. Speak를 구현하는 Bird 타입을 새로 추가해도, announce는 아무것도 고치지 않고 그대로 쓸 수 있어요.
이미 써 본 트레이트
섹션 제목: “이미 써 본 트레이트”구조체 페이지의 #[derive(Debug)]를 기억하나요? Debug 자체가 트레이트예요. derive는 여러분이 손으로 구현을 쓰는 대신 자동으로 만들어 줄 뿐이에요. 자주 만나게 될 다른 트레이트로는 Clone(.clone()용), PartialEq(==용), PartialOrd(<, >용. 제네릭 페이지에서 트레이트 바운드로 쓰였죠)가 있어요.
기본 구현
섹션 제목: “기본 구현”트레이트는 메서드의 기본 본문을 제공할 수 있어요. 구현하는 타입은 그것을 그대로 쓰거나 덮어쓸(override) 수 있어요.
trait Greet { fn name(&self) -> String;
fn greet(&self) -> String { format!("안녕하세요, {}님!", self.name()) }}Greet를 구현하는 타입은 name()만 정의하면 돼요. greet()는 직접 덮어쓰지 않는 한 공짜로 따라와요.
트레이트에는 이 페이지에서 다룬 것보다 더 많은 내용이 있어요. 자주 쓰는 트레이트 페이지에서는 매일 쓰게 될 표준 트레이트를 하나씩 살펴보고, 고급 트레이트 페이지에서는 연관 타입, 슈퍼트레이트처럼 큰 코드베이스에서 보게 될 도구를 다뤄요.
연습 문제
섹션 제목: “연습 문제”1. 넓이를 가진 도형
섹션 제목: “1. 넓이를 가진 도형”메서드 fn area(&self) -> f64 하나를 가진 트레이트 Area를 정의해 보세요. 그리고 Rectangle(width와 height를 가짐)과 Square(side를 가짐)에 구현하세요. 3×4 직사각형과 한 변이 5인 정사각형의 넓이를 출력해 보세요. 힌트: impl Area for ... 블록마다 메서드는 하나만 있으면 되고, 본문은 곱셈 한 번이에요.
정답 보기
trait Area { fn area(&self) -> f64;}
struct Rectangle { width: f64, height: f64,}
struct Square { side: f64,}
impl Area for Rectangle { fn area(&self) -> f64 { self.width * self.height }}
impl Area for Square { fn area(&self) -> f64 { self.side * self.side }}
fn main() { let rect = Rectangle { width: 3.0, height: 4.0 }; let square = Square { side: 5.0 };
println!("직사각형: {}", rect.area()); println!("정사각형: {}", square.area());}실행 결과:
직사각형: 12정사각형: 252. 여러 타입을 받는 함수 하나
섹션 제목: “2. 여러 타입을 받는 함수 하나”fn describe(&self) -> String을 가진 트레이트 Describe를 정의해 보세요. 이것을 Book(title을 가짐)과 Movie(title과 minutes를 가짐)에 구현하세요. 그다음 설명을 출력하는 함수 fn show(item: &impl Describe) 하나를 만들고, 두 타입으로 모두 호출해 보세요. 힌트: 설명 문자열은 format!으로 만들면 돼요.
정답 보기
trait Describe { fn describe(&self) -> String;}
struct Book { title: String,}
struct Movie { title: String, minutes: u32,}
impl Describe for Book { fn describe(&self) -> String { format!("책: {}", self.title) }}
impl Describe for Movie { fn describe(&self) -> String { format!("영화: {} ({}분)", self.title, self.minutes) }}
fn show(item: &impl Describe) { println!("{}", item.describe());}
fn main() { let book = Book { title: String::from("호빗") }; let movie = Movie { title: String::from("업"), minutes: 96 };
show(&book); show(&movie);}실행 결과:
책: 호빗영화: 업 (96분)3. 기본 구현을 쓰거나 덮어쓰기
섹션 제목: “3. 기본 구현을 쓰거나 덮어쓰기”이 페이지의 Greet 트레이트를 기본 greet() 메서드와 함께 가져오세요. 기본 구현을 그대로 쓰는 Person과, greet()를 덮어써서 어이, ...!라고 인사하는 Pirate에 구현해 보세요. 힌트: 두 타입 모두 name()은 정의해야 해요. greet()까지 직접 쓰는 건 Pirate뿐이에요.
정답 보기
trait Greet { fn name(&self) -> String;
fn greet(&self) -> String { format!("안녕하세요, {}님!", self.name()) }}
struct Person { name: String,}
struct Pirate { name: String,}
impl Greet for Person { fn name(&self) -> String { self.name.clone() }}
impl Greet for Pirate { fn name(&self) -> String { self.name.clone() }
fn greet(&self) -> String { format!("어이, {}!", self.name()) }}
fn main() { let ana = Person { name: String::from("Ana") }; let jack = Pirate { name: String::from("Jack") };
println!("{}", ana.greet()); println!("{}", jack.greet());}실행 결과:
안녕하세요, Ana님!어이, Jack!- 트레이트는 타입이 구현할 수 있는 동작을 정의해요. 인터페이스와 비슷해요.
impl 트레이트이름 for 타입으로 구현을 제공해요.- 매개변수 타입으로
&impl 트레이트이름을 쓰면, 그 트레이트를 구현한 타입은 무엇이든 받을 수 있어요. - 트레이트는 메서드의 기본 본문을 제공할 수 있고, 구현하는 쪽은 그것을 그대로 쓰거나 덮어쓸 수 있어요.
Debug,Clone,PartialEq는 표준 라이브러리에서 자주 쓰는 트레이트예요.#[derive(...)]가 자동으로 구현해 줘요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 트레이트는 무엇을 정의할까요?
2. 매개변수 타입 animal: &impl Speak는 무슨 뜻일까요?
3. #[derive(Debug)]는 실제로 무슨 일을 할까요?
4. 트레이트의 기본 구현이란 무엇일까요?
5. 이 페이지에 따르면, 다음 중 구조체나 열거형이 아니라 트레이트인 것은 무엇일까요?
점수: 0 / 5
더 알아보기
섹션 제목: “더 알아보기”- 트레이트로 공통된 동작을 정의하기 — The Rust Programming Language 한국어판: 트레이트 정의, 기본 메서드,
impl Trait매개변수, 트레이트 바운드를 다루는 공식 장이에요. - Traits — Rust by Example (영어): 트레이트, 기본 메서드, 구현이 모두 들어 있는 실행 가능한 예제예요.
- Derive — Rust by Example (영어):
#[derive(...)]로 구현할 수 있는 표준 트레이트를 예제와 함께 알려 줘요. - 고급 트레이트 — The Rust Programming Language 한국어판: 다음 단계로, 연관 타입, 슈퍼트레이트, 뉴타입 패턴을 다뤄요.
- Traits — The Rust Reference (영어): 자세한 내용까지 알고 싶을 때 보는 트레이트 정의의 정확한 규칙이에요.