Borrowing puzzle with closure and traits

Greetings !

I am trying to piece out this piece of borrowing puzzle :

I have a function with this signature :

async fn download_stars<T: AsRef<str>>(
    link: T,
    mut stop_condition: impl FnMut(Option<&str>) -> bool,
) -> Result<Vec<Query>, reqwest::Error> {
    // Some code elided

    let mut stars: Vec<Query> = Vec::new();
    let mut next_link: Option<&str> = Some(link.as_ref());
    while stop_condition(next_link) {
        if let Some(link) = next_link {
            let res = client.get(&*link).send().await?;
            let headers = &res.headers();
            next_link = extract_link_next(headers);
            let mut s = res.json().await?;
            stars.append(&mut s);
        }
    }

    Ok(stars)
}

And of course, the Rust compiler sends back :


error[E0597]: `res` does not live long enough
  --> src/main.rs:58:28
   |
55 |     while stop_condition(next_link) {
   |                          --------- borrow later used here
...
58 |             let headers = &res.headers();
   |                            ^^^ borrowed value does not live long enough
...
62 |         }
   |         - `res` dropped here while still borrowed

error[E0505]: cannot move out of `res` because it is borrowed
  --> src/main.rs:60:25
   |
55 |     while stop_condition(next_link) {
   |                          --------- borrow later used here
...
58 |             let headers = &res.headers();
   |                            --- borrow of `res` occurs here
59 |             next_link = extract_link_next(headers);
60 |             let mut s = res.json().await?;
   |                         ^^^ move out of `res` occurs here

error: aborting due to 2 previous errors; 1 warning emitted

Which seems totally fair but how would I solve it ?

Please make sure that the snippets you posts are correct. You are missing the headers argument in extract_link_next.

In this case it seems best to change next_line to the type Option<String>.

Oh yes please excuse me I did not copy properly

If I do this, the problem shifts to :

error[E0308]: mismatched types
  --> src/main.rs:54:46
   |
36 | async fn download_stars<T: AsRef<str>>(
   |                         - this type parameter
...
54 |     let mut next_link: Option<String> = Some(link);
   |                                              ^^^^ expected struct `std::string::String`, found type parameter `T`
   |
   = note:      expected struct `std::string::String`
           found type parameter `T`

error: aborting due to previous error; 1 warning emitted
   let headers = &res.headers();
   next_link = extract_link_next(headers);

That makes next_link borrow from headers, and headers are valid only where their let is. This code means:

while … {   
   let headers = &res.headers();
   next_link = extract_link_next(headers);
   …
   drop(headers);
}

and when headers are dropped, the value in next_link is gone too. It's essential to understand that &str doesn't store the string. It only says "someone else has a string over there".

The easiest solution is to copy the value out of headers, so use:

let mut next_link: Option<String> = Some(link.as_ref().to_string())

You can try moving the value out of headers. You can use Cow if you really want to avoid the first redundant alloc.

If I do this I get :

error[E0382]: use of moved value
  --> src/main.rs:56:21
   |
54 |     let mut next_link: Option<String> = Some(link.as_ref().to_string());
   |         ------------- move occurs because `next_link` has type `std::option::Option<std::string::String>`, which does not implement the `Copy` trait
55 |     while stop_condition(next_link) {
   |                          --------- value moved here
56 |         if let Some(link) = next_link {
   |                     ^^^^ value used here after move

error: aborting due to previous error; 1 warning emitted

Adjust stop_condition accordingly to borrow the value instead of taking it and destroying it immediately.

stop_condition(next_link.as_deref())
  • String — there is only one instance of it, whoever takes it, gets exclusive use of it and destroys it.
  • &str — it gives temporary permission to use a String without destroying it
1 Like

Of course ! :man_facepalming:

Thank you very much.