RustでのMutable referenceのキャッシュに関して


お世話様です。

Rustの習作をかねて以下のようなコードを書いております。

環境は以下の通りです。

  * nightly-x86_64-pc-windows-gnu (default)
  * rustc 1.74.0-nightly (62ebe3a2b 2023-09-08)

[code]

    trait Generator
    {
        type T;
        fn generate(&mut self)->Self::T;
    }
    
    
    trait Distributor{
        type T;
        type U;
        type G:Generator<T=Self::U>;
        fn generator(&mut self)->&mut Self::G;
    
        fn toss(&mut self)->Self::T
        {
            let mut gen=self.generator();
        
            loop{
                let candidate=gen.generate();
        
                return match self.conv(candidate) {
                    None => continue,
                    Some(s) => s
                };
            }
        }
    
        fn conv(&self,scr:Self::U)->Option<Self::T>;
    }
    
    fn main() {
    }
    
    
[/code]

概略`Generator`にて得た値を`Distributor`で変換して値を得るという使用用途を想定しております。  
さてこのとき、`conv`で受容できる値域以外では`None`を返却することで再び`generate`関数を呼びだし、成功するまでループするよう`toss`関数のデフォルト実装を行いました。  
これをコンパイルしようとしたところ、当然`let mut gen=self.generator();`でmutable
borrowが発生しその後ループの内方で`self.conv`を呼び出したためborrow
checkではじかれてエラーになりました。このコンパイルエラーの意図も理由も理解は出来るモノであります。  
また、以下のように書き直すことで問題なくビルドできることも理解できております。

[code]

        fn toss(&mut self)->Self::T
       {
           loop{
               let candidate=self.generator().generate();
    
               return match self.conv(candidate) {
                   None => continue,
                   Some(s) => s
               };
           }
       }
    
    
[/code]

上記のような実装を行った場合、基本的には問題はないと思われます。しかしながら、`self.generator()`の処理が複雑で有った場合、余計なオーバーヘッドコストが発生する場合を考えると余り望ましくないシナリオの発生を予見できることもまた事実です。

このような場合、例えば`Generator`を`RefCell`で包んでしまうなどの方策は思い浮かんだのですが、他に妙案が有ればご教授いただきたく質問させていただきました。

以上よろしく御願い致します。




解決策のひとつとして、`Generator`を`toss`メソッドの引数として渡す方法があります。

[code]

    trait Generator {
        type T;
        fn generate(&mut self) -> Self::T;
    }
    
    trait Distributor {
        type T;
        type U;
    
        fn toss(&mut self, gen: &mut impl Generator<T = Self::U>) -> Self::T {
            loop {
                let candidate = gen.generate();
    
                match self.conv(candidate) {
                    None => continue,
                    Some(s) => return s,
                };
            }
        }
    
        fn conv(&self, scr: Self::U) -> Option<Self::T>;
    }
    
    fn main() {}
    
[/code]

たとえばrandクレートの`Distribution`トレイトはそういうやり方をしていて、`Rng`（Random Number
Generator）を引数にとります。

<https://rust-random.github.io/book/guide-dist.html>

[code]

    pub trait Distribution<T> {
        fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T;
    
        // ...
    }
    
[/code]

> `Generator`にて得た値を`Distributor`で変換して値を得るという使用用途を想定しております。

ちなみに、Rustですと、そういう場合は`Iterator`を使うのが一般的かもしれません。

[code]

    // 1ずつ増加する自然数を無限に生成するジェネレーター（イテレーター）
    let gen = (0..).into_iter();
    
    // 3の倍数なら`None`を返し、それ以外は`Some(倍数)`を返すコンバーター
    let conv = |x| if x % 3 == 0 { None } else { Some(x * 2) };
    
    // コンバーターを使ってジェネレーターから値を取り出すディストリビューター（イテレーター）
    let dist = gen.filter_map(conv);
    
    // ジェネレーターから最初の10個の値を取り出しながら表示する
    for x in dist.take(10) {
        println!("{x}");
    }
    
[/code]

randクレートの`Distribution`トレイトにも`Iterator`を返すメソッドが実装されています。それを使うと`Iterator`が提供するメソッドを後ろに繋げていくことができます。

[code]

    pub trait Distribution<T> {
        // a convenience function defined using sample:
        fn sample_iter<R>(self, rng: R) -> DistIter<Self, R, T>
        where
            Self: Sized,
            R: Rng,
        {
            // [has a default implementation]
        }
    }
    
[/code]




解決策のひとつとして、`Generator`を`toss`メソッドの引数として渡す方法があります。

[code]

    trait Generator {
        type T;
        fn generate(&mut self) -> Self::T;
    }
    
    trait Distributor {
        type T;
        type U;
    
        fn toss(&mut self, gen: &mut impl Generator<T = Self::U>) -> Self::T {
            loop {
                let candidate = gen.generate();
    
                match self.conv(candidate) {
                    None => continue,
                    Some(s) => return s,
                };
            }
        }
    
        fn conv(&self, scr: Self::U) -> Option<Self::T>;
    }
    
    fn main() {}
    
[/code]

たとえばrandクレートの`Distribution`トレイトはそういうやり方をしていて、`Rng`（Random Number
Generator）を引数にとります。

<https://rust-random.github.io/book/guide-dist.html>

[code]

    pub trait Distribution<T> {
        fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T;
    
        // ...
    }
    
[/code]

> `Generator`にて得た値を`Distributor`で変換して値を得るという使用用途を想定しております。

ちなみに、Rustですと、そういう場合は`Iterator`を使うのが一般的かもしれません。

[code]

    // 1ずつ増加する自然数を無限に生成するジェネレーター（イテレーター）
    let gen = (0..).into_iter();
    
    // 3の倍数なら`None`を返し、それ以外は`Some(倍数)`を返すコンバーター
    let conv = |x| if x % 3 == 0 { None } else { Some(x * 2) };
    
    // コンバーターを使ってジェネレーターから値を取り出すディストリビューター（イテレーター）
    let dist = gen.filter_map(conv);
    
    // ジェネレーターから最初の10個の値を取り出しながら表示する
    for x in dist.take(10) {
        println!("{x}");
    }
    
[/code]

randクレートの`Distribution`トレイトにも`Iterator`を返すメソッドが実装されています。それを使うと`Iterator`が提供するメソッドを後ろに繋げていくことができます。

[code]

    pub trait Distribution<T> {
        // a convenience function defined using sample:
        fn sample_iter<R>(self, rng: R) -> DistIter<Self, R, T>
        where
            Self: Sized,
            R: Rng,
        {
            // [has a default implementation]
        }
    }
    
[/code]

