HRTB (Higher-Rank Trait Bounds) によるライフタイム注釈について。
HRTB と通常のライフタイム注釈には以下の違いがある。
アイテムの文脈に依存したスコープを対象とする。
例えば、関数の呼出元や値の生成元のスコープが対象となる。
アイテムの文脈によらない任意のスコープを対象とする。
例えば、コールバックの呼出元のスコープが対象となる。
明示的な場合と暗黙的な場合とがある。
for
キーワードを利用する。記述位置は型の先頭、またはトレイト境界の左辺か右辺の先頭、書式は for<'a>
のようなスタイルになる。複数の注釈が必要な場合、for<'a, 'b>
のようにカンマで区切る。
関数ポインタ型や呼出可能トレイト (Fn
, FnMut
, FnOnce
) の関数宣言風の境界の記法では、関数の場合と同様のライフタイム注釈の省略ルールが、HRTB のそれについても適用される。そのため、for
キーワードは必須ではなくなる。
、HRTB にはまだ制限が多い。
型推論の結果は HRTB を含まない。HRTB の箇所は型推論せずに明示しなければならない。これは例えば、as for<'a> fn(&'a _)
のようなキャストで実現される。
このルールは、HRTB を含んだ型推論が唯一のコンパイル手段であっても揺るがない。コンパイラはそのような型推論よりもエラーを選ぶ。これは奇妙に思えるかもしれないが、HRTB のような高階型への型推論が妥当な場面では、より具体的な型への型推論も妥当な場合が多い。このルールは単純さとそうした曖昧なケースの排除を両立している。
以下では、関数ポインタ型のライフタイム注釈に、HRTB が使用されている。
(このサンプルは for
キーワードを使わない形式でも書ける。)
use std::cmp::Ordering;
type Comparator = for<'a> fn(&'a i32, &'a i32) -> Ordering;
fn main() {
let text = get_cmp_text(i32::cmp, 1, 2);
assert_eq!(text, "<");
}
fn get_cmp_text(cmp: Comparator, x: i32, y: i32) -> String {
match cmp(&x, &y) {
Ordering::Less => "<",
Ordering::Equal => "=",
Ordering::Greater => ">",
}.to_string()
}
以下では、呼出可能トレイトによる境界に、HRTB が使用されている。
(このサンプルは for
キーワードを使わない形式でも書ける。)
use std::cmp::Ordering;
fn main() {
let text = get_cmp_text(i32::cmp, 1, 2);
assert_eq!(text, "<");
}
fn get_cmp_text<F>(cmp: F, x: i32, y: i32) -> String
where
F: for<'a> Fn(&'a i32, &'a i32) -> Ordering
{
match cmp(&x, &y) {
Ordering::Less => "<",
Ordering::Equal => "=",
Ordering::Greater => ">",
}.to_string()
}
以下では、通常のトレイトによる境界に、HRTB が使用されている。
(このサンプルは for
キーワードを使わない形式では書けない。)
use std::cmp::Ordering;
fn main() {
let text = get_cmp_text(IntComparator(), 1, 2);
assert_eq!(text, "<");
}
fn get_cmp_text<C>(comparator: C, x: i32, y: i32) -> String
where
C: for<'a> Comparator<&'a i32, &'a i32>,
{
match comparator.cmp(&x, &y) {
Ordering::Less => "<",
Ordering::Equal => "=",
Ordering::Greater => ">",
}.to_string()
}
struct IntComparator();
impl<'a> Comparator<&'a i32, &'a i32> for IntComparator {
fn cmp(&self, x: &'a i32, y: &'a i32) -> Ordering {
x.cmp(y)
}
}
trait Comparator<X, Y> {
fn cmp(&self, x: X, y: Y) -> Ordering;
}
以下では、GAT つきのトレイト IntCollection
が使われている。そして、関数 summary
の境界では、GAT により導出される型を、HRTB を含んだ形式で表現している。
use std::slice::Iter;
fn main() {
let vec = vec![1, 2, 3];
assert_eq!(summary(vec), 6);
}
trait IntCollection {
type IntStream<'a>: Iterator<Item = &'a i32> where Self: 'a;
fn int_stream(&self) -> Self::IntStream<'_>;
}
impl IntCollection for Vec<i32> {
type IntStream<'a> = Iter<'a, i32> where Self: 'a;
fn int_stream(&self) -> Self::IntStream<'_> {
self.iter()
}
}
fn summary<T>(ints: T) -> i32
where
T: for<'a> IntCollection<IntStream<'a> = Iter<'a, i32>>
{
ints.int_stream().sum()
}
以下では、GAT つきのトレイト IntCollection
が使われている。そして、関数 average
の境界では、GAT により導出される型の境界を、HRTB を含んだ形式で表現している。
use std::slice::Iter;
fn main() {
let vec = vec![1, 2, 3];
assert_eq!(average::<Vec<_>>(vec), 2.0);
}
trait IntCollection {
type IntStream<'a>: Iterator<Item = &'a i32> where Self: 'a;
fn int_stream(&self) -> Self::IntStream<'_>;
}
impl IntCollection for Vec<i32> {
type IntStream<'a> = Iter<'a, i32> where Self: 'a;
fn int_stream(&self) -> Self::IntStream<'_> {
self.iter()
}
}
fn average<T>(ints: T) -> f32
where
T: IntCollection,
for<'a> T::IntStream<'a>: ExactSizeIterator
{
let sum = ints.int_stream().sum::<i32>();
let len = ints.int_stream().len();
sum as f32 / len as f32
}