💨

[小技] Rustでマーカーを使用して引数のオーバーロードもどきを実現させる方法

2024/05/06に公開

以下のような状況を解決するための小技を記載します。

  1. コールバックを受け取る関数functionが1つ存在
  2. コールバックには引数の有無を問わない

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