【Rust】委譲に使えるクレート(Ambassador)

2023-03-14

Rustでは継承が使えないが委譲をするにしてもグルーコードを書く必要があって面倒と思ってた(「継承より合成(委譲)」について)んだけど、とあるコードを見ていたらambassadorというクレートを使っていた。

Ambassador

Ambassador

Delegate trait implementations via procedural macros

とのことで、アトリビュートを指定することで委譲するコードを自動生成してくれる。

詳しい解説はドキュメントを読むとして、

  • 委譲を可能にするトレイト側に #[delegatable_trait] アトリビュートを指定
  • 構造体で、あるトレイトを自分のフィールドに委譲したい場合に #[derive(Delegate)]#[delegate(FooTrait, target="bar")] を指定

で使える。 構造体以外にも enum などでも使える。

なぜかエラーが出るケース

どういう条件なのか分からないが、エラーが出るケースがある。

error: cannot find macro `ambassador_impl_FooTrait` in this scope
--> mods/xxx/yyy/zzz/foobar.rs:66:12
|
66 | #[delegate(FooTrait, target="info")]
| ^^^^^^^^
|
= note: consider importing this macro:
crate::ambassador_impl_FooTrait
= help: have you added the `#[macro_use]` on the module/import?

この場合、トレイトを宣言している側のファイル頭に #![macro_use] と置く必要がある。

どういう条件かわからなくて、別モジュールなのかスーパートレイトだとなのかトレイトオブジェクトとして使用されるとなのか、最小限の条件を突き止めようとしたがわからず…。 Ambassador がマクロを生成してる?のを外部モジュールから使おうとしたら?なのかどうか…。

自分が試した際に問題が起きた箇所CoordinateTraitFormationTrait で。

別クレートの場合

Cargo.toml が別れてる別のクレートのトレイトを委譲する場合も、別のエラーが出る:

error: cannot find macro `ambassador_impl_BarTrait` in this scope
--> mods/xxx/yyy/zzz/foobar.rs:64:12
|
64 | #[delegate(BarTrait, target="info")]
| ^^^^^^^^
|
= note: consider importing this macro:
hogepiyo::ambassador_impl_BarTrait

別クレートのトレイト側で #[delegatable_trait] アトリビュートを設定していても、それによって生成されたマクロ?が外部からは見えない?らしくエラーが出る。

この場合、利用側にuse hogepiyo::ambassador_impl_BarTrait;とする必要がある。

一部のメソッドを委譲したい(が無理っぽい)

トレイトに含まれる一部のメソッドは自前でカスタマイズして残りは委譲する、ということができればしたい。 しかしそういうことはできないっぽい。

やるとしたら、トレイトを細かく分割してスーパートレイトで結合してやる必要がある。

そういうことができるようなクレートを自作しようにも、そもそも普通のトレイトでも複数のimplに分けて書くことはできないようなので今のところ難しそう。

Add support for partial delegation · Issue #13 · hobofan/ambassador

リンク