ABOUT ME

-

Total
-
  • Rust: Generic Associated Types (GAT)
    컴퓨터/Rust 2022. 12. 19. 00:09
    728x90
    반응형

    Rust 언어에선 Associated Type (연관 유형, 이하 AT) 은 trait에 정의된 유형 (type)의 placeholder (자리 표시)이며

    trait가 구현될 때 조금 더 구체적인 type으로 대체될 수 있다.

    AT는 일반 형식의 매개 변수를 사용하여 구조 또는 함수 정의의 형식에 대해 추상화할 수 있는 방법과 유사한

    trait 정의 형식에 대해 추상화할 수 있다.

     

    또 AT는 하나 이상의 type 멤버가 있는 trait를 정의하려는 경우에 유용하지만

    이러한 멤버들의 특정 type은 trait가 구현될 때까지 알 수 없다. (런타임)

     

    trait에 AT가 있는 간단한 예제이다:

    trait Iterator {
        type Item;
        fn next(&mut self) -> Option<Self::Item>;
    }

    위 예제에서 Iterator(반복자) trait에는 AT와 다음 Item의 Option 타입을 반환하는 메소드가 있다.

    Item 타입이 trait 정의에 지정되지는 않았지만 trait가 구현되면 구체적인 type으로 대체된다.

     

    trait 구현할 때 AT를 사용하려면 type 키워드와 관련 타입에 사용할 구체적인 타입을 지정할 수 있다.

    struct Counter {
        count: u32,
    }
    
    impl Iterator for Counter {
        type Item = u32;
        fn next(&mut self) -> Option<Self::Item> {
            if self.count > 0 {
                self.count -= 1;
                Some(self.count)
            } else {
                None
            }
        }
    }
    
    fn main() {
        let counter = Counter{count: 1};
        println!("Value: {:#?}", counter.take(4))
    }  # n = 4

    위 예제를 보면 AT Item이 concrete한 u32 타입으로 대체된다. 따라서 Counter의 next 메소드를 부르면

    Option<u32> 의 값을 반환한다.

     

    Generic Associated Types 사용하기

    AT는 generic으로 사용될 수도 있다. 따라서 trait 정의에서 여러 타입을 추상화(abstract)할 수 있게 해 준다.

    Generic Associated Types (GAT)을 정의하려면 < > bracket을 쓰고 타입 파라미터를 아래처럼 정하면 된다:

     

    아래 예제에서는 MyTrait trait에 GAT 파라미터 T를 갖고 있고 관련 타입 Output을 갖고 있다.

    GAT을 이용하여 trait를 구현하려면 type 키워드 사용과 AT에 사용될 구체적인 타입을 지정해줘야 한다.

    trait MyTrait<T> {
        type Output;
        fn do_something(t: T) -> Self::Output;
    }
    
    struct MyStruct<T> {
        value: T,
    }
    
    impl<T> MyTrait<MyStruct<T>> for MyStruct<T> {
        type Output = T;
        fn do_something(t: MyStruct<T>) -> T {
            t.value
        }
    }
    
    fn main() {
        let x = MyStruct { value: 10 };
        let y: i32 = <MyStruct<i32>>::do_something(x);
        println!("y = {}", y); // 10
    }
     

    물론 장점이 있으면 단점은 아래와 같다.

    • 복잡성: GAT을 사용하면 특히 동일한 특성에서 여러 GAT을 정의하는 경우 코드가 복잡해질 수 있다. 이로 인해 코드를 이해하고 유지하기가 더 어려워질 수 있다.
    • 성능: GAT을 사용하려면 런타임 유형 정보(RTTI)를 사용해야 하기 때문에 성능에 영향을 미칠 수 있다. 이렇게 하면 컴파일된 코드의 크기가 증가하고 런타임 성능에 영향을 줄 수 있다.
    • 호환성: 일부 Rust 코드는 GAT이 러스트에 도입되기 전에 작성된 경우 일반적인 관련 유형과 호환되지 않을 수 있다. 이로 인해 일반적으로 GAT을 사용할 때 특정 코드의 재사용이 제한될 수 있다.
    • 추론: 경우에 따라 Rust 컴파일러는 일반적인 연관 유형의 유형을 추론할 수 없으며, 이를 명시적으로 지정해야 할 수도 있습니다. 이것은 코드를 더 상세하게 만들 수 있고 추가적인 유형 주석이 필요할 수 있다.

     

    그렇지만 trait에서 여러 타입 추상화가 필요하면 조금 더 flexible 하고 재사용 가능한 코드를 사용 가능해진다.

    아래 예제로 글을 마친다. 비교해보길 바람

    trait StringConversion<T> {
        fn from_string(s: &str) -> T;
        fn to_string(&self) -> String;
    }
    
    impl StringConversion<u32> for u32 {
        fn from_string(s: &str) -> u32 {
            s.parse().unwrap()
        }
        fn to_string(&self) -> String {
            self.to_string()
        }
    }
     

    위 구현의 문제는?

    1. parse 메소드, heap 메모리에 string을 새로 생성하고 parse 한다, 상대적으로 느려질 수도.
    2. 유연성. 다른 유형에 대해 StringConversion 특성을 구현하려면 반복적이고 오류가 발생하기 쉬운 해당 유형에 대한 특성의 새로운 구현을 정의해야 함.

     

    GAT으로 변환 시

    trait StringConversion {
        type T;
        fn from_string(s: &str) -> Self::T;
        fn to_string(&self) -> String;
    }
    
    impl StringConversion for u32 {
        type T = u32;
        fn from_string(s: &str) -> u32 {
            // Use a more efficient conversion method than parse()
            s.parse().unwrap()
        }
        fn to_string(&self) -> String {
            self.to_string()
        }
    }

     

    728x90

    '컴퓨터 > Rust' 카테고리의 다른 글

    Rust: Closure Syntax  (1) 2022.12.27
    Rust: Go와 비슷하게 멀티쓰레딩 짜기  (1) 2022.08.29
    Rust: HashMap 값 for 문에서 update하기  (0) 2022.08.10

    댓글