Rustで委譲をやりたい


# 委譲が欲しくなる場面

次のようなトレイト`Widget`と、それを実装する`BaseWidget`があるとします。

``` Rust
trait Widget {
    fn size(&self) -> (u32, u32);
}

struct BaseWidget {
    w: u32,
    h: u32,
}

impl Widget for BaseWidget {
    fn size(&self) -> (u32, u32) {
        (self.w, self.h)
    }
}
```

この`BaseWidget`の機能を利用して、`LabelWidget`を作成したいとします。

``` Rust
struct LabelWidget {
    base: BaseWidget,
    text: String,
}
```

この`LabelWidget`にも`Widget`トレイトを実装したいのですが、その実装は`BaseWidget`のものを使いたいとします。このように所有する別オブジェクトに実装を肩代わりさせる機能を、一般的に委譲(delegation)と呼ぶようです[^1]。Rustは現状、言語機能として明示的に委譲を扱いませんが、今回の場合はどのように書くのが良いでしょうか。一番わかり易いのは、`self.base`を使ってメソッドを定義してやることです。

``` Rust
impl Widget for LabelWidget {
    fn size(&self) -> (u32, u32) {
        self.base.size()
    }
}
```

しかし、今回のようにメソッドが`size()`の1つのような場合はまだ良いのですが、メソッドが増えてきたような場合はかなり冗長になります。`LabelWidget`だけでなく、他にいろいろな型に定義してやりたくなった場合も、定型的な行がどんどん増えていくことになります。

他には、`Deref`を用いて`Widget`のトレイトオブジェクトを返す方法があります。

``` Rust
impl std::ops::Deref for LabelWidget {
    type Target = dyn Widget;

    fn deref(&self) -> &(dyn Widget + 'static) {
        &self.base
    }
}
```

これで一応、`LabelWidget`の値から直接`size()`を呼び出せますが、`LabelWidget`は実際には`Widget`を実装していません。また、`Deref`をこのような用途に使うのは不適切だと考えられます。
参考: [Deref polymorphism](https://github.com/rust-unofficial/patterns/blob/master/anti_patterns/deref.md)

というわけで、[ambassador](https://crates.io/crates/ambassador)というクレートを利用して委譲を実現します。

# ambassador

[ambassador](https://crates.io/crates/ambassador)は、トレイトの実装を委譲したい場合に必要になるボイラープレートを手続きマクロの力により削減してくれるクレートです。

ambassadorを使うと、上記のコードは以下のように書けます。

``` Rust
use ambassador::{Delegate, delegatable_trait};

#[delegatable_trait] // <- 委譲させたいトレイトに付ける
trait Widget {
    fn size(&self) -> (u32, u32);
}

struct BaseWidget {
    w: u32,
    h: u32,
}

impl Widget for BaseWidget {
    fn size(&self) -> (u32, u32) {
        (self.w, self.h)
    }
}

#[derive(Delegate)]
#[delegate(Widget, target = "base")] // <- self.baseを用いてWidgetを実装
struct LabelWidget {
    base: BaseWidget,
    text: String,
}

fn main() {
    let label_widget = LabelWidget {
        base: BaseWidget { w: 64, h: 20 },
        text: "hello".into(),
    };
    // label_widgetからsize()が呼び出せる
    assert_eq!(label_widget.size(), (64, 20));
}
```

ちょっとした属性を追加するだけで、`LabelWidget`に`Widget`トレイトが実装できました。

# enumに適用する

`Widget`を実装した型をメンバとする`AnyWidget`というenumがあったとします。

``` Rust
enum AnyWidget {
    Base(BaseWidget),
    Label(LabelWidget),
}
```

どのメンバも`Widget`を実装しているので、`AnyWidget`にも`Widget`を実装させたいところです。これを愚直に書くと以下のようになります。

``` Rust
impl Widget for AnyWidget {
    fn size(&self) -> (u32, u32) {
        match self {
            AnyWidget::Base(base) => base.size(),
            AnyWidget::Label(label) => label.size(),
        }
    }
}
```

match文が必要になり、非常に冗長です。これをambassadorを使って書き直すと以下のようになります。

``` Rust
#[derive(Delegate)]
#[delegate(Widget)] // 属性を追加するだけ
enum AnyWidget {
    Base(BaseWidget),
    Label(LabelWidget),
}

fn main() {
    let any_widget = AnyWidget::Label(LabelWidget {
        base: BaseWidget { w: 64, h: 20 },
        text: "hello".into(),
    });
    assert_eq!(any_widget.size(), (64, 20));
}
```

というわけで、かなり短く書けるようになりました。

# 余談

Rustに委譲の機能の機能を付けよう、というのは割と昔から議論されているようで、将来的には公式で似たような機能が実装されるかもしれません。

参考(RFCsへのプルリク):
https://github.com/rust-lang/rfcs/pull/1406
https://github.com/rust-lang/rfcs/pull/2393

[^1]: 正確には転送(forwarding)と呼ぶべきかもしれません