#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. Большое вам спасибо за подробный ответ. Это значительно расширило мое понимание Ржавчины. Это отлично работает.