Accessing structs and ownerships

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

fn main()
{
    let rect1 = Rectangle {width: 30, height: 50};

    println!(
        "The area of the rectangle is {} square pixels.",
        area(&rect1)
    );
}

fn area(rectangle: &Rectangle) -> u32
{
    rectangle.width * rectangle.height
}

So why would I lose ownership of the variable rect1 if I passed it into the area function? Is it cause area1's contents are stored on the heap?

Because Rectangle does not implement Copy. All types are non-copy by default.

Also, nothing is stored on the heap by default.

Sorry what do you mean by that?

If you're going to pass something into a function, it has to either be moved or copied. Rectangle doesn't implement the Copy trait, so it can't be copied, thus it must be moved, thus you lose access to it after passing it.

There's more detail in the Ownership chapter of the book.

What doesn't structs implement copy traits?

It doesn't matter that it's a struct. To quote myself:

All types are non-copy by default.

All types. If you want copy semantics, you have to say so by implementing Copy. Copy by default makes it too easy to introduce ownership bugs.

I'm confused, both by the question and by the answer above. area() is taking a reference (borrow) of rect1. What makes you think you're losing ownership of it?

My question was that if I were not taking a reference and I just passed rect1 into the function, why do I lose ownership to the variable rect1 once when it is passed onto the function?

So what your saying is that let x = 3 does not have a copy trait? But then how can I do this let y = x and not lose ownership to x? Does let y = x copy the value x and stores it into y, right?

let x = 3 is not a type. In that case, you're dealing with an integer type, and all the builtin integer types explicitly have copy semantics, because they implement Copy.

1 Like

If you have struct Foo {}, Rust will forbid you from having the same object in two places. It'll enforce that there's only one copy. It could be one copy that is borrowed in a few places, but as owned it can be either in the function or outside the function, but not in both places at the same time.

That non-copying behavior is useful for structs that represent some unique data that wouldn't get duplicated when the struct data is copied, e.g. a file on disk or a network connection.

If you have #[derive(Copy)] struct Foo {} then you can have as many copies as you like, and Rust will create a new copy whenever you assign it or pass by value somewhere. That's the right choice for things like points, rectangles and other small simple types that are all about their data.

I see

With a variable that contains a String, it will have a ptr, len and cap, right, and the variable does not directly store the String data, it points to the String data that is stored on the heap. So with rect1 variable would it directly store the values 30 and 50 and does it store additional values?

Yes. Your rect1 takes 8 bytes on stack and that's it.

Why is it by default not allowed to be copied, what safety risks does it pose?

If Vec was copyable, .push() to one of the copies could reallocate its data, and make all other copies invalid. That's why similar structs in C (which are always freely copyable) are instead used as *mut Vec rather than bare Vec.

Similar behavior may be important even for trivial structs like struct File {fd: u32}.

The default also forces you to think which types are good to copy, e.g. if the struct is small enough to be cheaper to copy than to pass by reference.

1 Like

To put it another way:

If you forget to mark something Copy, it's inconvenient. The worst thing that happens is that you have to explicitly copy values.

If you forget to mark something as not Copy, it's catastrophic. You can end up unintentionally sharing resources that can't or shouldn't be shared, and you wouldn't even know it until the program explodes.

It's a choice between "convenient" and "safe"; Rust always picks "safe".

8 Likes

At one point in Rust's history, Copy was in fact the default for all types that could implement Copy, and to opt-out of Copy you had to add a std::marker::NoCopy member. When opt-in built-in traits were added, this was changed, along with making Send and Sync (then called Share) work the way they do today. The RFC contains this brief summary of the rationale for this particular change:

In addition, opting in to Copy makes sense for several reasons:

  • Experience has shown that "data-like structs", for which Copy is
    most appropriate, are a very small percentage of the total.
  • Changing a public API from being copyable to being only movable has
    a outsized impact on users of the API. It is common however that as
    APIs evolve they will come to require owned data (like a Vec),
    even if they do not initially, and hence will change from being
    copyable to only movable. Opting in to Copy is a way of saying
    that you never foresee this coming to pass.
  • Often it is useful to create linear "tokens" that do not themselves
    have data but represent permissions. This can be done today using
    markers but it is awkward. It becomes much more natural under this
    proposal.
3 Likes

Completely agree, though I do remember as someone new to Rust being surprised that something as trivial as the Rectangle struct (with two plain-old-data integers) wasn't Copy by default.

Are the compiler errors smart enough to detect if you have a trivial struct of literals and suggesting #[derive(Copy)] (and why)? That kind of thing would be really useful for people new to Rust.

As @trentj already noted, the compiler used to do that. The problem is that there's no discernible difference between "I don't want Copy" and "I forgot to impl Copy". If the compiler did suggest Copy anywhere it could be applied, all that would really do is force you to manually annotate both cases, which would be even more work for no gain.

2 Likes

(I added the emphasis)

I'm sorry, but the question title seems to be a bit misleading, what does this have to do with tuples? Also, what would the point to a borrowing system be if you'd just be blindly copying stuff everywhere you used it?