Почему времена жизни отличаются, когда функция вызывается в замыкании и вызывается непосредственно в Rust? - PullRequest
0 голосов
/ 06 ноября 2018

В следующем примере кода:

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 голосов
/ 08 ноября 2018

Нет разницы между замыканием и прямым вызовом функции: это просто вопрос вывода типа.

замыкание, которое компилируется:

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, безопасно.

0 голосов
/ 06 ноября 2018

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...