RustでOperator Overloadを利用したDSLを作ってみる
気が付いたら2025年が終わってました。今年の抱負とか考える前に仕事が始まったので、今年もいきあたりばったりに行きていければと思います。 最近Rustで細々と格闘しているんですが、ちょっとやってみたいことがあってやってみたらできたので、小ネタとして残しておこうかと思います。 やりたいこと 諸事情で、代数式をstructとして管理することをしています。毎度ですが、なんでそういうことをしようとしているのか?は聞いてはなりません。 // こんなtraitを定義しておく trait Equation { // envは変数のhashmap fn evaluate(&self, env: &HashMap<String, f32>) -> f32; } こんなtraitを実装したものを考えたいです。なお実装は自明なので省きます。で、これを普通に実装すると、ちょっと複雑になっただけで大変なことになります。 // 3.2 + 3.4 let e = ArithmeticEquation::new(Add, ConstantEquation::new(3.2), ConstantEquation::new(3.4)) // 掛け算とかがnestすると大変なことになる これをなんとかある程度楽にしたい、というのがモチベーションです。 方法の検討 Rustだと、大きく3通りのやり方があると思います。 procedural macroを実装する lexer/parserを利用してparseを実装する operator overloadとstructを駆使して頑張る macroとoperator overloadはcompile時に、lexer/parserは動的になる感じです。最終的にはlexer/parserが必要になりそうなんですが、一旦は静的にできれば(テストを書いたりするときに)便利です。となると、macroかoperator overloadが選択肢になる感じですね。 lexer/parser自体を生成するmacroとかはあるようですが、そもそもparseするという行為自体が、compileした後の話になるので。 Operator overloadを検討してみる RustのOperator overloadは大変に強力である意味シンプルなのですが、 Scopingが困難 です。 KotlinとかのOperator overloadでは、interfaceの実装元とかで切り分けられたり、scoped functionを利用することで、DSL/operator overloadの利用範囲をscopingすることがるできます。 object Ops { operator fun invoke(f: Ops.() -> Unit) { f() } infix fun String.test(rhs: String): boolean {...} } // こんな感じで使える Ops { "hoge" test "foo" } // 外だと明示的なimportが必要。 翻ってRustのOperator overloadは、標準にある Add や Sub といったTraitを型に対して実装する・・・という形です。 ...