为什么在闭包中调用函数与直接在Rust中调用函数相比,生存期有所不同?

时间:2018-11-06 11:36:06

标签: rust

在下面的代码示例中:

fn default_values() -> &'static [u32] {
    static VALUES: [u32; 3] = [1, 2, 3];
    &VALUES
}

fn main() {
    let values: [u32; 3] = [4, 5, 6];
    let optional_values: Option<&[u32]> = Some(&values);

    // this compiles and runs fine 
    let _v = optional_values.unwrap_or_else(|| default_values());

    // this fails to compile
    let _v = optional_values.unwrap_or_else(default_values);
}

最后一条语句无法编译为:

error[E0597]: `values` does not live long enough
  --> src/main.rs:8:49
   |
8  |     let optional_values: Option<&[u32]> = Some(&values);
   |                                                 ^^^^^^ borrowed value does not live long enough
...
12 | }
   | - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

我想知道:

  1. 正在发生的事情导致最后两个语句之间的行为差​​异
  2. 第一个unwrap_or_else(|| default_values())是否是处理此问题的正确方法,或者是否有更好的模式

2 个答案:

答案 0 :(得分:5)

发生这种情况是因为default_values实现了Fn() -> &'static [u32],但没有实现for<'a> Fn() -> &'a [u32]。特质是invariant,因此您不能将“实现Fn() -> &'static [u32]的东西强迫为”实现Fn() -> &'a [u32]的东西”(某些'a小于'static ),尽管从逻辑上讲,default_values可以满足这两个条件。

在闭包中调用时,default_values()返回一个&'static [u32],但是可以立即将其强制转换为&'a u32,从而使闭包本身能够实现Fn() -> &'a [u32]&'a由编译器确定的位置。

关于为什么添加as fn() -> &'static [u32]的原因,我假设编译器可以识别出函数指针类型fn() -> &'static [u32]能够为任何Fn() -> &'a [u32]实现'a。我不确定为什么它对普通的函数和闭包也不会这样做;也许将来的编译器版本可能足够聪明以允许使用原始代码。

另一种解决方案是使default_values的类型可以实现您需要的Fn特征:

fn default_values<'a>() -> &'a [u32] {
    static VALUES: [u32; 3] = [1, 2, 3];
    &VALUES
}

这里的签名不是说“这是一个返回'static引用的函数”,而是说“这是一个可以返回任何生存期的引用的函数”。我们知道“任何生命周期的引用”都必须是'static引用,但是编译器认为签名是不同的,因为该签名具有附加的自由度。 This change is sufficient to make your original example compile.

答案 1 :(得分:2)

闭包和直接函数调用之间没有区别:这只是类型推断的问题。

编译的闭包

let _v = optional_values.unwrap_or_else(|| default_values());
let _v = optional_values.unwrap_or_else(|| -> & [u32] {default_values()});

无法编译的封包:

let _v = unwrap_or_else(optional_values, || -> &'static [u32] {default_values()});

编译功能:

let _v = unwrap_or_else(optional_values, default_values as fn() -> &'static _);

无法编译的功能:

let _v = unwrap_or_else(optional_values, default_values);

一点点解释

考虑以下等效代码:

fn default_values() -> &'static [u32] {
    static VALUES: [u32; 3] = [1, 2, 3];
    &VALUES
}

fn unwrap_or_else<T, F>(slf: Option<T>, f: F) -> T where
    F: FnOnce() -> T, {
        match slf {
            Some(t) => t,
            None => f()
        }
    }

以下代码段:

fn main() {
    let values: [u32; 3] = [4, 5, 6];
    let optional_values: Option<&[u32]> = Some(&values);

    let _v = unwrap_or_else(optional_values, || -> &'static [u32] {default_values});

    // the above throws the same error of:
    //let _v = unwrap_or_else(optional_values, default_values);
}

失败:

error[E0597]: `values` does not live long enough
  --> src/main.rs:18:48
   |
18 |     let optional_values: Option<&[u32]> = Some(&values);
   |                                                ^^^^^^^
   |                                                |
   |                                                borrowed value does not live long enough
   |                                                cast requires that `values` is borrowed for `'static`
...
27 | }
   | - `values` dropped here while still borrowed

从单色化方面看:假设 编译器推断T解析为具体类型&'static [u32], 并假设产生的代码是这样的:

fn unwrap_or_else_u32_sl_fn_u32_sl(slf: Option<&'static [u32]>,
                                   f: fn() -> &'static [u32]) -> &'static [u32] {
    ...
}

然后以上的单色化解释了错误:

slf的值为optional_values:寿命不足且显然不能强制转换的Option<&'a [u32]>,因为它不满足'static的生存期要求。

如果您写:

let _v = unwrap_or_else(optional_values, || default_values());

// the same, expliciting the return type:
let _v = unwrap_or_else(optional_values, || -> & [u32] {default_values()});

它可以编译:现在返回类型的生存期与optional_values生存期兼容。

最后,我无法解释为什么,但是证据表明强制转换as fn() -> &'static _帮助编译器确保绑定到optional_valuesdefault_values的去耦生存期是安全的