我已经将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
我可以通过至少两种方式解决此错误:
value
本身,而不是传递test()
中对value
的引用T
,将make_debug
的参数类型明确声明为&String
或&str
我对所发生的事情的理解是,当有一个参数时,借用检查器假定该参数上的任何生存期都会影响输出impl Debug
的值。
有没有办法使代码参数化,继续传递引用并让借位检查器接受它?
答案 0 :(得分:1)
我认为这是由于有关impl trait
不透明类型如何捕获生存期的规则所致。
如果参数T
中有生存期,则impl trait
必须将它们合并。类型签名中的其他生存期遵循正常规则。
有关更多信息,请参见:
https://github.com/rust-lang/rust/issues/43396#issuecomment-349716967
https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md#lifetime-parameters
原始目标: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。