Почему времена жизни отличаются, когда функция вызывается в замыкании и вызывается непосредственно в 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()) правильным способом обработки этого или есть лучший образец

Ответ 1

Это происходит потому, что default_values реализует Fn() → &'static [u32], но не for<'a> Fn() → &'a [u32]. Черты являются инвариантными, поэтому вы не можете принудить "что-то, что реализует Fn() → &'static [u32] ", к "чему-то, что реализует Fn() → &'a [u32] " (для некоторых 'a меньше чем 'static), хотя, логически говоря, default_values может удовлетворить оба.

Когда он &'static [u32] в замыкании, 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:

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

Вместо того, чтобы сказать "это функция, которая возвращает 'static ссылку ", подпись здесь говорит:" Это функция, которая может возвращать ссылку любого времени жизни ". Мы знаем, что "ссылка на любое время жизни" должна быть 'static ссылкой", но компилятор видит сигнатуры как разные, потому что эта имеет дополнительную степень свободы. Этого изменения достаточно, чтобы ваш исходный пример компилировался.

Ответ 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_values и default_values безопасен.