🦀

RustでTraitのエイリアスをつくりたい

2022/02/06に公開
1

TL;DR

stable機能だけでいきたいならマクロを使う。

本編

RustはIteratorを使おうとすると辛い言語として有名でした。現在はimpl Traitなどの機能により状況は改善しつつありますが、それでも稀にこのようなアルティメットスゴクナガイ型名を書かなくてはならない場面に出くわします。

fn iterate_x<'x>(i: impl Iterator<Item =&'x X> + ExactSizeIterator + Clone) {
   // ...
}

これでイテレータを2つ取る関数だったりすると、シグネチャが見る気も失せるような長さになってしまいます。

対策としてトレイトにエイリアスを付けるというのはすぐ思いつきますが、エイリアスを付ける方法は3つあります。
好みと使いたいコンパイラに合わせて選択してください。

Trait aliasを使う

そのものズバリですね。これで解決、めでたしめでたしと言いたいところですが……はい、案の定これはexperimental機能(2022年8月現在、実装状況は#41517を参照)です。安定版のコンパイラでは使用できません。

#![feature(trait_alias)]

trait Foo = std::fmt::Debug + Send;
trait Bar = Foo + Sync;

// Use trait alias as bound on type parameter.
fn foo<T: Foo>(v: &T) {
    println!("{:?}", v);
}

pub fn main() {
    foo(&1);

    // Use trait alias for trait objects.
    let a: &Bar = &123;
    println!("{:?}", a);
    let b = Box::new(456) as Box<dyn Foo>;
    println!("{:?}", b);
}

impl Trait in type aliasを使う

こっちはunstable機能(#63063)です。発展途上言語の辛いところですね。

#![feature(type_alias_impl_trait)]

type DebugClone = impl Clone + Debug;

マクロを使う

RustのマクロはCより衛生的な分機能が制限されている印象がありますが、それでも結構色んな箇所で使えます。そう、型指定の位置でも使えるのです。これはもちろん安定版でも使える方法です。

macro_rules! IterX {
    () => { impl Iterator<Item =&'x X> + ExactSizeIterator + Clone };
    ($l: lifetime) => { impl Iterator<Item =&$l X> + ExactSizeIterator + Clone }
}

fn iterate_x<'x>(i: IterX!()) {
   // ...
}

ライフタイム指定は上のようにキャプチャすることもできます。

というわけで、今度こそめでたしめでたしです。

Discussion