Can't derive Copy because of String?


#1

Hi,

I’m surprised that String does not implement the Copy trait making impossible to derive Copy for struct with String field:

error[E0204]: the trait `Copy` may not be implemented for this type
  --> src\cobalt_model\pagination_config.rs:8:10
   |
8  | #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
   |          ^^^^
9  | pub struct PaginationConfig {
10 |   pub include: String,
   |   ------------------- this field does not implement `Copy`

Do I need to implement manually Copy or having String as field is bad practice?


#2

There’s nothing wrong with having a String field in your type, but it is incorrect/impossible to implement Copy for any type that contains a String.

String can’t implement Copy because (like Vec and any other variable-sized container), it contains a pointer to some variable amount of heap memory. The only correct way to copy a String is to allocate a new block of heap memory to copy all the characters into, which is what String's Clone implementation does.

https://doc.rust-lang.org/book/second-edition/ch04-01-what-is-ownership.html covers these topics in much more detail.

Is there any specific reason you wanted this type to implement Copy, or were you just wondering why String isn’t Copy?


#3

Thank you for your insight, I tried to derive Copy because I’m having this error:

error[E0382]: use of partially moved value: `doc`
   --> src\cobalt.rs:176:67
    |
173 |         let pagination_config = doc.front.pagination.expect("Must have a pagination");
    |                                 -------------------- value moved here
...
176 |             let paginators = pagination::generate_paginators(&mut doc, &posts_data);
    |                                                                   ^^^ value used here after move
    |
    = note: move occurs because `doc.front.pagination` has type `std::option::Option<cobalt_model::pagination_config::PaginationConfig>`, which does not implement the `Copy` trait

So I tried to follow the help given :slight_smile:

(btw, does the forum support markdown? Would easier to format the error message than 4 spaces for code ^^’)


#4

On formatting, yes Discourse does markdown, so the easier way to do a code/monospace block is three backticks ``` above and below it.

That error message is not recommending that you should implement Copy. Since your type contains a String, the lack of a Copy implementation is clearly legitimate, and you need to do something else.

Unfortunately there’s no way for us to know what the right solution would be without seeing your code. But, you should definitely read that chapter of The Book I linked because it explains all the ownership rules this error message is referring to. And that’s a subject you really need to understand to be productive in Rust.


#5

I’ll read it again tomorrow (bed time now), been a year or so I’m playing with Rust and sometime I still struggle with ownership. Thanks! If I need more help, I’ll post the code :smile:


#6

You’ll also want to take note of Option::as_ref() and Option::as_mut() methods as they allow you to get references to the value inside an Option.


#7

It seems that you interpreted this line as an indication that a move is a bad thing. This makes some sense, since the note is in the context of an error message. But in fact, in many cases, a move is a good thing! I recommend reading up move-semantics, because it’s at the core of the ownership system; since you say you’ve been struggling with ownership, maybe it would help to read about move semantics in another language, such as C++, for comparison.

(And if you have any suggestions for how the error message could be made clearer, please let us know.)


#8

I understand move semantic, at least in C++, to give some background, I’m a software engineer for 16 years, I’ve worked mostly in Java on different platform and C/C++ in drivers/firmware for 3 years now (but I’ve learn a lot C++ at school).

Thanks for the advice!


#9

Normally when I see that error message, I interpret it as meaning that I forgot to clone the object. Excess cloning is of course a waste of resources, but it’s often the easiest fix, and often the impact is negligible.


#10

It’s because implicit copying String, as it’s an owned type, would take non-negligible amount of time depending on string’s length. See birkenfeld answer below.

So when you need a real copy of non-Copy type, Rust forces you to be explicit (requiring cloning it instead). And that transitively applies to your struct.


#11

Knowing the application this code is for, depending on what you are doing with it, I recommend one of

  • Getting a reference to the pagination config (if you just need to read it and made a variable for “easy access”)
  • Cloning the pagination config (if you are creating a page-specific modification to it)

If this were a builder situation, I’d have a different recommendation but since generate_paginatoirs is in the error, I’m assuming this is later in the process.


#12

Actually, that’s not quite it - Copy requires that the value can be copied using a simple memcpy of the bytes on the stack. But copying a String not only needs to copy the value on the stack (which is just capacity, length and a pointer to the contents) but also to create a new allocation that duplicates the contents.

This is also why Rc, while cheap, cannot be Copy: it needs to increment the reference counter.


#13

Thank you for clarification! I thought Copy may have implementation that, when not overriden, defaults to copying bits.


#14

That’s reserved for Clone. There are two reasons:

  • a Copy copy can be caused by something innocuous like a = b or a function call; it should not be allowed to execute arbitrary code

  • a move (which is possible for all types) is (under the hood) always the same as a copy, just that the compiler doesn’t let you access the source anymore


#15

before (with the partially moved error):

let pagination_config = doc.front.pagination.expect("Must have a pagination");
if pagination_config.is_pagination_enable() {
}

now:

let pagination_enabled = {
  let pagination_config = doc
  .front
  .pagination
  .as_ref()
  .expect("Must have a pagination");
  pagination_config.is_pagination_enable()
};
if pagination_enabled {
}

Thank you all!