高階トレイト境界

Rust の Fn トレイトはちょっとした魔法です。例えば、次のように書くことができます。

struct Closure<F> {
    data: (u8, u16),
    func: F,
}

impl<F> Closure<F>
    where F: Fn(&(u8, u16)) -> &u8,
{
    fn call(&self) -> &u8 {
        (self.func)(&self.data)
    }
}

fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }

fn main() {
    let clo = Closure { data: (0, 1), func: do_it };
    println!("{}", clo.call());
}

ライフタイムの節と同じように単純に脱糖しようとすると、問題が起こります。

struct Closure<F> {
    data: (u8, u16),
    func: F,
}

impl<F> Closure<F>
    // where F: Fn(&'??? (u8, u16)) -> &'??? u8,
{
    fn call<'a>(&'a self) -> &'a u8 {
        (self.func)(&self.data)
    }
}

fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &'b data.0 }

fn main() {
    'x: {
        let clo = Closure { data: (0, 1), func: do_it };
        println!("{}", clo.call());
    }
}

F のトレイト境界は、一体どうすれば表現できるのでしょう? なんらかのライフタイムを提供する必要がありますが、問題のライフタイムは call 関数が呼ばれるまで名前が無いのです。さらに、ライフタイムは固定されていません。 &selfどんなライフタイムが割り当てられても、call は動作します。

この問題は、高階トレイト境界(HRTB: Higher-Rank Trait Bounds)という魔法で解決できます。 HRTB を使うとつぎのように脱糖できます。

where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,

Fn(a, b, c) -> d 自体が、まだ仕様が安定していない本当の Fn トレイトの糖衣構文です。)

for<'a> は、「'a に何を選んだとしても」という意味で、つまり F が満たさなくてはならないトレイト境界の無限リストを生成します。強烈でしょう? Fn トレイトを除けば、HRTB が使われる場所はほとんどありません。Fn トレイトにおいても、ほとんどの場合は魔法の糖衣構文が良いされています。

関連キーワード:  境界, data, self, call, func, Closure, ライフタイム, where, HRTB, 魔法