Copy trait failed as fields do not implement Copy

I've recently come across the error:

"An attempt to implement the Copy trait for a struct failed because one of the fields does not implement Copy. To fix this, you must implement Copy for the mentioned field. Note that this may not be possible, as in the example of"

My example is very similar and was curious what patterns / solutions people take to get around this. So in my example I have a Client struct and want users to pass additional headers to the client so they can be stored in a HashMap and used in subsequent requests.

#[derive(Copy, Clone)]
struct Client {
    headers: HashMap<String, String>,
}

While I'm on the subject as well are there any good resources for learning to structure a rust app / library from the sense that is an OOP style approach vs a functional approach as I tend to still think in object terms creating structs for most things?

2 Likes

It is simply not safe for Client to implement Copy because its HashMap field does not implement Copy, and for a good reason. Creating an exact bit-for-bit copy of HashMap would mean that you have two of them that think they own the same allocations--that's really, really bad.

I'm having a hard time understanding what you're trying to do. Would you mind giving an example that shows what you're using this struct for?

Hi thanks for the reply, makes a bit more sense now you've explained they're like for like in terms of owning the same memory allocations? It's the kind of pattern I would have done in Ruby without second thought.

I've pushed all my "broken" code up to Github. Essentially I have a lib that loads a client struct into it. I want users to add a set of default headers to the requests which will persist in the client for all subsequent requests. The two files that cause the issue are:

lib.rs is creating a new Client struct and assigning it to a client field in the Github struct. That client object is then passed into the UsersService or RepositoryService which make the requests such as github.users.get(by_github_handle). The client.rs file is below which is where I've started to add the headers.

Do you have an understanding of borrowing and ownership yet?

Right now, all of your methods on Client and GitHub take self, which means those methods take ownership of self. That's why the compiler is telling you that Client needs to implement Copy, because that's the only way add_header() can take self without consuming ownership of it, because it would make a copy. If this worked, it wouldn't be very useful, because we want to modify the original copy, not make a new one just for this method.

You were on the right track making the headers field of Client into a HashMap<String, String>, but the add_header() method on Client needs to change a bit to make it work.

add_header() should take &mut self instead of self; this tells the compiler that this is a mutable borrow, and after the method returns, the caller should still have ownership of Client:

// note: #[derive] annotation removed
pub struct Client {
    headers: HashMap<String, String>,
}

impl Client {

    // Note: &mut self` instead of self
    pub fn add_header(&mut self, key: &str, value: &str) {
        // We call .into() on these &str values to tell them to convert to String.
        self.headers.insert(key.into(), value.into());
    }

    // Other methods go here
}

I have probably a very basic understanding of borrowing and ownership but something I definitely need to brush up on.

I've seen the &mut self in a few libraries, specifically the 'rust-curl' library and never really thought about what is was doing. I'll try and find some good posts on learning a little more on borrowing and ownership.

I also still need to fully understand the whole '&' keyword in terms of Lifetime specifiers. It's like a whole new world coming from Ruby!

Thanks for your help!

David

The borrowing system is a key feature of Rust. Remember that it doesn't have a garbage collector like Ruby, so it needs to be able to verify at compile time what objects are reachable, and from where, so it knows what's safe to access and what's safe to deallocate.

Ruby doesn't concern you with these details, so it's a lot easier to reason about your program. However, it also has significant overhead with the garbage collector and the supporting runtime. Rust is designed to run as close to the metal as possible.

Have you read The Rust Programming Language yet? It's a detailed manual written by the Rust team and community and kept up to date with the language. I recommend you check out the chapters on borrowing and ownership:

https://doc.rust-lang.org/nightly/book/ownership.html

https://doc.rust-lang.org/nightly/book/references-and-borrowing.html

That should get you started down the right path.

You may need a basic primer on pointers since it doesn't sound like you've had much exposure to them. Again, Ruby and other GC'd languages hide these details from you so you don't have to worry about them.

Thanks again DroidLogican. Read through both those and the lifetime specifiers page a couple of times today and it makes a lot more sense now where I'm going wrong.

I've had some experience with pointers but really that's just been through some small Objective-C based projects, never with such defined rules around moving ownership.

Hopefully it'll start to become second nature when thinking about Rust programs.