"cannot infer an appropriate lifetime" 尝试 return 使用 hyper 的分块响应时

"cannot infer an appropriate lifetime" when attempting to return a chunked response with hyper

我想要 return 特定大小块中的二进制数据。这是一个minimal example.

我为 hyper::Response 制作了一个包装器结构来保存我的数据,如状态、状态文本、headers 和 return 的资源:

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

这个结构有一个 build 方法创建 hyper::Response:

impl<'a> Response<'a> {
    pub fn build(&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<&Resource>`
              found `Option<&'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<&[u8]>`
              found `From<&'static [u8]>`

我不知道如何满足这些终身要求。我怎样才能正确地做到这一点?

问题不在于'a本身,而在于std::slice::chunks()函数returns是一个借用原始切片的迭代器。您正在尝试从这个 Chunks<'_, u8> 值创建流未来,但流要求它是 'static。即使你的 Resource 没有 'a 生命周期,你仍然会借用 r.data,它仍然会失败。

请记住,这里 'static 并不意味着该值永远存在,而是可以使其在需要时一直存在。也就是说,未来不得持有任何(非'static)次借贷。

您可以克隆所有数据,但如果数据非常大,成本可能会很高。如果是这样,您可以尝试使用 Bytes,这就像 Vec<u8> 但引用计数。

看起来没有 Bytes::chunks() 函数 returns 是 Bytes 的迭代器。幸运的是,手工操作很容易。

最后,请记住 Rust 中的迭代器是惰性的,因此它们会保留借用的原始数据,即使它是 Bytes。所以我们需要将它们收集到一个 Vec 中才能真正拥有数据 (playground):

pub struct Resource {
    pub data: Bytes,
}

impl<'a> Response<'a> {
    pub fn build(&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("")),
        }
    }
}

更新:如果我们注意到 stream::iter() 拥有可以延迟计算的 IntoIterator 的所有权,我们可以避免调用 collect(),只要我们做到 'static。如果我们对 r.data 进行(廉价)克隆并将其移至 lambda (playground):

中,就可以做到这一点
    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)))