개요
- 다른 언어의 인터페이스와 유사
- 트레잇 혹은 타입이 우리의 크레이트 내의 것일 경우에만 해당 타입에서의 트레잇을 정의 가능
- 오버라이딩된 구현으로부터 기본 구현을 호출하는 것은 불가능
- 트레잇 바운드(trait bounds)
- 제네릭 타입 파라미터에 제약 사항을 지정
fn f<T: Summarizable>(t: T) {}
- 다수의 트레잇 바운드 가능
- 연관 타입(associated type)
- 트레잇 정의 내에서 플레이스홀더 타입을 명시
- 임의의 타입을 사용하는 트레잇을 정의 가능
- 제네릭
- 하나의 타입에 대해 제네릭 타입 파라미터의 타입을 변경해가면서 여러번 구현이 가능하므로 구현마다 타입 명시 필요
- 연관 타입은 하나의 트레잇에 대해 여러번의 구현을 할 수 없게 되므로 타입 명시를 할 필요가 없음
- 기본 제네릭 타입 파라미터
- 제네릭 타입에 대한 기본 타입 명시 가능
- 문법
<PlaceholderType=ConcreteType>
- 연산자 오버로딩이 좋은 예
- 서로 다른 트레잇에 동일한 메소드 호출
{$trait_name}::{$function_name}(&{${value}})
- 완전 정규화(fully qualified) 문법
- 동일한 이름의 메소드/연관함수를 호출해야하는 경우 모호성 방지
- 슈퍼트레잇(supertrait)
- 트레잇 내에서 다른 트레잇의 기능 요구
- 의존 중인 트레잇이 구현하는 트레잇의 슈퍼트레잇
- 뉴타입 패턴(newtype pattern)
- 외부 타입에 대해 외부 트레잇을 구현하기 위한 패턴
- 타입 안전성과 추상화를 위해서도 사용
- 튜플 구조체 내에 새로운 타입을 만드는 것
- 단점은 새로운 타입이므로 원래 값의 메소드를 가지지 못한다는 점
- 모든 메소드가 필요할 경우 Deref 트레잇을 구현하는 것이 해결책
- 일부 메소드만 필요하다면 수동 구현
예제
- 코드
-
trait TraitA {
fn job() -> String {
String::from("call job")
}
fn job1(&self) -> u32;
fn job2(&self) -> String {
String::from("call job2")
}
}
trait TraitB {
fn job(&self) -> u32 {
1
}
}
struct Test1 {
i: u32,
}
impl TraitA for Test1 {
fn job1(&self) -> u32 {
self.i
}
}
struct Test2 {}
impl TraitA for Test2 {
fn job1(&self) -> u32 {
1
}
fn job2(&self) -> String {
String::from("call job2 - Test2")
}
}
impl TraitB for Test2 {}
fn f1<T: TraitA>(t: T) -> String {
t.job2()
}
fn f2<T: TraitA + TraitB>(t: T) -> String {
t.job2()
}
fn f3<T>(t: T) -> String
where
T: TraitA + TraitB,
{
t.job2()
}
fn main() {
println!("1.1 : {}", Test1::job());
println!("1.2 : {}", Test1 { i: 1 }.job1());
println!("1.3 : {}", Test1 { i: 1 }.job2());
println!("2.1 : {}", Test2 {}.job2());
println!("3.1 : {}", f1(Test1 { i: 1 }));
println!("3.2 : {}", f1(Test2 {}));
println!("4.1 : {}", f2(Test2 {}));
println!("4.2 : {}", f3(Test2 {}));
}
- 실행 결과
-
1.1 : call job
1.2 : 1
1.3 : call job2
2.1 : call job2 - Test2
3.1 : call job2
3.2 : call job2 - Test2
4.1 : call job2 - Test2
4.2 : call job2 - Test2
예제 - 기본 제네릭 타입 파라미터
- 코드
-
use std::ops::Add;
struct Test1 {
i: i32,
}
impl Add for Test1 {
type Output = Test1;
fn add(self, rhs: Test1) -> Test1 {
Test1 { i: self.i + rhs.i }
}
}
impl Add<Test2> for Test1 {
type Output = Test1;
fn add(self, rhs: Test2) -> Test1 {
Test1 { i: self.i + rhs.i }
}
}
struct Test2 {
i: i32,
}
fn main() {
let test1_1 = Test1 { i: 1 };
let test1_2 = Test1 { i: 2 };
println!("1 : {}", (test1_1 + test1_2).i);
let test1 = Test1 { i: 3 };
let test2 = Test2 { i: 4 };
println!("2 : {}", (test1 + test2).i);
}
- 실행 결과
예제 - 서로 다른 트레잇에 동일한 메소드 호출
- 코드
-
trait Trait {
fn func(&self) -> String;
}
struct Test;
impl Test {
fn func(&self) -> String {
String::from("aaa")
}
}
impl Trait for Test {
fn func(&self) -> String {
String::from("bbb")
}
}
fn main() {
let test = Test {};
println!("1 : {}", test.func());
println!("2 : {}", Trait::func(&test));
println!("3 : {}", <Test as Trait>::func(&test));
}
- 실행 결과
예제 - 완전 정규화(fully qualified) 문법
- 코드
-
trait Trait {
fn func() -> String;
}
struct Test;
impl Test {
fn func() -> String {
String::from("aaa")
}
}
impl Trait for Test {
fn func() -> String {
String::from("bbb")
}
}
fn main() {
println!("1 : {}", Test::func());
println!("2 : {}", <Test as Trait>::func());
}
- 실행 결과
예제 - 슈퍼트레잇(supertrait)
- 코드
-
trait SuperTrait {
fn func1(&self) -> String {
String::from("SuperTrait::func call")
}
}
trait Trait: SuperTrait {
fn func(&self) {
println!("Trait::func start");
println!("{}", self.func1());
println!("Trait::func end");
}
}
struct Test;
impl SuperTrait for Test {}
impl Trait for Test {}
fn main() {
let test = Test {};
test.func();
}
- 실행 결과
-
Trait::func start
SuperTrait::func call
Trait::func end
예제 - 뉴타입 패턴(newtype pattern)
- 코드
-
trait SuperTrait {
fn func(&self) -> String {
String::from("SuperTrait::func call")
}
}
struct Test(Vec<i32>);
impl SuperTrait for Test {
fn func(&self) -> String {
self.0[0].to_string()
}
}
fn main() {
let test = Test(vec![1, 2]);
println!("{}", test.func());
}
- 실행 결과