C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

Rust の tia crate で accessor とか interface 的な trait 群の impl を楽できるようになりました。のメモ

と、いうわけで tia crate を公開しました。

tia は何をしてくれる lib な crate ですか?

  • tia を使うと struct|enum|union のフィールドに Getter/Setter 的なアクセサーの impl を全自動化できます。

はじめて使用、あるいは tia の機能の概要を知りたい場合用の簡単なエグザンプルは以下のコードです:

use tia::Tia;  // 1. use

#[derive(Tia)] // 2. derive
#[tia(rg)]     // 3. tia directives
struct MyStruct
{
 foo: i32,
 bar: String
}

fn main()
{
 let mys = MyStruct{ foo: 123, bar: "Hello".into() };
 let foo = mys.get_foo(); // <-- 4. !! generated by tia automatically !!
 let bar = mys.get_bar(); // <-- 5. !! generated by tia automatically !!
 println!("foo={} bar={}", foo, bar );
}

このコードは MyStructimpl を手書きしていません。しかし、ビルドでき、実行できます。エラーも生じません。 .get_foo().get_bar() アクセサー群は tia が proc-macro により与えられたディレクティブに基づき全自動で生成してくれます。↑の例のようにもっとも単純に MyStruct へ Getter や Setter を実装する効果はやや弱いのですが、それでも tiarsi ディレクティブで Into パターンをフィールドへ自動実装したり、便利なこともあるかもしれません。

tia が作者に必要とされた理由は trait に対応したフィールドを自動生成できるようにしなければ、XMLSchemaにゲームを楽しみ料理を楽しむ時間を奪われそうになった事に起因します。起因の部分の細かい話はまた別の機会として、 trait に対応したアクセサーの自動生成は:

use tia::Tia;

trait FooGettable<T>{ fn get_foo(&self) -> T; }
trait Fruit{ fn get_bar(&self) -> &String; }
trait Sushi{ fn tuna(&self) -> u8; fn avocado(&mut self, v: u8); }

#[derive(Tia, Debug, Default)] // derive
struct MyStruct
{
 #[tia(s, "FooGettable<i32>", g)]
 foo: i32,
 #[tia("Fruit",rg,"",rsi)]
 bar: String,
 #[tia("Sushi",g*="tuna",s*="avocado")] // <- `g` and `s`: Sushi trait
 baz: u8
}

fn main()
{
 let mut mys = MyStruct::default();
 mys.set_foo(123);
 mys.set_bar("meow");
 let foo_gettable = &mys as &dyn FooGettable<i32>;
 let fruit = &mys as &dyn Fruit;
 println!("{}, {}", foo_gettable.get_foo(), fruit.get_bar() );
 let sushi = &mut mys as &mut dyn Sushi;
 sushi.avocado(32);
 println!("{}", sushi.tuna());
}

こんな具合です。たくさんの trait たちに手書きで対応しなければならないときには便利そうです。

より詳しくは README へ書いたので興味がわいたらどうぞ❤

にゃーん

tia を作るため proc-macro や [syn][] をトリアエズ扱える程度に修行できました。ソースを見るとバレそうですが、はじめはもうちょっと簡単にほいほい作れちゃうのでは…と思い大雑把でわりと簡単な設計に実験などしつつごちゃごちゃ実装になってしまいました。需要があればリファクタリングというか、一部再設計もして保守したいと思います。

ちなみに…Rustでは enum, union, Box, などで多相性(=ぽりもーふぃずむ)の要件をばばばっとやっつけてしまう設計の方が言語仕様的にも楽ですが、世の中のすべてが Rust を前提に設計されているわけではなく、オブジェクト指向パラダイムを主とした言語、例えば C#, Java, C++ など向けを当初想定して作られた、 UML で設計された、そんなような何かや複雑な XMLSchema を前提とした何かを大量に扱うための構造を実装したい場合、素直にオブジェクト指向パラダイム風に trait を interface 的に Rust の設計に落とし込み、 dyn した trait 型のオブジェクトを介してアクセサーで多相性をどうにかする、という事が必要な場合もあるかもしれません。

XMLSchema はそもそも人が手書きでパーサーやプログラミング言語の中での構造、あるいはUMLでいう汎化(≈継承)や包含の実装を全自動化できるハッピーな仕組み…なのですが、意外とまともな言語バインディングって少ないのです。UMLにせよおおよそ実質的にJava界隈を強く意識して作られているので仕方ないのだけど。Rust も XMLSchema の言語バインディングがまだまだ未成熟でごく簡単な単一ファイルの XMLSchema ならかろうじて対応できる crate もあるのですが…実質実用はまだまだ無理そうです。一般的に XMLSchema さんは Java のソースファイルのように大量の別れたファイル群で相互に複雑にリンクを持っていて、… まあ…そのはなしはまたこんど別の記事で…。

おまけメモ

  • tia に似た機能の crate には yangby-cryptape/rust-property というのがあります。trait に対応していないのと、たぶん方向性として主な用途、目標が違いそうなのでばばばっと…夜な夜な tia を作りました。
  • XMLSchema の lib crate の希望は media-io/xml-schema というのがあります。 flatten 未対応だったりまだまだ先は長いですが何年後かには実用性を獲得できるかもしれません。
    • bin な binding 生成器で estk/xmlschemer というのも見つけましたが、こちらはビルドから困難な状態で放棄されているので触るのはしんどそうです。