Как передать функцию, которая создает структуру, содержащую Cell со временем жизни, другой функции? - программирование
Подтвердить что ты не робот

Как передать функцию, которая создает структуру, содержащую Cell со временем жизни, другой функции?

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

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 которая охватывает всю функцию? Не возможно ли это без создания без лексических времен или более высокого типа конструкторов и т.д.?

4b9b3361

Ответ 1

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

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) должен учитывать возможность того, что вы будете хранить значение в созданном типе.

Из Nomicon, мой удар:

  • 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 | | }
   | |_^

Это невозможно без [...]

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

Моя первая мысль заключалась в том, чтобы попытаться оценить границы верхнего уровня (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);
}

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