5

Does the variable s in print_struct refer to data on the heap or on the stack?

struct Structure {
    x: f64,
    y: u32,
    /* Use a box, so that Structure isn't copy */
    z: Box<char>,
}

fn main() {
    let my_struct_boxed = Box::new(Structure {
        x: 2.0,
        y: 325,
        z: Box::new('b'),
    });
    let my_struct_unboxed = *my_struct_boxed;
    print_struct(my_struct_unboxed);
}

fn print_struct(s: Structure) {
    println!("{} {} {}", s.x, s.y, s.z);
}

As I understand it, let my_struct_unboxed = *my_struct_boxed; transfers the ownership away from the box, to my_struct_unboxed, and then to s in the function print_struct.

What happens with the actual data? Initially it is copied from the stack onto the heap by calling Box::new(...), but is the data some how moved or copied back to the stack at some point? If so, how? And when is drop called? When s goes out of scope?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
mallwright
  • 1,670
  • 14
  • 31
  • 5
    `/* Use a box, so that Structure isn't copy */` This is not necessary: structs never implicitly implement `Copy`. This used to be the case in old (pre-1.0) versions of Rust, though, so if you got that from a book/article/etc., it's very out of date. – trent Jan 07 '20 at 14:07
  • @trentcl thanks for that! – mallwright Jan 07 '20 at 16:43

2 Answers2

4

The Structure data in my_struct_boxed exists on the heap and the Structure data in my_struct_unboxed exists on the stack.

Therefore naïvely speaking (no compiler optimizations), a move or copy operation when dereferencing (*) your Box will always involve copying of the data. On the borrow-checker/static-analysis side, since the Copy trait is not implemented for Structure, this represents a transfer of ownership of the data to the my_struct_unboxed variable.

When you call print_struct, another copy would take place that would copy the bits in memory representing your Structure from the local variable to the function's arguments call-stack. Semantically, this again represents a transfer of ownership into the print_struct function.

Finally when print_struct goes out of scope, it drops the Structure which it owns.


Reference: std::marker::Copy

Excerpt

It's important to note that in these two examples, the only difference is whether you are allowed to access [your variable] after the assignment. Under the hood, both a copy and a move can result in bits being copied in memory, although this is sometimes optimized away.

Note the last part "this is sometimes optimized away". This is why the earlier descriptions were simplified to assume no compiler optimizations i.e. naïve. In a lot of cases, the compiler will aggressively optimize and inline the code especially with higher values for the opt-level flag.

sshashank124
  • 31,495
  • 9
  • 67
  • 76
  • Do you have a reference for what you are saying here? I am suspicious that Rust would be such a fast language if all these copies that you mention were always being made... – mallwright Jan 07 '20 at 12:30
  • Ok, so to be clear, there are two meanings of the word copy being used. *Copy* as in the trait that allows us to create two instances of a variable from one instance, and *copy* in the sense of copying the low-level bits between frames on the stack and to/from the heap, right? – mallwright Jan 07 '20 at 12:37
  • Yes. For the compiler, everything by default has move semantics unless your type implements the `Copy` trait. For simplicity, let's refer to the bit-level memory copying as `memcpy`, the underlying function used for copying – sshashank124 Jan 07 '20 at 12:39
2

If so, how?

Both "copy" and "move" are semantically memcpy (though that may be optimised to something else, or even nothing whatsoever).

And when is drop called? When s goes out of scope?

Yes. When print_struct ends it cleans up its local scope, and drops s.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • So at what point in the code is the memory on the heap freed? – mallwright Jan 07 '20 at 12:32
  • 3
    The Box is dropped (and its memory freed) when it's deref'd. Box is special-cased in the compiler to move on deref' (there have been requests / RFCs about a hypothetical DerefMove but so far it remains unavailable to "userland" Rust). – Masklinn Jan 07 '20 at 12:39
  • Is it possible through compiler optimisations that `s` actually refers to the data on the heap and that it is only freed after `s` has gone out of scope? – mallwright Jan 07 '20 at 13:04
  • No, that would be the case if `s` was of type `Box` – sshashank124 Jan 07 '20 at 13:31
  • [How do I get an owned value out of a `Box`?](https://stackoverflow.com/q/42264041/155423) – Shepmaster Jan 07 '20 at 14:57
  • 2
    Isn't the box dropped (i.e. the memory freed) at the end of the scope it's defined in? The borrow checker makes sure that the memory isn't used anymore after the move, but I was assuming the actual drop happens later. (I may very well be wrong.) – Sven Marnach Jan 07 '20 at 15:11
  • @SvenMarnach looking at the MIR output, seems you are right – sshashank124 Jan 07 '20 at 16:58