Как привязать время жизни ссылки к локальной области видимости функции - PullRequest
1 голос
/ 01 октября 2019

Я хочу написать функцию A, которая принимает в качестве параметра функцию B, которая принимает в качестве параметра тип, параметризованный ссылочным типом, время жизни которого равно, по крайней мере, времени жизни локальных переменных в теле А.

Рассмотрим следующий пример:

struct Foo {}

fn consume(mut v: Vec<&Foo>) {
    while let Some(..) = v.pop() {
        // Do stuff
        continue;
    }
}

fn setup_and<F>(f: F)
where
    F: FnOnce(&mut Vec<&Foo>) + Send,
{
    let mut v: Vec<&Foo> = vec![];
    let other_foo = Foo {};

    f(&mut v);
    v.push(&other_foo);
    consume(v);
}

fn main() {
    let foo = Foo {};
    setup_and(|v| {
        v.push(&foo);
    });
}

rustc не может самостоятельно определить срок службы. Он жалуется:

error[E0597]: `foo` does not live long enough
  --> src/main.rs:25:17
   |
24 |     setup_and(|v| {
   |               --- value captured here
25 |         v.push(&foo);
   |         --------^^^-
   |         |       |
   |         |       borrowed value does not live long enough
   |         argument requires that `foo` is borrowed for `'static`
26 |     });
27 | }
   | - `foo` dropped here while still borrowed

Я пытался указать время жизни для ссылки, взятой setup_and, вот так:

fn setup_and<'a, F>(f: F)
where
    F: FnOnce(&mut Vec<&'a Foo>) + Send,
{
    let mut v: Vec<&'a Foo> = vec![];

Теперь rustc жалуется на setup_and локальную ссылку other_foo не живет достаточно долго. Я предполагаю, что это потому, что он хочет большего времени жизни, чем область действия setup_and.

Как бы я правильно связал времена жизни в этом случае? Я хотел бы выразить, что ссылки должны быть действительными до конца вызова consume.

1 Ответ

3 голосов
/ 01 октября 2019

У вас серьезная, серьезная проблема с конфликтом времени жизни в вашей реализации, и не может быть простого исправления, по крайней мере, без частичного изменения внешних сигнатур ваших struct и методов. Все они основаны на методе setup_and и подсвечиваются компилятором, когда вы явно описали границы времени жизни.

Тело вашего метода скопировано ниже с аннотациями для понимания проблемы:

let mut v: Vec<&Foo> = vec![];
let other_foo = Foo {}; // other_foo is created here

f(&mut v);
v.push(&other_foo); // other_foo requires lifetime 'a to be added to this
consume(v); // consume does not restrict the lifetime requirement 'a
// other_foo is dropped here, at lifetime less than 'a

Самым простым решением этой проблемы является сохранение Arc<Foo>, например, ( детская площадка ):

fn setup_and<F>(f: F)
where
    F: FnOnce(&mut Vec<Arc<Foo>>) + Send,
{
    let mut v: Vec<Arc<Foo>> = vec![];
    let other_foo = Foo {};

    f(&mut v);
    v.push(Arc::new(other_foo));
    consume(&mut v);
}

Arc - это атомный эталон. Подсчет указателя. Это клонируемая структура, которая работает как динамический указатель на объект в куче;для всех намерений и целей, он работает как ссылка только для чтения, без требования для жизни. Когда все копии Arc сброшены, элемент внутри него также сбрасывается.

Это решает две проблемы:

  1. Ваш other_foo теперь перемещен в Arc и больше не вызывает проблем с его временем жизни
  2. Теперь вы можете обращаться к своим объектам так же, как и к ссылке (Arc реализует Deref)

Выбор Arc был сделан, потому что вашему FnOnce требуется Send, что Rc (однопоточный вариант Arc) не может предоставить.

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