-
Rust: Generic Associated Types (GAT)컴퓨터/Rust 2022. 12. 19. 00:09728x90반응형
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() } }
위 구현의 문제는?
- parse 메소드, heap 메모리에 string을 새로 생성하고 parse 한다, 상대적으로 느려질 수도.
- 유연성. 다른 유형에 대해 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