📢

auto-delegateの新バージョンをリリースしたので宣伝

2024/02/16に公開

auto-delegateは委譲を自動化するための小さなライブラリです。めちゃくちゃ人気!...というわけでは全然ありませんが細々と使ってもらっていて、最近ついに1000ダウンロードを突破しました。

今回新バージョンをリリースし、いくつかのバグ修正と新機能の追加が行われたため紹介します。

https://crates.io/crates/auto-delegate

https://github.com/not-elm/auto-delegate

auto-delegateとは

auto-delegateのことを知らない人がほとんどだと思うので初めに説明します。
このライブラリは記事の冒頭でも述べた通り自動委譲を実現するライブラリです。Kotlinを知っている方なら委譲プロパティを思い浮かべて頂ければ分かりやすいと思います。

コード例
use auto_delegate::{Delegate, delegate};

#[delegate]
trait Calc {
    fn calc(&self, x1: usize, x2: usize) -> usize;
}

#[derive(Default)]
struct CalcAdd;

impl Calc for CalcAdd {
    fn calc(&self, x1: usize, x2: usize) -> usize {
        x1 + x2
    }
}

#[derive(Delegate, Default)]
struct Parent {
    // Calcトレイトの実装をchildに委譲
    #[to(Calc)]
    child: CalcAdd
}

fn main() {
    let parent = Parent::default();
    assert_eq!(parent.calc(2, 3), 5);
}

no_std環境でも使う事ができ、特に活躍できると思います。
例えばno_stdだとグローバルアロケータが実装されておらず動的ディスパッチに対応できない状況が出てくるかもしれません。
このような場合委譲だけを目的としたEnumを代わりに作成する必要がありますが、手動で委譲処理を書くと大変なのでauto_delegateを使いましょう。

動的ディスパッチが使いたいケース
/// 下記のようにトレイトオブジェクトを作成したい場合、グローバルアロケータが使えない環境だとコンパイルエラーになる
fn fact_calc(ty: &str) -> Box<dyn Calc>{
    match ty {
        "add" => Box::new(Add),
        "sub" => Box::new(Sub),
        _ => panic!()
    }
}

#[delegate]
trait Calc {
    fn calc(self, lhs: usize, rhs: usize) -> usize;
}

#[derive(Default)]
struct Add;
impl Calc for Add {
    fn calc(self, lhs: usize, rhs: usize) -> usize {
        lhs + rhs
    }
}

#[derive(Default)]
struct Sub;
impl Calc for Sub {
    fn calc(self, lhs: usize, rhs: usize) -> usize {
        lhs - rhs
    }
}
auto_delegateを使用
fn fact_calc(ty: &str) -> Calculator{
    match ty {
        "add" => Calculator::Add(Add),
        "sub" => Calculator::Sub(Sub),
        _ => panic!()
    }
}

#[derive(Delegate)]
#[to(Calc)]
enum Calculator {
    Add(Add),
    Sub(Sub),
}

#[delegate]
trait Calc {
    fn calc(self, lhs: usize, rhs: usize) -> usize;
}

#[derive(Default)]
struct Add;
impl Calc for Add {
    fn calc(self, lhs: usize, rhs: usize) -> usize {
        lhs + rhs
    }
}

#[derive(Default)]
struct Sub;
impl Calc for Sub {
    fn calc(self, lhs: usize, rhs: usize) -> usize {
        lhs - rhs
    }
}

バージョンアップ情報

ここからは今バージョンで修正されたバグや機能追加について記載します。

バグ修正

4つ以上のバリアントをもつ列挙型に対してDelegate出来ない不具合の修正

本来は12個までのバリアントに対応しているのですが、4つ目のバリアントから委譲する際に実装をミスしておりコンパイルエラーが生じるようになっていました。
余談ですが、実はこの問題は結構早くから認知していました。Issueに誰か挙げてくれないかと期待して待っていましたが残念ながら来ませんでした...。
もっとIssueが飛び交うほど人気のライブラリに成長させていきたいです。

ジェネリクスの型が推論できない場合にコンパイルエラーとなる不具合の修正

これは下記のコードを見てもらった方が早いと思います。
ジェネリクスが存在する場合、委譲するときに明示的に型を指定するようにしました。

#[delegate]
trait Trait {
    fn method<T>(self) ;
}

impl<C: Trait> Trait for C{
    fn method<T>(self) {
        // 以前はこう
        self.method();
        // 修正後
        self.method::<T>()
    }
}

機能追加

匿名フィールドに対応

タプル構造体はフィールドが匿名になりますが、それらに対して委譲できるようになりました。

#[derive(Delegate, Default)]
struct Parent(#[to(Trait)]Child)

/// NewTypeの場合、下記のようにも書けます。
#[derive(Delegate, Default)]
#[to(Trait)]
struct Parent(Child)

staticメソッドに対応

レシーバーが存在しないメソッドの委譲が出来るようになりました。

現状、列挙型に対しても使用できるようになっていますが、将来的に使用できなくする可能性があるためstaticメソッドを持つトレイトは列挙型に対して委譲させないようにしてください。
列挙型に対して委譲された場合、一番最初のバリアントの型のメソッドが呼び出されます。

現状コンパイルエラーにならない理由は単純に実装方法が思いついていないためです。

コード例
#[delegate]
trait Calc {
    fn calc(lhs: usize, rhs: usize) -> usize;
}

struct Add;
impl Calc for Add {
    fn calc(lhs: usize, rhs: usize) -> usize {
        lhs + rhs
    }
}

struct Sub;
impl Calc for Sub {
    fn calc(lhs: usize, rhs: usize) -> usize {
        lhs - rhs
    }
}

#[derive(Delegate)]
#[to(Calc)]
enum Parent{
    Add(Add),
    Sub(Sub)
}

fn main() {
    // Add::calcが呼び出されます
    assert_eq!(Parent::calc(1, 2), 3);
}

まとめ

auto_delegateで良き委譲ライフを!

Discussion