Why do structs not default to being Copy?

struct Rect
{
    width: u32,
    height: u32,
}

impl Rect
{
    fn area(self) -> u32
    {
        self.width * self.height
    }
}

fn main()
{
    let rect = Rect{width: 10, height: 20};

    println!("{}", rect.area());
}

If I call the rect.area() method, why does the self takes over the ownership? Why doesn't it copy the entire thing instead? Is it to avoid unnecessary memory usage?

If your type implements the Copy trait, it will be copied automatically. Otherwise, it will not.

3 Likes

Oh ok I got it. But why doesn't it copy by default, is it to avoid unnecessary memory usage?

Because a lot of types can't be copied (think String), and you don't want your struct to suddenly become uncopyable without any notice after you added a field to it.

18 Likes

Copy isn't an auto trait, like Send, or Sync is, which would be what you're suggesting (an opt-out model instead of an opt-in model). It's probably designed that way because it would be more verbose to make it an opt-out system than an opt-in system, when, for example, you want to insert Clone code which a Copy wouldn't override. Also, since auto traits are transitive (IE, if all the members of X implements the trait, then unless !Trait is implemented for X, it implements the trait), it is much more prone to bugs because your struct wouldn't fail at the implementation of Copy (it would just be silently omitted) instead it would fail at each usage site.

Another thing to notice is that Copy is intended for small things. A good way to keep it efficient I've heard of is to keep it about the size of a register.

6 Likes

Regarding the title:

When using impl and using self, why does it do borrowing instead of copying?

Just to clarify, borrowing refers to taking a reference to the data owned by the outer function. What you are trying to convey (taking ownership) is called moving.

2 Likes

Why not just edit the title so that future new Rustacians (i.e., nauplii) who find this thread aren't confused by it?

2 Likes

The Copy trait was actually opt-out a long time ago but was changed to be opt-in.
You can find some reasoning at the bottom of that RFC: https://github.com/rust-lang/rfcs/blob/master/text/0019-opt-in-builtin-traits.md#why-are-copy-and-sized-different

14 Likes

Being Copy actually increases your chance of increased memory usage, because it allows the compiler to transparently create extra copies of your data. And this, I presume, is why it's not default.

2 Likes

I would assume the compiler can optimize out keeping extra copies alive if they aren't used. Copy types can't implement Drop, so a Copy type will never need to be kept around longer than its last usage.

As I remember it, the reasoning for this was much more related to backwards-compatibility than any kind of runtime performance. As @Cerberuser said, it's because auto-Copy types could suddenly become non-Copy if you added a non-Copy` field.

auto-Copy types would be a maintenance hazard, and add yet another thing that you'd have to think about when adding a field to a struct, an operation that is (excluding Send/Sync) backwards compatible. This is also one of the reasons listed in the blog post mentioned in the RFC discussion which @troplin linked. Notably, though, the blog post also lists a bunch of other reasons why we should do this which I hadn't remembered (and those turned into reasons why the rust compiler did do this).

1 Like

To be honest, making a struct deliberately not Copy when it can be is something that I rarely do myself. I can imagine situations when that is useful, but those rarely happened to me — and are as unlikely to happen to anyone else. Personally, I'd prefer a marker type for removing Copy semantics, like PhantomMove or AssertMove, which you use if you know you'll be adding non-Copy fields to the struct.

One major hurdle for that is the lack of negative reasoning in the type system. There are some coding patterns that work only with Copy types, but none that are prevented. If the trait was AssertMove instead, it would be impossible to write generic code that uses copy semantics, because there’s no way to say !AssertMove.

Also, one of Rust’s philosophies for forward compatibility is that adding a new trait impl shouldn’t break existing code. Adding AssertMove to a type would be a breaking change because it restricts the allowed operations for that type, instead of expanding them.

2 Likes

This is not true AFAIK. The only difference between a Copy and a non-Copy type is that when you move a Copy type, the original is still usable. I suspect by the time we get to codegen Copy is completely forgotten about (but I'm willing to be shown wrong).

It might be the case that implementing Copy for a type might encourage the programmer to make some extra copies, intentionally or otherwise. But if you have a lot of code that doesn't make extra copies of something, implementing Copy for it doesn't give the compiler carte blanche to make more of them.

1 Like

If the trait was AssertMove instead, it would be impossible to write generic code that uses copy semantics

That was not what I meant. What I mentioned is PhantomMove/AssertMove in the sense of PhantomData and PhantomPinned, the trait can still stay Copy. I think that with an appropriate RFC proposal, this could work in a future Rust edition.

Joe232 It is just the Rust creators design decision...among the other alternatives are shallow copy default (C, C++) and deep copy default (Eiffel) - each of which have its own pros and cons. The reason is behind Rust philosophy

That was my point. Having something be only Clone means that any copies made are explicit rather than implicit, so it's obvious.

1 Like

I may be misunderstanding your question, but just FYI, your method area takes ownership because the first argument is self. If the first argument was instead &self, the method would not take ownership of the rectangle, but instead borrow it read-only. This way, you can avoid copying and also avoid taking ownership.

You specifically asked about copying, but it looks like what you would want is for the method singature to be

fn area(&self) -> u32