How do I copy/clone a string including the heap?

I can't seem to copy a string including the heap. Clearly there is some concept I don't understand, I'm still a beginner in the particular language and still learning.

I'm getting a value returned into my variable called response which has the datatype reqwest::Response I want to copy the value of the string from here, so that I can work with that string and not worry about the variable called response because I might be populating that with something else later on, I don't know the rest of my plan.

I've read how when copying a string in Rust is copying the pointers to a heap, but I want to copy the heap as well. This might not be the most optimal in terms of clock cycles of the CPU, but the bottle neck in my code is going to be the speed of the API calls across the internet, so waiting for a few clock cycles of the CPU to copy the heap is not a problem.

#[tokio::main]
pub async fn do_api_stuff7() -> Result<(), Box<dyn Error>> {
    let response: reqwest::Response = reqwest::Client::new()
        .get("https://www.themuse.com/api/public/jobs?page=1")
        .header(AUTHORIZATION, "Bearer [AUTH_TOKEN]")
        .header(CONTENT_TYPE, "application/json")
        .header(ACCEPT, "application/json")       
        .send()
        .await
        .unwrap(); 
    println!("one");

    if response.status() != reqwest::StatusCode::OK {
        println!("not OK");
    } else {
        println!("OK");
    }

    match response.status() {
        reqwest::StatusCode::OK => {
            println!("one");
            println!("response {:?}", response);
            println!("two");
            let mut res_str: &str = "";
            let mut str_is_ok = true;
            
            match response.text_with_charset("utf-8").await {
                Ok(res) => {
                    str_is_ok = true;
                    res_str = res.clone();
                },
                Err(_) => {
                    str_is_ok = false;
                },
            };

            println!("three");
            println!("{}", res_str);
            println!("four");
        }
        reqwest::StatusCode::UNAUTHORIZED => {
            println!("Need to grab a new token");
        }
        other => {
            panic!("Uh oh! Something unexpected happened: {:?}", other);
        }
    };

    Ok(())
}

In my code if you have a look down to the second match statement match response.text_with_charset("utf-8").await I create a variable called res_str just before the match statement so that the scope of this variable is outside that match statement.

Inside this second match statement I try to copy or clone or something anything just to copy the string including the heap.

Then after this second match statement have println!("{}", res_str); so I can print out the string.

I don't know if this second match statement was the right thing to do but I've been trying all sorts of things.

How do I copy a string including the heap?

The problem is that the type of res_str does not have any ownership of heap data, since it's a &str reference. If you consider making this local variable e. g. a String instead (not addressing the potentially confusing name for res_str: String in this case that's maybe suggesting a str) you could just do res_str = res in the match then. The Ok(res) you match on will already give you an owned value of type String that has nothing to do with the response variable anymore, as far as the borrow checker is concerned, so you don't even need to clone it.

I'm on mobile right now, so I won't go through the effort of addressing individual points and sentences, but (if you couldn't tell already by the above explanations), your question does indicate you might have some incorrect assumptions/preconceptions regarding how strings work. In particular, clone will actually clone the heap data (which is what you asked for) and the error you will be getting from your code above is cause by this new heap data not being assigned to a sufficiently long-lived owners. But also note that sometimes for copying a string, if you start out with a &str reference, the clone method will not work and you'll have to use something like to_owned or String::from (and there's a handful more equivalent alternatives) instead.

4 Likes

A String is something like

// The String                On the heap somewhere
[length][capacity][ptr] ---> [byte][byte][byte][byte]

And when you move the String -- transfer ownership -- it's true you just copy the pointer (and length and capacity).

let new_string = old_string;

// Old String (unusable)     On the heap somewhere
[length][capacity][ptr] ---> [byte][byte][byte][byte]
                             ^
// New String                |
[length][capacity][ptr] -----'

But because you have transfered ownership (String is not Copy), the old String is not usable any more. The new String is now the sole owner of the data (including the data on the heap), and that's what you want in this case. The data on the heap wasn't duplicated, but because no one else can use it this isn't a problem.

let (res_str, str_is_ok) = match response.text_with_charset("utf-8").await {
    Ok(res) => (res, true),
    Err(_) => (String::new(), false), // String::new is cheap
};

We covered String, but what's a &str? It looks like:

// &str             data *somewhere* (heap, stack, static?  we don't know)
[length][ptr] ----> [byte][byte][byte][byte]

When talking about "literal strings", it's a &'static str pointing to static memory. If the lifetime isn't 'static, it's a temporary borrow of some string data somewhere, but you don't know where. You're not the sole owner of the underlying data.

Reference do implement Copy, so you can have many of them usable and pointing at the same data.

If you want to become the sole owner of the data (and get rid of the lifetime in the process), that's where clone/to_string/etc. come in. They copy the data from wherever the &str is pointing onto the heap and create a new String for you to own.

1 Like

Thank you for that.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.