«не удается определить подходящее время жизни» при попытке вернуть фрагментированный ответ с помощью hyper

#rust #stream #lifetime #chunked #hyper

Вопрос:

Я хотел бы возвращать двоичные данные кусками определенного размера. Вот минимальный пример.

Я создал структуру оболочки для hyper::Response хранения моих данных, таких как статус, текст статуса, заголовки и возвращаемый ресурс:

 pub struct Response<'a> {
    pub resource: Option<amp;'a Resource>
}
 

Эта структура имеет build метод, который создает hyper::Response :

 impl<'a> Response<'a> {
    pub fn build(amp;mut self) -> Result<hyper::Response<hyper::Body>, hyper::http::Error> {
        let mut response = hyper::Response::builder();
        match self.resource {
            Some(r) => {
                let chunks = r.data
                .chunks(100)
                .map(Result::<_, std::convert::Infallible>::Ok);
                response.body(hyper::Body::wrap_stream(stream::iter(chunks)))       
            },
            None => response.body(hyper::Body::from("")),
        }
    }    
}
 

Существует также другая структура, содержащая содержимое базы данных:

 pub struct Resource {
    pub data: Vec<u8>
}
 

Все работает до тех пор, пока я не попытаюсь создать фрагментарный ответ. Компилятор Rust выдает мне следующую ошибку:

 error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:14:15
   |
14 |         match self.resource {
   |               ^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 11:6...
  --> src/main.rs:11:6
   |
11 | impl<'a> Response<'a> {
   |      ^^
note: ...so that the types are compatible
  --> src/main.rs:14:15
   |
14 |         match self.resource {
   |               ^^^^^^^^^^^^^
   = note: expected `Option<amp;Resource>`
              found `Option<amp;'a Resource>`
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the types are compatible
  --> src/main.rs:19:31
   |
19 |                 response.body(hyper::Body::wrap_stream(stream::iter(chunks)))       
   |                               ^^^^^^^^^^^^^^^^^^^^^^^^
   = note: expected `From<amp;[u8]>`
              found `From<amp;'static [u8]>`
 

Я не знаю, как выполнить эти пожизненные требования. Как я могу сделать это правильно?

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

1. Ошибка показывает, что Resource это http::resource::Resource так, но я не вижу этого типа в http документах ящика. Можете ли вы показать, откуда он берется?

2. Это также структура, которую я создал как абстракцию для строки базы данных.

3. @rodrigo Создание тела только из ресурсов::данные работают. Но поскольку данные ресурсов могут быть слишком большими, я хотел бы вместо этого возвращать фрагментированный ответ, и я не могу понять, как это правильно сделать с помощью hyper framework.

4. @Shepmaster. Вот минимальный пример: play.rust-lang.org/…

5. wrap_stream требуется, чтобы данные потока соответствовали 'static ограничению. Твоя-нет.

Ответ №1:

Проблема не в 'a самой функции, а в том, что std::slice::chunks() функция возвращает итератор, который заимствует исходный срез. Вы пытаетесь создать поток будущего из этого Chunks<'_, u8> значения, но поток требует, чтобы это было 'static так . Даже если бы у Resource вас не было времени 'a жизни, у вас все равно был бы r.data заимствованный, и он все равно потерпел бы неудачу.

Помните, что здесь 'static не означает, что ценность живет вечно, но что ее можно заставить жить так долго, как это необходимо. То есть в будущем не должно быть никаких (не — 'static ) заимствований.

Вы можете клонировать все данные, но если они очень большие, это может стоить дорого. Если это так, вы могли бы попробовать использовать Bytes , это так же, как Vec<u8> если бы ссылка учитывалась.

Похоже, что нет Bytes::chunks() функции, которая возвращает итератор Bytes . К счастью, это легко сделать вручную.

Наконец, помните, что итераторы в Rust ленивы, поэтому они сохраняют заимствованные исходные данные, даже если это a Bytes . Поэтому нам нужно собрать их в a Vec , чтобы фактически владеть данными (игровая площадка).:

 pub struct Resource {
    pub data: Bytes,
}

impl<'a> Response<'a> {
    pub fn build(amp;mut self) -> Result<hyper::Response<hyper::Body>, hyper::http::Error> {
        let mut response = hyper::Response::builder();
        match self.resource {
            Some(r) => {
                let len = r.data.len();
                let chunks = (0..len)
                    .step_by(100)
                    .map(|x| {
                        let range = x..len.min(x   100);
                        Ok(r.data.slice(range))
                    })
                    .collect::<Vec<Result<Bytes, std::convert::Infallible>>>();
                response.body(hyper::Body::wrap_stream(stream::iter(chunks)))
            }
            None => response.body(hyper::Body::from("")),
        }
    }
}
 

ОБНОВЛЕНИЕ: Мы можем избежать вызова collect() , если заметим, что stream::iter() он принимает на себя ответственность IntoIterator за то, что может быть оценено лениво, пока мы это делаем 'static . Это можно сделать, если мы сделаем (дешевый) клон r.data и переместим его в лямбду (игровую площадку):

     let data = r.data.clone();
    let len = data.len();
    let chunks = (0..len).step_by(100)
        .map(move |x| {
            let range = x .. len.min(x   100);
            Result::<_, std::convert::Infallible>::Ok(data.slice(range))
                    });
    response.body(hyper::Body::wrap_stream(stream::iter(chunks)))       
 

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

1. Большое вам спасибо за подробный ответ. Это значительно расширило мое понимание Ржавчины. Это отлично работает.