HRTB (Higher-Rank Trait Bounds) によるライフタイム注釈について。

概要

HRTB と通常のライフタイム注釈には以下の違いがある。

通常のライフタイム注釈

アイテムの文脈に依存したスコープを対象とする。

例えば、関数の呼出元や値の生成元のスコープが対象となる。

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()
}

トレイト境界 1

以下では、呼出可能トレイトによる境界に、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()
}

トレイト境界 2

以下では、通常のトレイトによる境界に、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 との連携 1

以下では、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 との連携 2

以下では、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
}