Search code examples
rustownershipreference-lifetimes

Lifetime of items in impl Iterator of Reqwest Cookies


I just started writing in Rust and I am writing a small program to learn Rust and the package Reqwest.

Here's my little program. I don't understand why changing from HashMap<&str, &str> to HashMap<String, String> can make it work whilst cookie still only lives within the scope? I've commented my doubts along the code as well.

use reqwest::cookie::Jar;
use reqwest::{Client, Url};
use std::sync::Arc;
use std::collections::HashMap;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let jar = Jar::default();
    let raw_url = "https://www.google.com";

    let url = Url::parse("https://www.google.com").unwrap();
    jar.add_cookie_str("session=1", &url);

    let jar = Arc::new(jar);
    let client_builder = Client::builder();

    let client = client_builder
                   .cookie_provider(Arc::clone(&jar))
                   .build()?;

    let response = client.get(raw_url).send().await?;

    let headers: &HeadersMap = response.headers();

    let cookies: Impl Iterator<Item=Cookie>+Sized = response.cookies();

    // let mut cookie_map = HashMap::<String, String>::new(); // this would work;

    let mut cookie_map = HashMap::<&str, &str>::new(); // impl<'a> Cookie<'a>

    let mut header_map = HashMap::<&str, &str>::new(); // live as long as the programme

    for cookie in cookies { // declared borrowed value here, hence the lifetime kicks in
         // Error: cookie does not live long enough
         cookie_map.insert(cookie.name(), cookie.value());

         // cookie_map.insert(cookie.name().to_string(), cookie.value().to_string());
         // with `to_string` we have taken ownership of the value; is this why it's working?
    }
    // cookie dropped while still borrowed here;
    // kv pairs are both borrowed values that have a lifetime `'a` which ends here
    // and cookie_map apparently continues to exist, hence the error (this is my guess)
    

    for (k, v) in headers.iter() {
         match v.to_str() {
             Ok(val) => header_map.insert(k.as_str(), val),
             Err(_) => panic!()
         };
    }
    

    for (hk, kv) in header_map {
         println!("header name is {}, header value is {}", hk, kv);
    }

    Ok(())
}

Here's the cargo.toml:

[package]
name = "tester"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[[bin]]
name = "tester"
test = false
bench = false

[dependencies]
reqwest = { version = "0.11", features = ["blocking", "json", "cookies"] }
tokio = { version = "1", features = ["full"] }
futures = "0.3"
reqwest_cookie_store = "0.7.0"

cargo check error log is provided here:


error[E0597]: `cookie` does not live long enough
  --> src/tools/fetcher.rs:32:32
   |
31 |          for cookie in cookies {
   |              ------ binding `cookie` declared here
32 |              cookie_map.insert(cookie.name(), cookie.value());
   |              ----------        ^^^^^^ borrowed value does not live long enough
   |              |
   |              borrow later used here
33 |          }
   |          - `cookie` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `data_fetcher` (bin "data_fetcher") due to 1 previous error



Solution

  • cookie only exists during the duration of one iteration of the loop. &str is a reference to a string, so the value referenced must live at least as long as the HashMap the reference is in. But since cookie is destroyed after the iteration, the value is lost.

    The solution is to take ownership of the &str. That's what you did by changing the type to String.