#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
службы . Если это сработает, я отредактирую свой ответ.