💨
[小技] Rustでマーカーを使用して引数のオーバーロードもどきを実現させる方法
以下のような状況を解決するための小技を記載します。
- コールバックを受け取る関数
function
が1つ存在 - コールバックには引数の有無を問わない
fn function(f: impl FnOnce(usize)){
}
fn main(){
// コンパイルできる
function(|input: usize|{
})
// コンパイルできない
function(||{
});
}
上記は専用のトレイトを定義することで解決できます。
ポイントはマーカー型(Marker
)を追加していることです。
これがない場合トレイトの実装の衝突が発生し、コンパイルエラーになります。
なぜ衝突が発生しないかというとF: FnOnce()
はFun<()>
を実装し、F: FnOnce(usize)
はFun<bool>
を実装しており、Fun<()>
とFun<bool>
は別々のトレイトとみなされる(多分)ためです。
そのためMarker
の型は衝突しなければなんでも大丈夫です。
trait Fun<Marker>{
fn fun(self, input: usize);
}
impl<F> Fun<()> for F
where
F: FnOnce() + 'static
{
fn fun(self, _input: usize) {
(self)()
}
}
impl<F> Fun<bool> for F
where
F: FnOnce(usize) + 'static
{
fn fun(self, input: usize) {
(self)(input)
}
}
fn function<Marker>(f: impl Fun<Marker>){
f.fun(0)
}
fn main(){
function(||{
});
function(|input: usize|{
})
}
Discussion