遇到的 Rust 所有权转移问题

ErioifpudErioifpud
1 min read
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 实例的借用,借用完会还;而 textinto 一样是转移,用完不会还,v 实例就被回收了。

这里还需要考虑的是,“借用什么时候结束?”,因为当所有的引用都消亡后,才算是归还,才能对 self 本身进行转移。

解法

所以解法有两种:

  1. 第一种简单的调换顺序,然后分别处理 headerstext 的结果:

     let headers = v.headers();
     //....把 headers 处理完
     let text = v.text().await.unwrap();
     //....接着再处理 text
    

    这样这样 headers 处理完后,自身和 v引用都能安全被回收,从而能将所有权转移text 上。

  2. 第二种也要调换顺序,但会做一个拷贝:

     // headers() 返回类型 HeaderMap 实现了 Clone
     let headers = v.headers().clone();
     let text = v.text().await.unwrap();
     //一起处理,因为 clone 解耦了 headers 与 v 的生命周期约束
    

    为了一起处理数据,在拿到 headers 后,对他做一份拷贝,这样后续的操作就和原 headers 引用和 v 的引用无关了,他们两个正常回收,后续使用的是拷贝的新数据。

    这样的性能略低于第一种,因为要做内存拷贝,但影响不微乎其微。

0
Subscribe to my newsletter

Read articles from Erioifpud directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Erioifpud
Erioifpud