GAT 懶人包
Rust 1.65 提供了大家期待已久的功能「Generic Associated Types」,這是在 Functional Programming 中 Higher-Kind Types 的其中一種實現。對於已經有這樣需求的人來說,這些概念應該並不陌生,或是早已知道如何善用它們了。但對於從未接觸過的人來說,我想應該就會遇到一些困難。通常看到新出的 Rust 進階功能,我們都會推薦大家去看 RFC,而我近日也將 GAT 的 RFC 翻譯完畢。但我並沒有相當的把握確保新手能了解文中舉的例子「StreamingIterator」,因為它的泛型用到了生命週期,尤其是後面又繼續提到了各種進階的用法,像是怎麼與 Higher-Rank Trait Bounds 一起使用等等。我有點擔心新手一開始接觸時就會看的眼花撩亂。這次我們不妨把所有概念都拋諸腦後,看看在寫程式的過程中,到底為何會遇上這樣的需求。
讓我們用各型別都有的方法 map
來作為例子,這在 Option
和 Result
都有。如果今天我們想定義一個 Trait 將該方法抽象化成泛型(generic)型別的話,我們會發現這是做不到的!其中一個得回傳 Option<T>
而另一個得回傳 Result<T>
。這時你可能就會想將 Option
或 Result
作為泛型的引數(argument),但是這也是做不到的!泛型型別的名稱在 Rust 不是一個基礎的型別!
而 GAT 能夠允許你達成這樣的事,這也是為何 RFC 一開始得先介紹 Kind 以及 Type Constructor 等概念,我非常推薦有時間可以讀一讀這段。不過畢竟這篇是懶人包,怕太難懂我們可以直接先來看看真正的例子會怎麼寫。我們可以宣告一個這樣的 trait:
trait Mappable {
type Item;
type Target<U>;
fn map<U, F: FnMut(Self::Item) -> U>(self, f: F) -> Self::Target<U>;
}
然後針對 Option
和 Result
實作它們:
impl<T> Mappable for Option<T> {
type Item = T;
type Target<U> = Option<U>;
fn map<U, F: FnMut(Self::Item) -> U>(self, f: F) -> Option<U> {
self.map(f)
}
}
impl<T, E> Mappable for Result<T, E> {
type Item = T;
type Target<U> = Result<U, E>;
fn map<U, F: FnMut(Self::Item) -> U>(self, f: F) -> Result<U, E> {
self.map(f)
}
}
這樣一來你就能使用「泛型的泛型」來寫出這樣的函數:
fn absolute<M: Mappable<Item = i32>>(m: M) -> <M as Mappable>::Target<u32> {
m.map(|x| x.abs() as u32)
}
這還能用在 Vec
上,完整的範例可以參考 Rust Playground 的連結。在 RFC 中稱這樣的模式為「Family」模式,同樣的作法也能套用在指標的泛型上,像是 Arc
與 Rc
。當然 GAT 也可以用在生命週期上,就如同 RFC 上的 StreamingIterator,而這種 trait 的用途就是希望能只可變借用到特定片段的切片,而非平常 IterMut
得借用一整個集合。而在往下還有更多複雜的用法,不過希望看完這篇懶人包你能對 GAT 有初步的了解,在閱讀 RFC 時就不會那麼令人望之卻步了。