【问题标题】:Rust function returning a closure: ``explicit lifetime bound required"Rust 函数返回一个闭包:“需要明确的生命周期限制”
【发布时间】:2014-11-16 00:18:12
【问题描述】:

以下代码无法编译。

fn main() {
    let foo = bar(8);

    println!("Trying `foo` with 4: {:d}", foo(4));
    println!("Trying `foo` with 8: {:d}", foo(8));
    println!("Trying `foo` with 13: {:d}", foo(13));
}

//

fn bar(x: int) -> (|int| -> int) {
    |n: int| -> int {
        if n < x { return n }

        x
    }
}

错误如下。

11:32 error: explicit lifetime bound required
.../hello/src/main.rs:11 fn bar(x: int) -> (|int| -> int) {
                                            ^~~~~~~~~~~~

我将整数参数按值传递给bar。为什么 Rust 关心按值传递的整数的生命周期?编写这样一个返回闭包的函数的正确方法是什么?谢谢。

编辑

我在手册中找到了以下内容。 In the simplest and least-expensive form (analogous to a || { } expression), the lambda expression captures its environment by reference, effectively borrowing pointers to all outer variables mentioned inside the function. Alternately, the compiler may infer that a lambda expression should copy or move values (depending on their type.) from the environment into the lambda expression's captured environment.

是否有关于编译器如何推断是否通过引用捕获外部变量、复制它们或移动它们的进一步规范?评估标准是什么,它们的应用顺序是什么?这是否记录在案(缺少阅读编译器的代码)?

【问题讨论】:

    标签: rust


    【解决方案1】:

    编辑:我将int 替换为i32,因为int 现在已被弃用。它已被替换为 isize,但这可能不是正确的类型。

    编译器没有抱怨闭包的参数;它抱怨关闭本身。您需要为闭包指定生命周期。

    fn bar<'a>(x: i32) -> (|i32|:'a -> i32) {
        |n: i32| -> i32 {
            if n < x { return n }
    
            x
        }
    }
    

    但它不起作用:

    <anon>:13:16: 13:17 error: captured variable `x` does not outlive the enclosing closure
    <anon>:13         if n < x { return n }
                             ^
    <anon>:11:41: 17:2 note: captured variable is valid for the block at 11:40
    <anon>:11 fn bar<'a>(x: i32) -> (|i32|:'a -> i32) {
    <anon>:12     |n: i32| -> i32 {
    <anon>:13         if n < x { return n }
    <anon>:14 
    <anon>:15         x
    <anon>:16     }
              ...
    <anon>:11:41: 17:2 note: closure is valid for the lifetime 'a as defined on the block at 11:40
    <anon>:11 fn bar<'a>(x: i32) -> (|i32|:'a -> i32) {
    <anon>:12     |n: i32| -> i32 {
    <anon>:13         if n < x { return n }
    <anon>:14 
    <anon>:15         x
    <anon>:16     }
              ...
    

    这是因为闭包试图通过引用捕获x,但闭包的寿命超过x,这是非法的(同样返回对x的引用也是非法的)。

    让我们尝试使用procproc 通过移动捕获值。

    编辑:proc 已从语言中删除,因为此答案最初是编写的。

    fn main() {
        let foo = bar(8);
    
        println!("Trying `foo` with 4: {:d}", foo(4));
        println!("Trying `foo` with 8: {:d}", foo(8));
        println!("Trying `foo` with 13: {:d}", foo(13));
    }
    
    //
    
    fn bar<'a>(x: i32) -> (proc(i32):'a -> i32) {
        proc(n: i32) -> i32 {
            if n < x { return n }
    
            x
        }
    }
    

    不幸的是,这也不起作用。

    <anon>:5:43: 5:46 error: use of moved value: `foo`
    <anon>:5     println!("Trying `foo` with 8: {:d}", foo(8));
                                                       ^~~
    note: in expansion of format_args!
    <std macros>:2:23: 2:77 note: expansion site
    <std macros>:1:1: 3:2 note: in expansion of println!
    <anon>:5:5: 5:51 note: expansion site
    <anon>:4:43: 4:46 note: `foo` moved here because it has type `proc(i32) -> i32`, which is non-copyable (perhaps you meant to use clone()?)
    <anon>:4     println!("Trying `foo` with 4: {:d}", foo(4));
                                                       ^~~
    

    您只能拨打proc 一次。调用会消耗闭包。

    现在正确的解决方案是使用“未装箱”的闭包:

    fn main() {
        let foo = bar(8);
    
        println!("Trying `foo` with 4: {}", foo(4));
        println!("Trying `foo` with 8: {}", foo(8));
        println!("Trying `foo` with 13: {}", foo(13));
    }
    
    //
    
    fn bar(x: i32) -> Box<Fn(i32) -> i32 + 'static> {
        Box::new(move |&: n: i32| -> i32 {
            if n < x { return n }
    
            x
        })
    }
    

    输出:

    Trying `foo` with 4: 4
    Trying `foo` with 8: 8
    Trying `foo` with 13: 8
    

    【讨论】:

    • 感谢您的详细解答。我的 Rust 版本是 rustc 0.12.0-nightly (af3889f69 2014-09-18 21:20:38 +0000)。它无法编译最终版本。我收到以下错误:error: internal compiler error: get_unique_type_id_of_type() - unexpected type: closure, ty_unboxed_closure(syntax::ast::DefId{krate: 0u32, node: 170u32}, ReScope(169u32)) note: the compiler hit an unexpected failure path. this is a bug.
    • 另外,如果我希望使用实验性功能,从函数返回此类闭包的最直接方法是什么?谢谢。
    • @sigma.ml:你可能想再次更新 rustc。最终版本对我来说很好用(rustc 0.12.0-nightly (d7e1bb5ff 2014-09-21 01:00:29 +0000))。
    • @KennyTM:我已经更新了我的 Rust 副本。当前版本:rustc 0.12.0-nightly (72841b128 2014-09-21 20:00:29 +0000)。我发现它和你的完全一样。使用上述版本,我继续像以前一样遇到上述编译器崩溃。我在Ubuntu 14.04 x86_64,如果提供任何其他信息。
    • @sigma.ml,未装箱的闭包是实验性功能,常规闭包不能从函数返回,因为它们是堆栈分配的。因此,您将不得不等到未装箱的闭包不再是实验性的,或者您可以使用常规结构来模拟它们。毕竟,未装箱的闭包只是实现某些特征的匿名结构。
    【解决方案2】:

    是否有关于编译器如何推断是否通过引用捕获外部变量、复制它们或移动它们的进一步规范?评估标准是什么,它们的应用顺序是什么?这是否记录在案(缺少阅读编译器的代码)?

    让我补充弗朗西斯的回答:

    |x| a*x+b 这样的闭包总是通过引用来捕获它们的周围环境(如ab 这里)。在您的情况下,这些是函数局部变量,Rust 会阻止您返回这样的闭包,因为这些函数局部变量将不再存在。让我们感谢借阅检查器发现了这个错误。这些闭包的用例通常是将它们作为参数传递给其他函数,而不是返回它们。但是,如果您不访问任何其他变量,则允许这样的闭包比创建它的函数的范围更长:||:'static -&gt; SomeType。这些闭包的表示只是一对指针。一个指向函数,一个指向函数的堆栈帧(如果通过引用捕获了某些内容)。

    您使用proc 编写的闭包总是通过“获取”它们来捕捉周围的环境(它们被移动到闭包对象的状态)。这类闭包的另一个特性是您只能调用它们一次,因为相关函数实际上消耗闭包的状态。这对于启动并发任务很有用。 proc 闭包会产生堆分配成本,因为它们间接存储状态(类似于 Box 所做的)。这样做的好处是proc 闭包的表示(忽略装箱状态)只是一对指针。一个指向函数的指针,一个指向装箱变量的指针。

    然后,有所谓的未装箱闭包。据我所知,未装箱的闭包仍然被认为是实验性的。但是它们允许您完全按照您的意愿去做——不是直接地,而是在装箱时。未装箱的闭包也可以通过价值捕捉周围的环境。但与proc 闭包不同,它不涉及堆分配。他们直接存储变量。您可以将它们视为具有唯一的、不可拼写的类型的结构,它实现了以下特征之一:FnFnMutFnOnce,其中一个方法将其自身参数作为&amp;self&amp;mut selfself 分别。未装箱的闭包很好,因为它们缺少函数和变量的间接级别,这允许更好的内联并因此获得更好的性能。但目前还不可能编写一个直接返回这种未装箱闭包的函数。一种解决方案是像 Francis 在此答案的最后一部分中显示的那样对未装箱的闭包进行装箱。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-10
      • 2018-03-21
      • 2019-12-27
      • 1970-01-01
      • 2018-07-28
      • 1970-01-01
      • 2021-05-31
      • 2022-11-21
      相关资源
      最近更新 更多