改变闭包体内的变量

时间:2017-01-03 22:43:20

标签: rust closures

我正在编写一堆断言,这些断言都涉及从列表中弹出一个值。

来自Scala背景,我自然而然地这样做了:

let mut list = List::new();
let assert_pop = |expected| assert_eq!(list.pop(), expected);

这样我就可以写assert_pop(None)assert_pop(Some(3)),而不必每次都写assert_eq!(list.pop(), None)assert_eq!(list.pop(), Some(3))

当然借用检查器不喜欢这一点,因为闭包基本上需要在未公开的时间内借用该值,而我的其余代码会发生变异,从而违反的规则没有别名,如果你正在改变“

问题是:有没有办法解决这个问题?我是否必须编写一个宏,或者是否有一种时髦的内存安全方式可以解决这个问题?

注意我知道我可以像这样定义闭包:

let_assert_pop = |lst: &mut List, expected| assert_eq!(lst.pop(), expected);

但那不会是 DRY ,因为我必须在每个电话中传入&mut list作为第一个参数。

2 个答案:

答案 0 :(得分:2)

他们的关键是将闭包定义为mut ,因为它需要一个可变的引用。

这有效:

let mut v = vec![1, 2];
let mut assert_pop = |expected| assert_eq!(v.pop(), expected);

assert_pop(Some(2));
assert_pop(Some(1));
assert_pop(None);

请注意,pop关闭可以随意借用,所以如果您想在之后使用该列表,则必须使用它:

let mut v = vec![1,2];
{
    let mut assert_pop = |expected| assert_eq!(v.pop(), expected);
    assert_pop(Some(2));
    v.push(33); // ERROR: v is borrowed mutably...
}
v.push(33); // Works now, since pop is out of scope.

答案 1 :(得分:1)

我不是直接回答你的问题(well-enough answered already),而是解决你的其他问题:

  • 只需撰写assert_pop(None)assert_pop(Some(3))
  • 即可
  • 内存安全方式解决方案
  • 不传递&mut list

要解决所有问题,请不要使用闭包,只需创建一个新类型:

type List<T> = Vec<T>;

struct Thing<T>(List<T>);

impl<T> Thing<T> {
    fn assert_pop(&mut self, expected: Option<T>) 
        where T: PartialEq + std::fmt::Debug,
    {
        assert_eq!(self.0.pop(), expected);
    }
}

fn main() {
    let list = List::new();
    let mut list = Thing(list);

    list.0.push(1);
    list.assert_pop(Some(1));
    list.assert_pop(None);

    // Take it back if we need to
    let _list = list.0;
}