在闭包中调用堆上的函数参数

4

我正在使用 Rust 0.8 版本。

为什么我可以这样做:

fn add(num: ~int) -> ~fn(int) -> int { |x|
    *num + x
}

但不包括这个:

fn outer(num: ~int) -> ~fn(int) -> int { |x|
    *inner(num) + x
}

fn inner(num: ~int) -> ~int {
    num
}

第二个错误提示为:“error: cannot move out of captured outer variable in a heap closure”。调用函数有何特殊之处?
问题在于内部函数可能会对包装后的函数进行脏操作,而静态分析无法捕捉到。

2
不是一个合适的答案,所以我会将它作为评论添加进去:https://github.com/mozilla/rust/wiki/Doc-under-construction-FAQ - Ercan Erden
1个回答

3
问题在于闭包可能被调用两次。在第一次运行时,捕获的变量num被移动到inner中,也就是说,它被移出了闭包的环境。然后,在第二次调用时,num原来所在的位置现在已经无效(因为它已经被移出去了),导致内存安全性受到破坏。
更详细地说,可以将闭包视为(近似)
struct Closure { // stores all the captured variables
    num: ~int
}

impl Closure {
    fn call(&self, x: int) -> int { 
        // valid:
        *self.num + x

        // invalid:
        // *inner(self.num) + x
    }
}

希望这能让问题更加清晰:在无效的代码中,一个人试图将self.num从借用指针后面移出到inner调用中(之后它完全与num字段断开连接)。如果这是可能的,那么self将处于无效状态,因为例如,在self.num上的析构函数可能已被调用,释放内存(违反内存安全性)。
其中一种解决方法是“一次性函数”,它们已经实现,但被隐藏在编译器标志后面,因为它们可能会被删除,以支持(最基本的)调整上述call类型为fn call(self, x: int),即调用闭包移动self,这意味着您可以移出环境(因为call现在拥有self及其字段),并且也意味着该函数静态保证仅被调用一次*。

*如果闭包的环境不移动所有权,则不完全正确,例如如果它是struct Env { x: int }


似乎只要我将函数放入的闭包具有相同的生命周期,就不必关心函数的使用方式。为什么我不能添加生命周期参数使其正常工作?除非它是可变的且不是幂等的,否则多次调用函数并没有任何固有的危险。此外,为什么将函数移动到内部会将其移出闭包的环境?既然我知道闭包何时被释放,那么它应该保留在相同的环境中才对。 - nnythm
问题不在于闭包,而在于 numnum 的捕获被移动了,而 ~ 指针将在第一次调用后被释放(因为它被移动到 inner 中,所以闭包的环境失去了对 num 的控制和所有权,即它不能“选择”何时丢弃 num)。这意味着第二个(以及第三个、第四个……)将尝试使用已经释放的内存来处理 num - huon

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