2

I'm trying to wrap some logic common to many Monte Carlo calculations in a Mutator, which contains a set of possible random Mutations that can be applied to a Configuration struct. Each Mutation generates a Demutation operation that reverts it. While the Mutation requires a random number generator to operate, the Demutation is fully deterministic and does not.

Here's a very minimal definition of the relevant components:

use rand::Rng;

type MutatorResult<T> = Result<T, ()>;

trait Configuration {}

trait Mutation<C: Configuration> {
    fn execute(&self, configuration: &mut C, rng: &mut impl Rng) -> MutatorResult<Box<dyn Demutation<C>>>;
}

trait Demutation<C: Configuration> {
    fn execute(&self, c: &mut C) -> MutatorResult<()>;
}

A simple Configuration might look like this, for example:

struct ConfigurationImpl {
    x: f64,
}

impl ConfigurationImpl {
    fn displace(&mut self, shift: f64) {
        self.x += shift;
    }

    fn random_displace_right(&mut self, rng: &mut impl Rng) -> impl Fn(&mut Self) {
        let shift: f64 = rng.gen();
        self.displace(shift);
        move |c: &mut ConfigurationImpl| c.displace(-shift)
    }
}

impl Configuration for ConfigurationImpl {}

Now, when I try to implement a Mutation, I run into a problem like this:

struct MyDemutation {
    undo_closure: Box<dyn Fn(&mut ConfigurationImpl)>
}

impl Demutation<ConfigurationImpl> for MyDemutation {
    fn execute(&self, c: &mut ConfigurationImpl) -> MutatorResult<()> {
        (self.undo_closure)(c);
        Ok(())
    }
}

struct MyMutation {}

impl Mutation<ConfigurationImpl> for MyMutation {
        fn execute(&self, configuration: &mut ConfigurationImpl, rng: &mut impl Rng) -> MutatorResult<Box<dyn Demutation<ConfigurationImpl>>> {
            // This works:
            // let undo_closure = Box::new({
            //     let shift: f64 = rng.gen();
            //     configuration.displace(shift);
            //     move |c: &mut ConfigurationImpl| c.displace(-shift)
            // });
        
            // But this doesn't compile:
            let undo_closure = Box::new(configuration.random_displace_right(rng));
            
            Ok(Box::new(MyDemutation { undo_closure }))
        }
}

The result is:

error[E0310]: the parameter type `impl Rng` may not live long enough
  --> src/lib.rs:94:40
   |
83 | ...guration: &mut ConfigurationImpl, rng: &mut impl Rng) -> MutatorResult<Box<dyn Demutation<ConfigurationImpl>>> {
   |                                                -------- help: consider adding an explicit lifetime bound...: `impl Rng + 'static`
...
94 | ...ation { undo_closure }))
   |            ^^^^^^^^^^^^ ...so that the type `impl Fn(&mut ConfigurationImpl)` will meet its required lifetime bounds

For more information about this error, try `rustc --explain E0310`.

How do I tell Rust that the lifetime of the Rng reference is not at all relevant here?

This Playground link contains the code, and a bit more code for the Mutator itself, where I was able to rely on the answer to this question to decouple the random number generator from the struct definitions.

gc18003960
  • 21
  • 3

1 Answers1

0

Eventually solved this myself. The key was to add a "leave me alone, none of these should be linked" lifetime signature to the execute function, and work it out from there. Thanks to anyone who may have tried to help!

trait Mutation<C: Configuration> {
    fn execute<'a,'b,'c>(&'a self, configuration: &'b mut C, rng: &'c mut impl Rng) -> MutatorResult<Box<dyn Demutation<C> + 'static>>;
}
gc18003960
  • 21
  • 3
  • It seems odd that that changes the meaning, because that seems like it ought to be equivalent to the form with elided lifetimes! – Kevin Reid Jan 24 '22 at 15:55
  • @KevinReid Not exactly - the actual signature should add `'d` with `rng: &'c mut (impl Rng + 'd)`, and if you do it does error. – Chayim Friedman Jan 25 '22 at 18:35