【问题标题】:Mutating a variable in the body of a closure改变闭包体中的变量
【发布时间】:2017-01-03 22:43:20
【问题描述】:

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

来自 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 作为第一个参数传递。

【问题讨论】:

    标签: rust closures


    【解决方案1】:

    他们的关键是将闭包定义为 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.
    

    【讨论】:

    • 必须限定它的范围并不理想,(我知道并没有说清楚)因为代码交错了推送和断言。 IE。它推送一些值,然后通过弹出断言如果它为真,然后推送更多值,等等。
    • 我一般不会按照你的描述去做。对于读者来说,这听起来很混乱。 assert_eq 有什么不好!(list.pop(), Some(3)); ?
    • 如果你真的,真的想这样做,你可以写一个宏,虽然这同样令人困惑,imo:macro_rules! assert_pop { ($expected:expr) => { assert_eq!(v.pop(), $expected); } }
    • 是的,我想这是为了简洁而不是清晰,这并不总是最好的
    • 如果您使用宏,请注意,由于宏卫生,您必须在声明列表变量之后在函数内声明宏内联。
    【解决方案2】:

    我不会直接回答您的问题(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;
    }
    

    【讨论】:

      猜你喜欢
      • 2014-06-12
      • 1970-01-01
      • 1970-01-01
      • 2012-11-21
      • 1970-01-01
      • 2015-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多