Hacker News new | ask | show | jobs
by steveklabnik 4087 days ago
Semantically speaking, the only difference between a move and a copy is that you're allowed to use a copy type afterwards, and you're not allowed to use a move type afterwards. It's still a memcpy. Of course, these may be elided by optimization passes.
1 comments

> Semantically speaking, the only difference between a move and a copy is that you're allowed to use a copy type afterwards

Are you speaking about Rust specifically, or move in general? I had always understood that move was no more expensive than passing by reference. That is, I had thought the memory was on the heap and didn't need to be copied each time someone new took ownership of that heap space.

I mean in Rust.

> That is, I had thought the memory was on the heap

An example:

    let x = Box::new(5);
    let y = x;
While the 5 is allocated on the heap, when we move x to y, _the pointer itself_ is memcpy'd. That's why Box<T> isn't Copy; as you say, a simple memcpy won't actually duplicate the structure. Make sense?

(and in this case, I'd assume llvm's optimizations would realize the copy is superflous and just elide it, but semantically, that's what's up)

Oh, agreed. But how about this:

    let x = BigExpensiveStruct::new();
    some_function(x);
That won't trigger a big, expensive memcpy of the BigExpensiveStruct, will it? I'd thought that its memory was on the heap.
If that's just a struct, it's stack allocated. So it's not ok the heap in the first place. IIRC, LLVM may optimize passing it to the function by reference though.
Good to know. Thanks! So I guess the moral is, if you have a big, expensive struct, make sure the expensive part is in a sub-structure you know is heap allocated, such as a Vec. E.g.:

    struct Expensive {
        cheap_value: u32,
        expensive_value: Vec<u32>
    }
Does that seem like a good maxim?
That is not a big expensive struct (to memcpy). (edit: I'm just bickering about what to call things, what you say is right.)

Your intuition about the performance of this sort of thing might be served by reading about how calling conventions work. You might not need to copy the struct from a local variable to where it belongs on the stack or in registers when calling a function, either because the calling convention says you put a pointer to the struct somewhere (depending on its size) or because you're (you being a compiler) clever enough to put it in the place that'll end up in the argument list later. The callee, however, has less flexibility, and if it needs to pass the value somewhere else, it'll probably have to copy the data. This is way better than allocating it on the heap -- stuff on the stack is in the L1 cache, you compute it's location statically instead of having to traverse data structures, but yeah if you found yourself copying around a 1000-byte struct you might want to box it or pass it by reference. I only know about C and C++ calling conventions though, so don't infer from my comment that Rust isn't doing anything "special" for big non-copyable structs -- I wouldn't know.

Well, it depends. I mean, generally, you want to give flexibility to your users. Maybe I _do_ want to stack allocate an ExpensiveStruct for some reason. You can always Box the struct to have it on the heap.