3 분 소요

개요

  • 다른 언어의 인터페이스와 유사
  • 트레잇 혹은 타입이 우리의 크레이트 내의 것일 경우에만 해당 타입에서의 트레잇을 정의 가능
  • 오버라이딩된 구현으로부터 기본 구현을 호출하는 것은 불가능
  • 트레잇 바운드(trait bounds)
    • 제네릭 타입 파라미터에 제약 사항을 지정
    • fn f<T: Summarizable>(t: T) {}
    • 다수의 트레잇 바운드 가능
      • + 혹은 where를 이용
  • 연관 타입(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);
        }
  • 실행 결과

        1 : 3
        2 : 7


예제 - 서로 다른 트레잇에 동일한 메소드 호출

  • 코드

        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));
        }
  • 실행 결과

        1 : aaa
        2 : bbb
        3 : bbb


예제 - 완전 정규화(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());
        }
  • 실행 결과

        1 : aaa
        2 : bbb


예제 - 슈퍼트레잇(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());
        }
  • 실행 결과

        1