如何在从闭包返回的迭代器中包含可变引用的移动闭包内捕获它?

3

我有一个伪随机数生成器,我希望允许闭包通过可变引用来访问它。理论上,所有的生命周期应该都能够协调一致,这是它的样子:

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));

在这里,一个闭包正在创建一个迭代器,用于包装PRNG生成的值。此迭代器包含一个map和一个闭包,该闭包具有克隆到其中的wrap范围x,但问题在于它无意中也克隆了rng,我已经验证。必须使它成为移动闭包,因为必须捕获x的值,否则闭包将超时x。
我尝试添加此行以强制将引用移动到闭包中:
let rng = &mut rng;

然而,Rust 报告了以下错误:
error: cannot move out of captured outer variable in an `FnMut` closure

我能否在move闭包内可变访问PRNG?如果不能,由于PRNG显然比函数调用生存周期更长,是否有其他解决方案(除了重新设计API)?

编辑:

我已经重写了它以删除复制问题,并且调用看起来像这样:

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

这会导致一个新的错误:
error: cannot infer an appropriate lifetime for autoref due to conflicting requirements

1
顺便提一下,但是请不要使用取模来获取范围内的随机数! rand crate 有一个针对这种情况的“范围内随机数”函数。 - Shepmaster
1
@Shepmaster 啊,谢谢。我熟悉偏差问题,但我没有意识到 Rng 暴露了一个接口来轻松解决这个问题!最好查看我使用的特征公开的所有函数。我会假设它是 rand::Rng::gen_range() - vadix
是的,我太懒了,没去找那个函数 ^_^。还有长版本,但gen_range更易懂。 - Shepmaster
@Shepmaster 标准库中是否包含类似于gen_iter的机制来获取迭代器以生成范围内的值?我非常喜欢Rust迭代器,并且正在尝试找到有效使用它们解决问题的方法。我注意到了SampleRange特征,也注意到了Generator迭代器,但似乎没有办法获取调用Rng上的gen_range()而不是gen()的生成器。 - vadix
1
我建议你在这里的SO上提出一个新问题;这样其他人将来就更容易找到相同的东西!^_^ - Shepmaster
1个回答

6
你遇到了需要多个相互冲突的可变借用的情况,Rust编译器正在拒绝执行此操作,这是正确的行为。我们需要理解为什么会发生这种情况以及原因。
一个重要的注记:
- `Isaac64Rng` 实现了 `Copy`,这意味着它隐式地复制而不是只是移动。我假设这是一种遗留/向后兼容的事情。
我编写了这个版本的代码来更好地理解:
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)
    });
}

让我用几个要点来说明:
  • someFunction需要一个可调用的闭包,它每次被调用都返回一个迭代器。这个闭包是可变的并且可以被多次调用 (FnMut)。

  • 我们必须假设所有返回的迭代器可以同时使用,而不是按顺序(一次一个)。

  • 我们想要将Rng借用到迭代器中,但可变借用是排他的。因此,借用规则不允许同时存在多个迭代器。

  • FnOnce而不是FnMut就是一个闭包协议的例子,可以帮助我们解决这个问题。它会让rustc看到只有一个迭代器。

在没有(##)这行代码的工作版本中,你可以同时拥有多个活动的迭代器,那里发生了什么?这是隐式复制起作用,所以每个迭代器将使用原始Rng的相同副本(听起来不太理想)。

你的代码的第二个版本遇到了基本相同的限制。

如果你想避免借用的互斥性,你可以使用特殊容器如RefCellMutex来序列化对Rng的访问。


4
假设所有返回的迭代器可以同时使用。这是我个人认为未来调试这类问题的关键所在。 - Shepmaster
@Shepmaster 谢谢你强调这一点。这就是我的误解所在。幸运的是,将其更改为 FnOnce 是令人满意的。 - vadix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接