Как работает пожизненная обработка для объектов-черт Fn с универсальными типами? - PullRequest
0 голосов
/ 16 января 2019

Следующий код завершается ошибкой, как и ожидалось:

struct T<'a> {
    f: &'a Fn(&'a i32),
}

fn test<'a>(_: &'a i32) {}

fn main() {
    let t = T { f: &test };

    {
        let v = 4;
        (t.f)(&v);
    }

    let v1 = 5;
    (t.f)(&v1);
}

выдает ошибку:

error[E0597]: `v` does not live long enough
  --> src/main.rs:12:15
   |
12 |         (t.f)(&v);
   |               ^^ borrowed value does not live long enough
13 |     }
   |     - `v` dropped here while still borrowed
...
16 |     (t.f)(&v1);
   |     ----- borrow later used here

Причина в том, что объекты признаков являются инвариантными по отношению к своим родовым типам. В результате 'a, во время разговора t.f() не будет сокращено для звонка. Я понял это. Однако, если я удалю следующий фрагмент кода:

let v1 = 5;
(t.f)(&v1);

код компилируется нормально, и я тоже это понимаю, так как считаю, что это связано с нелексическими временами жизни.

Вещи становятся интересными, когда я одалживаю t (или какое-то поле внутри него). Рассмотрим слегка модифицированную версию приведенного выше кода:

struct T<'a> {
    f: &'a Fn(&'a i32),
    i: i32,
}

fn test<'a>(_: &'a i32) {}

fn main() {
    let t = T { f: &test, i: 1 };
    let n = &t.i;

    {
        let v = 4;
        (t.f)(&v);
    }

    println!("{:?}", n);
}

Этот код компилируется, но я не понимаю, почему! Здесь t.i заимствовано, и в результате t заимствовано, и оно используется после окончания внутренней области видимости. Я ожидал, что это потерпит неудачу с вышеупомянутой ошибкой компилятора.

В качестве контрапункта следующий код занимает все t и не компилируется с тем же сообщением, что и выше.

struct T<'a> {
    f: &'a Fn(&'a i32),
    i: i32,
}

fn test<'a>(_: &'a i32) {}

fn main() {
    let t = T { f: &test, i: 1 };
    let n = &t;

    {
        let v = 4;
        (t.f)(&v);
    }

    println!("{:?}", n.i);
}

1 Ответ

0 голосов
/ 16 января 2019

На самом деле он работает так же, как и другие.

struct T<'a> {
    f: &'a Fn(&'a i32),
}

Это определение говорит, что функция f будет жить в 'a, и ее параметр должен также жить 'a.

Рассмотрим время жизни fn main() как 'm, а время жизни внутренней области видимости - 'i. Вы создаете свою структуру в main, поэтому ее время жизни будет 'm, оно будет жить в main, если вы не перейдете в другую область.

  1. В вашем первом примере аргумент, который вы передаете в t.f: создан в 'i. Согласно определению; аргумент и тому Функция должна жить в одной и той же жизни. Но 'm больше 'i. Так что это не приемлемо.
  2. Во втором примере все то же самое, но ваша структура фактически не переживает 'i. Это можно считать временем жизни f равным его аргументу. Так что это обеспечивает определение.
  3. Третий пример такой же с 1.

Если вы измените определение, как показано ниже, удалив ограничение из параметра. Таким образом, компилятор может определить подходящее время жизни аргумента, оно может быть больше, равно или меньше времени жизни.

//this means your f and it's argument's life time can be different
struct T<'a> {
    f: &'a Fn(&i32),
}

Все ваши примеры будут действительны.

Здесь вы можете найти рабочие примеры в Playground

...