Simple question to official Rust book

Hello,

I just discovered that I can easily log in with GitHub account, so I might ask a simple question now.

I just started reading the official Rust book. I initially intended to learn some Rust already in 2014, but them spent nine years with Nim instead.

I read the first five chapters of the Rust book a few days ago, and most seems to be well explained. But still there are some difficult points, e.g. ownership transfer of plain stack allocated data. (I think I understand it for data with heap allocated components as for the String data type mostly used in the fine explanations.) In chapter What is Ownership? - The Rust Programming Language we have

Stack-Only Data: Copy

There’s another wrinkle we haven’t talked about yet. This code using integers—part of which was shown in Listing 4-2—works and is valid:

    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);

But this code seems to contradict what we just learned: we don’t have a call to clone, but x is still valid and wasn’t moved into y.

The reason is that types such as integers that have a known size at compile time are stored entirely on the stack, so copies of the actual values are quick to make. That means there’s no reason we would want to prevent x from being valid after we create the variable y. In other words, there’s no difference between deep and shallow copying here, so calling clone wouldn’t do anything different from the usual shallow copying, and we can leave it out.

Well, so for plain stack allocated data like integers there is no reason for a move, as a copy is very fast. But what is for (stack allocated) arrays or structs containing only plain types like integers? The explanations are not fully clear, so my guess was that there is no move and borrow for pure stack allocated data.

But them, in chapter An Example Program Using Structs - The Rust Programming Language the book told us:

Here’s an example where we’re interested in the value that gets assigned to the width field, as well as the value of the whole struct in rect1:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let scale = 2;
    let rect1 = Rectangle {
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
}

We can put dbg! around the expression 30 * scale and, because dbg! returns ownership of the expression’s value, the width field will get the same value as if we didn’t have the dbg! call there. We don’t want dbg! to take ownership of rect1, so we use a reference to rect1 in the next call. Here’s what the output of this example looks like:

“We don’t want dbg! to take ownership of rect1 , so we use a reference to rect1 in the next call.”

So in principle, a function can take ownership for pure stack allocated data like this Rectangle struct? Which we avoid here by use of the reference? Note that there is never a dealloc() operation for stack allocated data. My guess would be, that in this case, the reference is used to avoid the copy effort, like using pointers in C, and not because of ownership transfer? But I am not sure.

Well, I might finally understand this when continuing reading the book or thinking about it more, but asking might be OK as well, as most Rust people seems to be smart and friendly.

Best regards,

Stefan Salewski

1 Like

In your second example, Rectangle cannot be copied because it doesn't implement the Copy trait. Making it "copyable" is trivial (i.e. you can implement the Copy trait with the derive macro), but I would agree with your observation that the use of a reference would be preferable to avoid the work of copying the whole struct.

1 Like

it doesn't matter where the data is allocated, stack or heap, what matters is whether the type implements Copy trait or not. Copy is a marker trait, and built in primitive types like bool, char, u8, isize, etc, are all Copy.

again, allocated on stack or heap doesn't matter. an array type is Copy if the element type is Copy, for example, [u8; N] is Copy, where [String; N] is not.

the function doesn't care whether your argument is from caller stack or heap, it's the argument's type that matters. user defined types (struct and enum) is not Copy by default, so if you don't want to move, you borrow it.

what's called destructor in C++ is the Drop trait in rust. when a variable goes out of scope, it gets drop-ped and the Drop implementation will be called to cleanup any resources.

you can think of ownership as the responsibility to drop the value. e.g. if you call a function by move an argument, the callee owns the argument, which means the value is dropped in the callee.

note Drop and Copy is mutually exclusive.

yes, you'll get the idea very quickly, keep learning, but feel free to ask questions if you need help.

5 Likes

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.