본문 바로가기
개발/러스트 (Rust)

Rust 기본 : 특성 제약 (Traits Bounds)

by 레조 2024. 9. 12.

https://rustacean.net/

 

Rust 기본 : 특성 제약 (Traits Bounds)

 

러스트에서 트레잇 바운드는,

구현하는 시점의 타입에 제한을 두고자 할 때 사용합니다.

(특정 트레잇을 구현하는 타입만을 허용하는 제네릭 타입에 대한 제약)

 

예를 들어 개나 고양이 객체를 입력 받아서 빨간색으로 염색하는 함수를 만들고 싶습니다.

이를 위해 'object'라는 이름의 입력 매개변수를 받는데, 객체가 'Animal' 트레잇을 구현했다면 어떤 타입이든 입력 매개변수로 사용 가능합니다. 

 

트레잇 바운드를 지정하는 세 가지 방법이 있습니다.

trait Animal {
    fn make_sound(&self);
    fn given_name(&self) -> String;
}

 

1. <T: Animal>(object: &T)

첫 번째 방법은 제네릭 타입을 정의한 후 콜론을 추가하고 트레잇을 지정하는 것입니다.

이제 T는 'Animal' 트레잇을 구현하는 어떤 타입에도 매개 변수로 받을 수 있습니다.

fn dye_red1<T: Animal>(object: &T) {
    println!("{} dye red1.", object.given_name());
}

 

2. (object: &impl Animal)

impl 구문은 단순히 <T: Animal> (object: &T) 에 대한 syntax sugar입니다.

fn dye_red2(object: &impl Animal) {
    println!("{} dye red2.", object.given_name());
}

 

3. <T>(object: &T) where T: Paint

fn dye_red3<T>(object: &T) where T: Animal {
    println!("{} dye red3.", object.given_name());
}

 

여러 트레잇 바운드를 가지고 있다면 where 절을 사용하는 것이 유용합니다.

fn dye_animal_red<T>(object: &T)
where T: Animal + Playful {
    println!("{} dye red. {}", object.given_name(), object.play());
}

 

트레잇 바운드는 반환 타입으로도 사용합니다.

단, 반환하는 타입은 트레잇를 적용한 구체적인 타입이어야합니다.

fn create_animal_object() -> impl Animal {
    Duck {}
}

 


 

전체 코드

trait Animal {
    fn make_sound(&self);
    fn given_name(&self) -> String;
}

trait Playful {
    fn play(&self) {
        println!("Animal is sleeping...");
    }
}

/* -------------------------------------------------------------------------- */

struct Cat {
    name: String,
}

// 선언만 한 트레잇는 반드시 구현해야한다.
impl Animal for Cat {
    fn make_sound(&self) {
        println!("{} says meow!", self.given_name());
    }

    fn given_name(&self) -> String {
        self.name.clone()
    }
}

// 트레잇가 기본 구현을 가지고 있다면 추가 구현 없이 객체에 바로 적용할 수 있다.
impl Playful for Cat {}

/* -------------------------------------------------------------------------- */

struct Dog {
    name: String,
}

// 메소드 구현
impl Dog {
    fn sleep(&self) {
        println!("The dog, {}, is sleeping.", self.given_name());
    }
}

// 트레잇 구현
impl Animal for Dog {
    fn make_sound(&self) {
        println!("{} says woof!", self.given_name());
    }

    fn given_name(&self) -> String {
        self.name.clone()
    }
}

// 기본 트레잇 덮어쓰기 (trait overwrite)
impl Playful for Dog {
    fn play(&self) {
        println!("{} loves playing fetch with a ball!", self.given_name());
    }
}

/* -------------------------------------------------------------------------- */

struct Duck {}

impl Animal for Duck {
    fn make_sound(&self) {
        println!("{} Quack!", self.given_name());
    }

    fn given_name(&self) -> String {
        "No name.".to_string()
    }
}

/* -------------------------------------------------------------------------- */

fn dye_red1<T: Animal>(object: &T) {
    println!("{} dye red1.", object.given_name());
}

fn dye_red2(object: &impl Animal) {
    println!("{} dye red2.", object.given_name());
}

fn dye_red3<T>(object: &T) where T: Animal {
    println!("{} dye red3.", object.given_name());
}

fn dye_animal_red<T>(object: &T) where T: Animal + Playful {
    println!("{} animal dye red.", object.given_name());
    object.play();
}

fn create_animal_object() -> impl Animal {
    Duck {}
}

/* -------------------------------------------------------------------------- */

fn main() {
    let dog = Dog { name: "Buddy".to_string() };
    let duck = Duck {};
    let object = create_animal_object();

    dye_red1(&dog);
    dye_red2(&duck);
    dye_red3(&object);

    // duck과 object는 Playful 트레잇이 없다.
    dye_animal_red(&dog);
    //dye_animal_red(&duck);
    //dye_animal_red(&object);
}

 

결과

Buddy dye red1.
No name. dye red2.
No name. dye red3.
Buddy animal dye red.
Buddy loves playing fetch with a ball!