RustのGeneric associated types(GATs)が1.65.0でstabilizeとなりました。
ということで本記事では、GATsについての各種解説等を読みながら、GATsの理解を少しでも進めることを目指します。
Rustのversionは1.69.0
を利用しました。
The push for GATs stabilization
まずは、2021年8月Rust blogの記事The push for GATs stabilizationから読んでいきます。
そこでは、GATsの具体例として以下のcodeが挙げられています。
今までは、traitのassociated typeにはgenericsやlifetimeは書けなかったのですが、GATsによって書けるようになりました。
以前、Rustのlifetimeとvarianceでも書いたのですが、Vec<T>
が、Vec<usize>
のように具体的な型を与えられてtypeになるように、Bar<'a>
も具体的なlifetimeがあたえられてtypeになると理解しています。
Associated typeのところにlifetimeが書けるとなにがうれしいかというと
こんなcodeが書けるようになります。以前ですと、&'a T;
の'a
は実装するstructに定義されている必要がありました。
ちなみに上記のcodeはcompileが通りません。
|
82 | type Bar<'a> = &'a T;
| ^^^^^- help: consider adding a where clause: `where T: 'a`
| |
| ...so that the reference type `&'a T` does not outlive the data it points at
compile通すには結果的に以下のようになりました。
T
は実際には&str
かもしれないので、&str: 'a
の制約が必要といわれるのはなんとなくわかります。
次にもう少し具体的な利用例が紹介されます。
ここではmut sliceのwindow(sub slice)を返すiteratorを実装したいというユースケースです。
具体的には[1,2,3,4,5]
があったときに[1,2,3], [2,3,4], [3,4,5]
を返すようなiteratorです。
このWindowsMut
にIterator
を実装しようとすると
error: lifetime may not live long enough
--> src/lending.rs:21:9
|
15 | impl<'t, T> Iterator for WindowsMut<'t, T> {
| -- lifetime `'t` defined here
...
18 | fn next<'a>(&'a mut self) -> Option<Self::Item> {
| -- lifetime `'a` defined here
...
21 | Some(retval)
| ^^^^^^^^^^^^ method was supposed to return data with lifetime `'t` but it is returning data with lifetime `'a`
|
= help: consider adding the following bound: `'a: 't`
self.slice
の参照を返しているので、'a: 't
の制約が必要となり、compile errorとなります。
のようにwhere 'a: 't'
をつけると今度は
error[E0195]: lifetime parameters or bounds on method `next` do not match the trait declaration
--> src/lending.rs:18:12
|
18 | fn next<'a>(&'a mut self) -> Option<Self::Item>
| ^^^^ lifetimes do not match method in trait
Iteraotr
traitにそんな制約はないのでこれもcompile errorとなってしまいます。
そこで、GATsを利用した、LendingIterator
を定義します。
これはself.next()
の戻り値を利用している間だけ有効なItem
を返すIterator
と読めます。
実装はIterator
と同じですが、Item
のlifetimeに呼び出し時のlifetimeを渡せています。
// => [1, 2, 3]
// => [2, 3, 4]
// => [3, 4, 5]
無事、windowを返すことができました。
記事でも触れられていますが、type Item<'a> where Self: 'a
がなぜ必要かというと
fn next<'static>(&'static mut self) -> Option<Self::Item<'static>>
のようなcodeを書けなくするためみたいです。
この点はissueになっており将来的には暗黙的に仮定されるかもしれません。
Generic Associated Types Initiative
次はGeneric Associated Types Initiativeが提供してくれているbookをみていきます。 GATsに関する過去あった議論も整理されており、RFCやissueのコメントは長く全部読むのは大変なので助かりました。
例えば、GATsはRustの複雑度を上げすぎてしまう懸念の話は頷けるところ多かったです。
Iterable
trait
次はIterable
traitを通してGATsの使い所をみていきます。
まず以下のように今まで通りIterator
を実装してみます。
ここで次のように、ある処理の中で複数回iterateしたいので、iteratorを返せるcollectionを引数にした処理を書きたいとします。
これは以下のように2回目のfor loopでmoveした値を利用しているのでcompileが通りません。
28 | for elem in collection {
| ^^^^^^^^^^ value used here after move
|
ということで、Iterator
を返せることを抽象化したIterable
traitを作ることにします。
Iter
がIterator
でItem
を返します。IntoIterator
の&self
版といった感じです。
&[T]
は複数回Iterator
を返せるのでIterable
を実装したいところですが..
ここで&'what
lifetimeをどこからもってくるかで問題が生じます。GATsがないと今までは、impl<'a> Iterable for A<'a>
のようにimplにlifetime書く必要がありましたが、今回の[T]
にはlifetimeがでてきません。
というわけで、Iterable
traitをGATsを使って書き直します。
Item
とIter
にlifetimeを渡したいので、trait側で定義します。
実装はtrait側のlifetimeを使うだけです。
fn count_twice<I: Iterable + ?Sized>(collection: &I) { /* ... */}
Sized制約をopt outすると無事compileが通りました。
要はGATsによって、呼び出し側が使っている間だけ有効なself
のdataの参照を返せるようになったと理解しています。
bookにはGATsを実際に利用しているcrateのlistも載っていたりしました。
また、Iterable
やLendingIterator
以外にも、GATsのdesign patternとして、Many modeやgenerics scopesも取り上げられていました。
Many modeについてはわからないことが多く、今後の課題です。
まとめ
GATsの一利用例をみてきました。今後はlibrary等でも使われてくることが増えてくるのではと思っているのでうまい使い所を見つけていきたいです。