Should I implement Copy for large structs?

I'm new to low level memory control like in Rust, pardon my ignorance about anything related to memory.

I'm not sure if this can be called "large" on modern computers, but anyway, I have a struct to represent an arc:

pub struct Point {
    pub x: f64,
    pub y: f64,

pub struct Arc {
    pub from: Point,
    pub to: Point,
    pub O: Point,
    pub r: f64,
    pub sweep: bool,
    pub large_arc: bool,
    pub angle: f64,

They're fixed-sized objects, which I thought should live on stack. But the size of Arc is 72, which seems rather large to me.

In computation, should I always borrow a rather large struct like Arc, or copying them would be fine?

There are no hard-and-fast rules, but generally I'd only implement Copy for Point and leave Arc as !Copy.

You can get 512-bit (64-byte) SIMD operations so 72 bytes isn't actually that much, however what matters more for me is the semantics... It's pretty common for operations to update an Arc in place and having &mut self methods on Copy types can be a bit of a footgun - you can accidentally make a temporary copy and update that rather than updating the original Arc as intended.

The std::ops::Range type is !Copy for this exact reason. Technically it's just a pair of usizes (16 bytes), but because it's also an Iterator with a &mut self method, you could have situations where you iterate over a copy and the original doesn't make progress.


It's of the right size that one can not answer whether it's better to copy it or not. Modern CPUs usually pass 6-8 integer arguments and 6-8 float arguments on registers without using stack. Arc includes 2 integers and 8 floats which almost exhaust these limitations.

This means that the best efficiency would be different depending on what kinds of functions your implement for these types.

In such cases it's better to think about whether you usually want to modify these types or create a modified copy.

My gut feeling is similar to @Michael-F-Bryan : Point is very likely to be created anew and it's not hard to make a copy of it with unmodified piece explicitly preserved, but Arc looks complex enough that you would probably want to modify it and clone explicitly when needed.

1 Like

Implementing traits (and specifically Copy) should not be a function of the size of a type, but its intended semantics.

72 bytes is… really not that big if you consider that commodity hardware literally has billions of bytes of operative memory.

Now whether you should implement Copy should depend on whether it makes sense for your type to be implicitly duplicated or not. Your structs are pure data:

  • they describe atomoic domain (geometric) concepts
  • they don't have intrinsic behavior
  • they don't implement Drop
  • they don't have a concept of "object identity"

Based on this, I would say that yes, you should implement Copy. On the other hand, there are types which are specifically small (e.g. containing a 1 or 2 references), but shouldn't by any means be Copy. For example, a MutexGuard is 16 bytes on the Playground, yet it absolutely shall not be Copy, because that would cause unsoundness.

I find size-based recommendations of "optimization" ill-advised anyway. Not implementing Copy won't make your code more efficient, because moves are still bitwise copies, and every value of a statically-sized type can be moved. The compiler will emit and then attempt to optimize away several spurious copies, so you don't really help by omitting Copy; the only thing it does is annoy downstream users of your code.


There's one annoying edge case where Copy can impact optimizations and make copy elision more difficult to perform. In general though I agree; it's not meaningful enough to change the semantic implementation of Copy being a better idea.

Namely, copying a value is a by-ref operation, so doesn't invalidate extant borrows of the value. This means that the old value must remain valid until the end of its defining scope and can't be copy-elided to a potentially mutating use site (if any pointers to the local could potentially still be valid). The compiler currently struggles more than strictly necessary, but this does have an unavoidable[1] impact in some cases.

  1. Unless the language gets some way to get a destructive move for a Copy local. *&mut local might be theoretically sufficient, at least in the absence of addr_of[_mut]!s; it's not completely clear, though. ↩︎


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.