遇到的 Rust 所有权转移问题
let client = reqwest::Client::new();
let res = client
.get(url)
.send()
.await;
match res {
Ok(v) => {
let text = v.text().await.unwrap();
let headers = v.headers().clone();
println!("{}", text.clone());
HttpResponse::Ok().content_type(headers.get("content-type").unwrap().to_str().unwrap()).body(text)
},
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
这里的 v.headers
会报错,说 v
在之前所有权就被转移了,遇到这种问题需要分析。
分析签名
首先看两个函数的签名:
text
的参数是一个值,返回的是 Result<String>
,所以这里的 self
会转移,转移到 text
函数内部,之后在 text
执行完成时被 drop
掉,结合例子一句话就是 v
在调用 text
后会被消耗。
而 headers
的参数是一个引用,这里就会涉及到生命周期,签名写完整是这样的:
pub fn headers<'a>(&'a self) -> &'a HeaderMap
这意味着,返回值 &Header
的生命周期不得长于参数 &self
,换句话说就是,&Header
要在 &self
之前被回收。
总结
分析完签名后可以发现,例子中的使用顺序是有问题的,headers
应该放在前面,因为他是对 v
实例的借用,借用完会还;而 text
和 into
一样是转移,用完不会还,v
实例就被回收了。
这里还需要考虑的是,“借用什么时候结束?”,因为当所有的引用都消亡后,才算是归还,才能对 self 本身进行转移。
解法
所以解法有两种:
第一种简单的调换顺序,然后分别处理
headers
和text
的结果:let headers = v.headers(); //....把 headers 处理完 let text = v.text().await.unwrap(); //....接着再处理 text
这样这样
headers
处理完后,自身和v
的引用都能安全被回收,从而能将所有权转移到text
上。第二种也要调换顺序,但会做一个拷贝:
// headers() 返回类型 HeaderMap 实现了 Clone let headers = v.headers().clone(); let text = v.text().await.unwrap(); //一起处理,因为 clone 解耦了 headers 与 v 的生命周期约束
为了一起处理数据,在拿到 headers 后,对他做一份拷贝,这样后续的操作就和原 headers 引用和 v 的引用无关了,他们两个正常回收,后续使用的是拷贝的新数据。
这样的性能略低于第一种,因为要做内存拷贝,但影响不微乎其微。
Subscribe to my newsletter
Read articles from Erioifpud directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by