有没有办法传递对泛型函数的引用并返回与参数的生存期无关的impl特性?

时间:2019-07-10 14:40:53

标签: rust

我已经将real-life example in a web app分解为以下示例:{{3}},使用不必要的堆分配解决了该问题:

// Try replacing with (_: &String)
fn make_debug<T>(_: T) -> impl std::fmt::Debug {
    42u8
}

fn test() -> impl std::fmt::Debug {
    let value = "value".to_string();

    // try removing the ampersand to get this to compile
    make_debug(&value)
}

pub fn main() {
    println!("{:?}", test());
}

按原样,编译这段代码可以给我:

error[E0597]: `value` does not live long enough
  --> src/main.rs:9:16
   |
5  | fn test() -> impl std::fmt::Debug {
   |              -------------------- opaque type requires that `value` is borrowed for `'static`
...
9  |     make_debug(&value)
   |                ^^^^^^ borrowed value does not live long enough
10 | }
   | - `value` dropped here while still borrowed

我可以通过至少两种方式解决此错误:

  1. 传递value本身,而不是传递test()中对value的引用
  2. 代替参数T,将make_debug的参数类型明确声明为&String&str

我对所发生的事情的理解是,当有一个参数时,借用检查器假定该参数上的任何生存期都会影响输出impl Debug的值。

有没有办法使代码参数化,继续传递引用并让借位检查器接受它?

1 个答案:

答案 0 :(得分:1)

我认为这是由于有关impl trait不透明类型如何捕获生存期的规则所致。

如果参数T中有生存期,则impl trait必须将它们合并。类型签名中的其他生存期遵循正常规则。

有关更多信息,请参见:

更完整的答案

原始目标:send_form函数采用类型为&T的输入参数,该参数呈现为二进制表示形式。该二进制表示形式归于最终的隐含的Future所拥有,并且原始&T的剩余部分均不存在。因此,&T的寿命不必超过impl特性。一切都很好。

当T本身另外包含具有生存期的引用时,就会出现此问题。如果我们不使用impl Trait,我们的签名将如下所示:

fn send_form<T>(self, data: &T) -> SendFormFuture;

通过查看SendFormFuture,我们可以很容易地观察到根本没有T的剩余部分。因此,即使T有自己的生命周期要处理,我们也知道所有引用都在send_form主体内使用,而以后SendFormFuture再也不会使用。

但是,以impl Future作为输出,我们没有得到这样的保证。没有办法知道Future的具体实现是否确实适用于T

T没有引用的情况下,这仍然不是问题。 impl Future引用了T并完全拥有了它的所有权,或者它没有引用它,并且不会出现生命周期问题。

但是,如果T确实有引用,则可能会遇到以下情况:具体的impl Future保留在T中存储的引用上。即使impl Future拥有T本身的所有权,也不拥有T引用的值的所有权。

这就是为什么借入支票必须保守的原因,并坚持要求T内的任何引用都必须有'static的生存期。

我看到的唯一解决方法是绕过impl Future并在返回类型中进行明确显示。然后,您可以很容易地向借阅检查器演示输出类型根本不引用输入T类型,并且其中的任何引用都不相关。

actix Web客户端中send_form的原始代码如下:

https://docs.rs/awc/0.2.1/src/awc/request.rs.html#503-522

pub fn send_form<T: Serialize>(
        self,
        value: &T,
    ) -> impl Future<
        Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
        Error = SendRequestError,
    > {
        let body = match serde_urlencoded::to_string(value) {
            Ok(body) => body,
            Err(e) => return Either::A(err(Error::from(e).into())),
        };

        // set content-type
        let slf = self.set_header_if_none(
            header::CONTENT_TYPE,
            "application/x-www-form-urlencoded",
        );

        Either::B(slf.send_body(Body::Bytes(Bytes::from(body))))
    }

您可能需要修补库或编写自己的函数,但要执行的功能相同,但要使用具体类型。如果其他人知道如何解决impl trait的明显限制,我很想听听。

这是我在send_form(actix-web客户端库)中重写awc所走的距离:

    pub fn send_form_alt<T: Serialize>(
        self,
        value: &T,
        // ) -> impl Future<
        //     Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
        //     Error = SendRequestError,
    ) -> Either<
        FutureResult<String, actix_http::error::Error>,
        impl Future<
            Item = crate::response::ClientResponse<impl futures::stream::Stream>,
            Error = SendRequestError,
        >,
    > {

到目前为止有一些警告:

  • Either::B一定是impl trait的不透明Future
  • FutureResult的第一个参数实际上可能是Void或Rust中与之等效的Void