Как передать функцию, которая создает структуру, содержащую ячейку со временем жизни, другой функции? - PullRequest
0 голосов
/ 28 июня 2018

Я пытаюсь передать функцию конструктора в качестве аргумента другой функции. Функция создает структуру со связанным временем жизни. Мне нужно создать структуру из этого указателя после Я создал несколько других объектов, на которые эта структура может затем ссылаться. Пример ниже, кажется, работает:

struct Bar<'a> {
    number: Option<&'a usize>,
}

impl<'a> Bar<'a> {
    pub fn new() -> Bar<'a> {
        Bar { number: None }
    }
}

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let mut bar = (func)();
    bar.number = Some(&number);
}

fn main() {
    foo(&Bar::new);
}

Когда я добавляю Cell для внутренней изменчивости, он не компилируется:

use std::cell::Cell;

struct Bar<'a> {
    number: Cell<Option<&'a usize>>,
}

impl<'a> Bar<'a> {
    pub fn new() -> Bar<'a> {
        Bar {
            number: Cell::new(None),
        }
    }
}

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = (func)();
    bar.number.set(Some(&number));
}

fn main() {
    foo(&Bar::new);
}

Дает мне следующую ошибку:

error[E0597]: `number` does not live long enough
  --> src/main.rs:21:26
   |
21 |     bar.number.set(Some(&number));
   |                          ^^^^^^ borrowed value does not live long enough
22 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 15:1...
  --> src/main.rs:15:1
   |
15 | / fn foo<'a, F>(func: &F)
16 | | where
17 | |     F: Fn() -> Bar<'a>,
18 | | {
...  |
21 | |     bar.number.set(Some(&number));
22 | | }
   | |_^

Почему первый пример работал, а не второй? Есть ли способ указать время жизни, существующее для области действия let mut bar до конца функции, а не 'a, которое охватывает всю функцию? Разве это невозможно без Нелексических Времени Жизни или Конструкторов Высокого Вида и т. Д.?

1 Ответ

0 голосов
/ 28 июня 2018

Компилятор умнее, чем вы считаете. помешал вам ввести небезопасную память :

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = (func)();
    bar.number.set(Some(&number));
}

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

fn b() -> Bar<'static> {
    Bar {
        number: Cell::new(None),
    }
}

fn main() {
    foo(&b);
}

Обратите внимание, что это не имеет ничего общего с замыканиями или функциями:

use std::cell::Cell;

fn main() {
    let number = Cell::new(None);
    let x = 1;
    number.set(Some(&x));
    let y = 2;
    number.set(Some(&y));
}
error[E0597]: `x` does not live long enough
 --> src/main.rs:6:22
  |
6 |     number.set(Some(&x));
  |                      ^ borrowed value does not live long enough
...
9 | }
  | - `x` dropped here while still borrowed
  |
  = note: values in a scope are dropped in the opposite order they are created

Почему первый пример работал, а не второй?

Поскольку компилятор знает, что Cell (на самом деле UnsafeCell) необходимо учитывать вероятность того, что вы будете хранить значение в созданном типе.

С Номикон , упор мой:

  • UnsafeCell<T>, Cell<T>, RefCell<T>, Mutex<T> и все другие типы внутренней изменчивости инвариантны относительно T (как и метафора *mut T)

Дисперсия - это плотная тема, которую я не могу объяснить кратко.

@ trentcl предоставляет этот пример, который показывает, что ваш исходный код может не работать так, как вы думаете.

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

fn foo<'a, F>(func: F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let mut bar: Bar<'a> = func();
    //           ^^^^^^^
    bar.number = Some(&number);
}
error[E0597]: `number` does not live long enough
  --> src/main.rs:17:24
   |
17 |     bar.number = Some(&number);
   |                        ^^^^^^ borrowed value does not live long enough
18 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 11:1...
  --> src/main.rs:11:1
   |
11 | / fn foo<'a, F>(func: F)
12 | | where
13 | |     F: Fn() -> Bar<'a>,
14 | | {
...  |
17 | |     bar.number = Some(&number);
18 | | }
   | |_^

Разве это невозможно без [...]

Да, но я не уверен точно, что это будет. Я полагаю, что для этого потребуется универсальных связанных типов (GAT) из RFC 1598 .

Моей первой мыслью было попробовать более высокие оценки черт (HRTB):

fn foo<F>(func: F)
where
    F: for<'a> Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = func();
    bar.number.set(Some(&number));
}

Это вызывает E0582 :

error[E0582]: binding for associated type `Output` references lifetime `'a`, which does not appear in the trait input types
  --> src/main.rs:17:25
   |
17 |     F: for <'a> Fn() -> Bar<'a>,
   |                         ^^^^^^^

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

Вы также можете изменить замыкание, приняв необходимое значение:

fn foo<F>(func: F)
where
    F: for<'a> Fn(&'a i32) -> Bar<'a>,
{
    let number = 42;
    let bar = func(&number);
}

Смотри также:

...