A very simple question - returning a non-Copy structure from function

struct SA {
    x: i32,
}

fn fg() -> [SA; 1] {
    let a = [SA { x: 1 }];
    return a;
}

struct SA is not Copy, so I wonder, why function fg can return the array?

I think that the array a is allocated in stack and a cannot be copied too. but of course i’m wrong. but why?

fn main() {
    let b = fg();

    assert_eq!(b[0].x, 1);

    println!("END");
}

The array is moved out of the function to the caller. The only difference between Copy vs not is whether the source can be used after the move/copy operation is done. Otherwise, moving or copying is the same thing.

You can see the move better if you do:

fn fg() -> [SA; 1] {
    let a = [SA { x: 1 }];
    let b = a;
    a
}

rustc will tell you that a has been moved, and cannot be used. If SA were Copy, then it’d be fine.

thanks. I conclude that, Copy or Move is only about membership or ownership, it is not about whether the value is really copied in memory.

3 Likes

That’s exactly right. You should consider the value to be copied in both cases; the only difference is whether the original location is still usable.

2 Likes

I think this is the confusing part of Rust. In this program, is “a”'s memory really copied? Just as dikinova mentioned, “a” is allocated on stack which means it will be freed when function returns, in that case, how can we still reference this block of mem without copying “a”'s memory?

a's memory is bitwise copied into the caller, regardless of whether a is Copy or not. You can think of moving as a logical operation: the compiler will prevent you from touching the original value after it’s moved, but that’s it (it may reuse the stack space for something else, and whatnot, but that’s a low-level implementation detail).

In machine code, no actual copying may occur (e.g. optimizer elides the copies), but you’re none the wiser at Rust source code level. But that’s why I mentioned that you should consider both cases doing actual memory copying - that’s the semantic meaning, and the difference between move and Copy is only whether the original location is accessible to your source code.

2 Likes

I would also like to add a detail to what @vitalyd said: the optimizer will avoid the stack as much as possible, putting you variables into registers. There are many cases in which a struct is completely spread across registers and passed to a function as it would take n arguments instead of a single struct.

The language works on abstractions, and it is often not easy to predict the behaviour of the compiler. The concept of copy-ing, move-ing and clone-ing are mere abstractions, if you focus on ideas like “a copy must produce a separate object on the stack, doubling the amount of stack memory usage”, you will end up fighting with the optimizer, disabling it and writing raw asm code. And this is not a joke, some embedded programmers really do that.

5 Likes

oh man yes I’m an embedded programmer and yes again, I really care about what happened in the background. :smile:
But okay, let’s focus on the abstractions. What I still feel confusing is, what is the difference between “Copy” you guys mentioned and the “Copy” trait of Rust. For example, if I change the program to:

#[derive(Debug)]
struct SA {
    x: i32,
    s: Vec<u8>,
}

fn fg() -> SA {
    let mut a = SA {
        x: 1,
        s: Vec::new(),
    };
    a.s.push(0);
    a.s.push(1);
    a
}

fn main() {
    let b = fg();
    println!("b is: {:?}", b);
}

in this program, obviously struct SA is not “Copy” because it contains a Vec, you can prove it by adding a “Copy” in #derive then you’ll hit a build error. But the program shows that local variable “a” in function fg gets Copied so that we can reference the SA object outside the function. So now it seems that the bitwise memory copy is totally different thing with “Copy” trait in Rust, right? I mean, it’s not “objects can only be copied when it supports Copy trait”, right? If yes, what is the purpose of inventing “Copy” trait? Sorry perhaps this is a dumb question but I’m confused on this.

1 Like

Copy is the (marker) trait that identifies types that can be copied in the rust sense of the word. Namely, a normally-would-be-a-move operation, like assignment I showed earlier with let b = a;, will allow the original value (a in this case) to still be usable. So when people call something a copy type in Rust, they’re typically talking about some type that implements Copy. Since Rust is a move-by-default language, only the Copy type exists to mark types that are copyable in terms of Rust’s ownership semantics.

To give you another perspective on this, consider a generic function:

fn echo<T>(val: T) -> T{
    let x = val;
    val
}

This won’t compile because val is moved from, and becomes unusable. Since Rust is move-by-default, this is the stock behavior. If you wanted to indicate that you require a copyable type, you’d instead put a trait bound:

fn echo<T: Copy>(val: T) -> T {
   let x = val;
   val
}

This is now fine because we know val is copyable since we require that in the signature. So the Copy trait is how you convey this requirement in generic code.

3 Likes

It for me is best to think of instances. In rust you can have variables uninitialised. Typically you need to call the constructor to create a new instance but Copy is the special case where (assuming everything is move) there is a second instance remaining in the original variable.

1 Like

Alright, that makes sense, I feel much better now. :smile:
If so, if Copy trait is mainly for semantics, why does only primitive types and types which are consisted by primitive types support Copy trait?

1 Like

Reasoning about the needs of specifying Copy or not, a simple example could be the MutexGuard. This is declared as following:

pub struct MutexGuard<'a, T: ?Sized + 'a> {
    __lock: &'a Mutex<T>,
    __poison: poison::Guard,
}

poison::Guard is the following:

pub struct Guard {
    panicking: bool,
}

A (const) reference is Copy, bool is Copy too, therefore the MutexGuard could be Copy-able looking at its memory layout. However we do not want to be Copy, just because you will have two guards for a Mutex. :boom:

3 Likes

The primary reason is ownership (and associated semantics).

I see @dodomorandi gave you an example, but let me offer up another one. Take Vec as an example - it owns some heap memory (where the data is stored). If Vec were Copy, you couldn't just naively create another Vec that points to the same heap memory - who owns it? Who's responsible for deallocating it? So it would require some sort of deep copy (aka Clone in Rust) to ensure the "copied" vector (and its contained elements, if they're not Copy themselves) is distinct from the original. But now if let vec2 = vec1; were to actually do deep copies, we'd have hidden performance gotchas and possible unintended semantics violations (e.g. perhaps you really want the same vector, and not two separate copies).

2 Likes

Understood. Thanks guys, really appreciated for your inputs.
Regarding Copy trait, I hit an awkward thing of it recently which is, I found I can’t put my struct in array as long as my struct contains any member which doesn’t support Copy trait(like a Vec, a String). I’m a C/C++ programmer and also a newbie of Rust, so this surprised me and I was like: “this is really weird!”.
Anyway, I know most of time, we can use Vector to replace array, so just need some time to get familiar with Rust design ideas.

You can definitely put non-Copy types into an array - the array itself won't be Copy anymore, it'll be moved (in terms of ownership) around instead.

Oh really? If so how to fix the build error of this program:

#[derive(Debug)]
struct SA {
    x: i32,
    s: Vec<u8>,
}

fn fg() -> SA {
    let mut a = SA {
        x: 1,
        s: Vec::new(),
    };
    a.s.push(0);
    a.s.push(1);
    a
}

fn main() {
    let b = fg();
    println!("b is: {:?}", b);
    
    let c = [fg(); 10];
}

the build error says:

Compiling playground v0.0.1 (file:///playground)
error[E0277]: the trait bound `SA: std::marker::Copy` is not satisfied
  --> src/main.rs:21:13
   |
21 |     let c = [fg(); 10];
   |             ^^^^^^^^^^ the trait `std::marker::Copy` is not implemented for `SA`
   |
   = note: the `Copy` trait is required because the repeated element will be copied

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

The problem is not that the array needs Copy elements, but that that specific array initialiser wants Copy elements, so that it doesn’t have to call fg repeatedly.
Remember, we can also use that initialiser to create 4MB buffers for decompression or something. We wouldn’t want to have a footgun where we accidentally call an expensive constructor 4.096.000 times

1 Like

It gives you this error because you are trying to Copy SA 10 times. You have the same situation in C++ with the following:

#include <array>
#include <vector>

struct SA {
  SA() = default;
  SA(const SA&) = delete;
  SA& operator=(const SA&) = delete;

  int a;
  std::vector<int> s;
};

SA fg() {
    SA a {1, {}};
    a.s.emplace_back(0);
    a.s.emplace_back(1);
    return a;
}

int main() {
  std::array<SA, 10> v;
  std::fill(std::begin(v), std::end(v), fg());
}

It is more or less the same thing. If you try to compile it, you will obtain an error, saying that you cannot copy SA because it has the copy constructor deleted. And it is almost as like not implementing Copy trait in Rust.

1 Like

Oh!! So I made another stupid mistake! I misunderstood the array initializer. Thanks for your patience guys. :wink:

2 Likes

That's how all of learned this stuff, lots and lots of "stupid" mistakes! :heart:

P.s. mind if we take your problems back to your thread, so we don't highjack @dikinova 's thread? :wink:

3 Likes