背景
プログラミングする際にCall OperatorをしょっちゅうOverloadして使うんですがC++とかPythonと違ってOverloadの仕方がちょっと特殊?な上に全然関連ドキュメントが出てこないので備忘録がてらにメモしておきます。
C++/PythonでのOperator Overload
自分がある程度書いていてOperator Overloadができる言語がC++/Pythonあたりしかなかったのでその2言語での例になりますがやり方はいずれも以下の通りです。
C++
# include <iostream>
# include <string>
class Foo {
public:
void operator()(std::string arg) {std::cout << "Hello, " << arg << "!" << std::endl;}
};
Python
class Foo:
def __init__(self):
pass
def __call__(self, arg):
print "Hello {}!".format(arg)
いずれもクラス定義の中で関連する関数をOverloadすればおしまいですね。
RustでのOperator Overload
普通の演算子のOverloadでさえちょっと面倒なのにCall Operatorでは更に一手間必要です。というのもRustにはオブジェクトという概念が存在しないのでCall OperatorをもったTraitを実装してやることでOperator Overloadを実現するんですがそのためのTraitが三種類 (FnOnce
, FnMut
, Fn
) 存在します。そしてFnMut
の実装にはFnOnce
の実装が必要で、Fn
の実装にはFnMut
の実装が必要というなんとも面倒な仕様になっています。
use std::ops::{FnOnce, FnMut, Fn};
struct Foo {}
impl FnOnce for Foo {
type Output = (); // 返り値は無し
extern "rust-call" fn call_once(self, (arg,): (String,)) -> Self::Output {
println!("Hello {}!", arg);
}
}
impl FnMut for Foo {
extern "rust-call" fn call_mut(&mut self, (arg,): (String,)) -> Self::Output {
println!("Hello {}!", arg);
}
}
impl Fn for Foo {
extern "rust-call" fn call(&self, (arg,): (String,)) -> Self::Output {
println!("Hello {}!", arg);
}
}
self
が呼び出し時にconsumeされてもいいのであればFnOnce
だけ実装してあげればいいんですがそうでなく単純にImmutableなReferenceを貸したいだけの時はFnMut
とFn
を実装してやる必要があります。なんでこうなったのかはわかりませんがそういうものみたいです。めんどくさい。