Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to capture mutable reference into move closure contained in iterator returned from a closure

Tags:

closures

rust

I have a PRNG that I would like to allow a closure to access by mutable reference. The lifetimes of everything should theoretically be able to work out, here is what it looks like:

fn someFunction<F, I>(mut crossover_point_iter_generator: F)
        where F: FnMut(usize) -> I, I: Iterator<Item=usize>;

let mut rng = Isaac64Rng::from_seed(&[1, 2, 3, 4]);
someFunction(|x| (0..3).map(move |_| rng.gen::<usize>() % x));

Here, a closure is creating an iterator that wraps PRNG generated values. This iterator contains a map with a closure that has the wrap range x cloned into it, but the problem is that it unintentionally clones rng as well, which I have verified. It is necessary to make it a move closure because the value of x must be captured, otherwise the closure will outlive x.

I attempted to add this line to force it to move the reference into the closure:

let rng = &mut rng;

However, Rust complains with this error:

error: cannot move out of captured outer variable in an `FnMut` closure

Can I mutably access the PRNG from inside the move closure, and if not, since the PRNG clearly outlives the function call, is there an alternative solution (aside from redesigning the API)?

Edit:

I have rewritten it to remove the copy issue and the call looks like this:

someFunction(|x| rng.gen_iter::<usize>().map(move |y| y % x).take(3));

This results in a new error:

error: cannot infer an appropriate lifetime for autoref due to conflicting requirements
like image 249
vadix Avatar asked Oct 25 '25 18:10

vadix


1 Answers

You're got a situation that requires multiple conflicting mutable borrows, and rustc is denying this as it should. It's just up to us to understand how & why this happens!

A note that will be important:

  • Isaac64Rng implements Copy, which means that it implicitly copies instead of just moving. I'm assuming is is a legacy / backwards compatibility thing.

I wrote this version of the code to get it straight:

extern crate rand;

use rand::Isaac64Rng;
use rand::{Rng, SeedableRng};

fn someFunction<F, I>(crossover_point_iter_generator: F)
    where F: FnMut(usize) -> I, I: Iterator<Item=usize>
{
    panic!()
}

fn main() {
    let mut rng = Isaac64Rng::from_seed(&[1, 2, 3, 4]);
    let rng = &mut rng;  /* (##) Rust does not allow. */
    someFunction(|x| {
        (0..3).map(move |_| rng.gen::<usize>() % x)
    });
}

Let me put this in points:

  • someFunction wants a closure it can call, that returns an iterator each time it's called. The closure is mutable and can be called many times (FnMut).

  • We must assume all the returned iterators can be used at the same time, and not in sequence (one at a time).

  • We would like to borrow the Rng into the iterator, but mutable borrows are exclusive. So borrowing rules do not allow more than one iterator at a time.

  • FnOnce instead of FnMut would be one example of a closure protocol to help us here. It would make rustc see that there can be only one iterator.

In the working version, without the line (##), you have several iterators active at the same time, what's happening there? It's the implicit copying kicking in, so each iterator will use an identical copy of the original Rng (sounds undesirable).

Your second version of the code runs into essentially the same limitation.

If you want to work around the exclusivity of borrowing, you can use special containers like RefCell or Mutex to serialize access to the Rng.

like image 185
bluss Avatar answered Oct 27 '25 15:10

bluss