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

Rust 기본 : 특성 객체 (Trait Object)

by 레조 2024. 9. 25.

https://rustacean.net/

 

Rust 기본 : 특성 객체 (Trait Object)

 

러스트에서 특성 객체(trait object)는 특정 트레잇을 구현한 모든 타입의 인스턴스를 동적으로 처리할 수 있습니다. 이런 동적 처리(dynamic dispatch)는 런타임에 어떤 타입으로 결정된 메서드가 호출될지 결정하므로 해당 트레잇을 지원하는 다양한 타입(struct, enum)을 동일한 방식으로 처리할 때 사용합니다.

 

객체 생성 함수

 - 정적 처리(static dispatch)

   : create_hynix_memory() -> impl Compatible

특성 객체를 impl Compatible 방법으로 트레잇  리턴할 수 있습니다.

컴파일 시간에 호출 함수를 결정하기 때문에 런타임 성능이 좋습니다.

 

 - 동적 처리(dynamic dispatch)

   : create_compatible_memory(is_samsung: bool) -> Box<dyn Compatible>

특성 객체를 Box<dyn Compatible> 방식으로 감싸서 실행 시간에 리턴할 객체를 결정합니다.

실행 시간에 호출 함수를 결정하기 때문에 런타임 성능을 떨어지지만 코드 유연성은 올라갑니다.

// return static dispatch
fn create_hynix_memory() -> impl Compatible {
    HynixMemory { size: 16, speed: 2400 }
}

// return dynamic dispatch
fn create_compatible_memory(is_samsung: bool) -> Box<dyn Compatible> {
    if is_samsung {
        Box::new(SamsungMemory { size: 24, speed: 2400 })
    }
    else {
        Box::new(HynixMemory { size: 16, speed: 2400 })
    }
}

런타임 사용이 많은 로직은 정적 처리로 구현하고 나머지는 동적 처리로 코드 유연성을 높일 수 있습니다.

 

특성 객체 object를 함수 파라미터로 사용

 - 정적 처리 파라미터 : trait bound를 파라미터를 받는다. ex) &impl Trait

 - 동적 처리 파라미터 : trait object를 파라미터로 받는다. ex) &dyn Trait

// static parameter dispatch: &impl Trait
fn check_memory(object: &impl Compatible, other_speed: u32) -> bool {
    object.is_compatible(other_speed)
}

// dynamic parameter dispatch: &dyn trait
fn check_memory_dyn(object: &dyn Compatible, other_speed: u32) -> bool {
    object.is_compatible(other_speed)
}

 


 

전체 코드

// 호환(Compatible)과 업그레이드(Upgrade)는 의존 관계이다.
// 업그레이드 트레잇은 호환 트레잇에 의존한다. (종속)

trait Compatible {
    //fn is_compatible(&self, other: &Self) -> bool;
    fn is_compatible(&self, speed: u32) -> bool;
}

trait Upgradeable: Compatible {
    fn upgrade(&mut self);
}

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

struct HynixMemory {
    size: u32, // 메모리 크기 (예: GB 단위)
    speed: u32, // 메모리 속도 (예: MHz 단위)
}

impl Compatible for HynixMemory {
    fn is_compatible(&self, otherSpeed: u32) -> bool {
        self.speed == otherSpeed
    }
}

impl Upgradeable for HynixMemory {
    fn upgrade(&mut self) {
        if self.size < 32 {
            println!("Upgrading from {}GB to 32GB.", self.size);
            self.size = 32; // 예를 들어 최대 크기로 업그레이드
        } else {
            println!("Maximum upgrade size reached.");
        }
    }
}

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

struct SamsungMemory {
    size: u32, // 메모리 크기 (예: GB 단위)
    speed: u32, // 메모리 속도 (예: MHz 단위)
}

impl Compatible for SamsungMemory {
    fn is_compatible(&self, otherSpeed: u32) -> bool {
        self.speed == otherSpeed
    }
}

impl Upgradeable for SamsungMemory {
    fn upgrade(&mut self) {
        if self.size < 32 {
            println!("Upgrading from {}GB to 32GB.", self.size);
            self.size = 32; // 예를 들어 최대 크기로 업그레이드
        } else {
            println!("Maximum upgrade size reached.");
        }
    }
}

/* -------------------------------------------------------------------------- */
// static parameter: object
fn check_memory<T: Compatible>(object: &T, other_speed: u32) -> bool {
    object.is_compatible(other_speed)
}

// dynamic parameter: object
fn check_memory_dyn(object: &dyn Compatible, other_speed: u32) -> bool {
    object.is_compatible(other_speed)
}

/* -------------------------------------------------------------------------- */
// return static dispatch
fn create_hynix_memory() -> impl Compatible {
    HynixMemory { size: 16, speed: 2400 }
}

// return dynamic dispatch
fn create_compatible_memory(is_samsung: bool) -> Box<dyn Compatible> {
    if is_samsung {
        Box::new(SamsungMemory { size: 24, speed: 2400 })
    }
    else {
        Box::new(HynixMemory { size: 16, speed: 2400 })
    }
}

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

fn main() {
    let hynix_memory = HynixMemory { size: 16, speed: 2400 };
    let compatible_memory = create_compatible_memory(true);

    check_memory(&hynix_memory, 16);
    check_memory_dyn(compatible_memory.as_ref(), 32);
}

 


 

enum을 사용한 trait object.

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

enum Creature {
    Dog,
    Cat,
}

impl Animal for Creature {
    fn speak(&self) -> String {
        match self {
            Creature::Dog => "Woof!".to_string(),
            Creature::Cat => "Meow!".to_string(),
        }
    }
}

fn main() {
    let my_pet: Box<dyn Animal> = Box::new(Creature::Dog);
    println!("{}", my_pet.speak());
}