Есть ли способ продлить срок службы локально определенных переменных?

#rust

Вопрос:

Пример использования: У меня есть API Graphql с разбиением на страницы, где множество различных сущностей возвращают непрозрачный курсор и логическое hasNext значение . Я хотел бы сделать эти объекты доступными как a TryStream , чтобы можно было выполнять вычисления во время извлечения всех страниц.

Я определил черту, чтобы абстрагироваться от этого. get_data извлекает одну страницу:

 trait PaginatedEntityQuery {
    type ResponseData;
    fn get_cursor(data: amp;Self::ResponseData) -> Option<String>;
    fn has_next_page(data: amp;Self::ResponseData) -> bool;
    fn get_data<'a>(
        amp;self, // access to query variables
        backend: amp;'a Backend,  // access to a backend to get data from
        cursor: Option<String>,  // the cursor in the pagination
    ) -> Pin<Box<dyn Future<Output = Result<Self::ResponseData>>   'a>>;
}
 

Я использую try_unfold в своем бэкэнде impl :

 fn get_paginated_entities<'a, T>(
  amp;'a self,
  query: amp;'a TryStream<Ok = T::ResponseData, Error = anyhow::Error>   'a
where
  T: PaginatedEntity
{
  try_unfold(StreamState::Start, async move |state| {
    // stream state handling code that defines `cursor`
    let data = query.get_data(self, cursor).await?
    // more data   state computations   return result of `get_data`.
  }
}
 

Теперь я хотел бы определить что-то вроде

 struct SomeEntityQuery { filter: String }
impl PaginatedEntityQuery for SomeEntityQuery { /* … */ }
 

Наконец, я могу определить функцию, которая используется get_paginated_entities для выполнения тяжелой работы:

 fn get_some_entities(filter: String, /* … */) -> impl TryStream<Ok = Vec<SomeOtherType>, Error = anyhow::Error>   'a {
  backend.get_paginated_entities(amp;SomeEntityQuery { filter }).map_ok /* … */
}
 

Конечно, это не работает. Я определил экземпляр SomeEntityQuery in get_some_entities , но возвращаю собственные значения. Ржавчина не может знать, что никакие части SomeEntityQuery на самом деле не отображаются в возвращаемом значении.

Я бы сделал query: amp;'a T в get_some_entities собственности вместо ссылки, но затем я переезжаю t в async move том, что я даю try_unfold , и FnMut , поскольку это закрытие, оно все равно не может владеть им несколько раз. Это должно query быть общим. Кроме того, удаление формы ссылки T заставляет ржавчину жаловаться на то, что параметр типа потенциально не живет достаточно долго.

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

Ответ №1:

Давайте сначала сделаем минимальный рабочий пример этого, который мы действительно можем скомпилировать:

 trait Query {
    fn get_data<'backend>(amp;self, backend: amp;'backend Backend) -> Result<'backend>;
}

struct Result<'data> {
    data: amp;'data str,
}

struct Backend {
    database_data: String,
}

struct SomeQuery {
    length_filter: usize,
}

impl Query for SomeQuery {
    fn get_data<'backend>(amp;self, backend: amp;'backend Backend) -> Result<'backend> {
        Result {
            // we use SomeQuery here, but it's not in the data
            data: amp;backend.database_data[..self.length_filter],
        }
    }
}

impl Backend {
    fn get_paginated_entities<'backend>(
        amp;'backend self,
        query: amp;'backend SomeQuery,
    ) -> Result<'backend> {
        query.get_data(self)
    }
}

fn get_some_entities<'backend>(
    length_filter: usize,
    backend: amp;'backend Backend,
) -> Result<'backend> {
    backend.get_paginated_entities(amp;SomeQuery { length_filter })
}
 

Надеюсь, теперь легче найти проблему; Rust думает, что данные имеют тот же срок службы, что и запрос, потому что мы сказали ему, что они есть!

 fn get_paginated_entities<'backend>(
    // the returned data is referenced from the backend,
    // so we specify a lifetime that Result lives as long
    // as backend does
    amp;'backend self,

    // but we told Rust here that query also has a lifetime
    // of 'backend, so the query also has to live as long as
    // the return value does
    query: amp;'backend SomeQuery,
) -> Result<'backend> {
    query.get_data(self)
}
 

Мы можем использовать здесь несколько периодов жизни, чтобы сказать, что время жизни возвращаемого значения не имеет никакого отношения к времени жизни запроса (на самом деле в этом примере оно нам не нужно, но вы бы сделали это в своем коде).

 fn get_paginated_entities<'backend, 'query>(
    amp;'backend self,
    // now the return data isn't linked to query's lifetime
    query: amp;'query SomeQuery,
) -> Result<'backend> {
    query.get_data(self)
}
 

Это работает нормально, потому get_data что также указывает, что query время жизни не связано Result .

 fn get_data<'backend>(amp;self, backend: amp;'backend Backend) -> Result<'backend>;
 

РЕДАКТИРОВАТЬ: проблема здесь также связана с async ; поскольку мы не знаем, когда будет запущен код, данные должны быть 'static . Мы можем исправить это, используя Rc вместо обычной ссылки. Rc фактически имеет статический срок службы, так как он может плавать вечно, пока на него больше не будет ссылок.

Комментарии:

1. Большое вам спасибо за то, что ответили на мой плохо заданный вопрос и так мило на него ответили! Это определенно помогло расширить мой кругозор в отношении жизни. Я приму ответ, потому что, написав вопрос, он хорошо справляется с ответом на него. К сожалению, кажется, что типы в моей программе несколько сложнее. Я постараюсь воспользоваться вашим советом и задать другой вопрос, если в этом возникнет необходимость!

2. Я думаю, что моя проблема связана с тем фактом, что get_data это не возвращается Result<'backend> , но Pin<Box<dyn Future… 'backend>> . Итак, как я вижу, Pin принадлежит get_data self ли он пожизненно? Что дает мне: несоответствие продолжительности жизни, так как query.get_data paginated_entities возвращает что-то при жизни query , а не только при жизни бэкенда. Но спасибо, я постараюсь посмотреть, смогу ли я исправить ситуацию.

3. @AleksandarDimitrov async жизнь действительно сложнее, и я, к сожалению, недостаточно знаком с Future этим, чтобы дать окончательный ответ, особенно без минимального воспроизводимого примера. Однако я думаю, что проблема в том, что Future она всегда ограничена ссылками, которые она использует. Предположение get_data возвращает будущее , которое использует amp;self , даже если amp;self его нет ResponseData , так Future как s ленивы amp;self , не живет достаточно долго. Видишь rust-lang.github.io/async-book/03_async_await/…

4. Можете ли вы попробовать перейти amp;self на self: Rc<Self> (конечно, соответствующим образом переработав остальную часть вашего кода)? Использование Rc параметра удаляет привязку к сроку self службы . Если это сработает, я отредактирую свой ответ.