Reputation: 3
Im struggling to fully grasp what happens to the memory part during ownership transfer. if a value is owned by one variable and transferred to another, what happens to the memory in the original variable's stack frame after the transfer
As an example, t is out of scope, but test still works. Where is Test(1) stored, and how is it still valid after the block ends if its created in the stack?
struct Test(u8);
impl Drop for Test {
fn drop(&mut self) {
println!("{} is dropped", self.0);
}
}
fn main() {
let test;
{
println!("step 1");
let t = Test(1);
println!("step 2");
test = t;
}
println!("{}", test.0);
}
output:
step 1
step 2
1
1 is dropped
When an owner goes out of scope, Rust automatically clean-up the data associated with that owner by reclaiming memory. Here's an example that seems to contradict it:
fn main() {
println!("step 1");
let test = create_struct();
println!("step 2");
println!("{}", test.0);
}
pub fn create_struct() -> Test {
Test(1)
}
output:
step 1
step 2
1
1 is dropped
If memory is reclaimed when an owner goes out of scope, how does returning a value from a function prevent this?
Judging from the output of the first example, it's not copied because only one instance of Test was dropped, also that means the stack memory wasn't reclaimed in the inner block or what is happening here?
Upvotes: 0
Views: 101
Reputation: 23443
Disregarding compiler optimizations, here's how the stack may look conceptually at various points in your examples:
struct Test(u8);
impl Drop for Test {
fn drop(&mut self) {
println!("{} is dropped", self.0);
}
}
fn main() {
// Stack:
// +-----+
// | xxx | test
// +-----+
// | xxx | t
// +-----+
let test;
{
println!("step 1");
let t = Test(1);
// Stack:
// +-----+
// | xxx | test
// +-----+
// | 1 | t
// +-----+
println!("step 2");
test = t;
// Stack:
// +-----+
// | 1 | test
// +-----+
// | xxx | t (in practice the value is still "1", but
// +-----+ this is now irrelevant and ignored)
}
println!("{}", test.0);
// Stack:
// +-----+
// | 1 | test
// +-----+
// | xxx | t
// +-----+
// - `drop (test)` is called implicitly here.
// - `drop (t)` isn't called (and indeed cannot be called) since
// the corresponding space is considered unused.
}
Note that space is allocated on the stack only once at the beginning of the function and freed all at once when the function returns, even if some parts of the space may be unused at some points in time (denoted by xxx
in the schematics above).
Note also that the optimizer may decide to merge the two allocations so that test
and t
have the same address and there will be no need to copy anything on the test = t
line.
fn main() {
// Stack:
// +-----+
// | xxx | test
// +-----+
println!("step 1");
let test = create_struct();
// Stack:
// +-----+
// | 1 | test
// +-----+
println!("step 2");
println!("{}", test.0);
// `drop (test)` is called implicitly here.
}
pub fn create_struct() -> Test {
// Stack:
// +-------+
// | xxxxx | test (from the `main` stack frame)
// +-------+
// | ptr | code address from which the function was called
// +-------+
// | &test | address where the return value must be stored
// +-------+
Test(1)
// Stack:
// +-------+
// | 1 | test (from the `main` stack frame)
// +-------+
// | ptr | code address from which the function was called
// +-------+
// | &test | address where the return value must be stored
// +-------+
}
Note that the function calling convention is unspecified. I chose to show a convention where the caller passes the address of the return value as a hidden argument to the function, and where that argument is passed on the stack. Depending on the size of the return value, the compiler may also choose to:
Upvotes: 0
Reputation: 155366
Disclaimer: what actually happens in memory is difficult to pinpoint due to optimizations. For example, a variable doesn't need to be stored in memory at all, it could be in a CPU register. The remainder of this answer assumes that the variables are stored in memory in the "obvious" way.
if a value is owned by one variable and transferred to another, what happens to the memory in the original variable's stack frame after the transfer
It remains unused (or gets reused for storing other variables, with optimization).
As an example, t is out of scope, but test still works. Where is Test(1) stored, and how is it still valid after the block ends if its created in the stack?
It is first stored in t
, then moved (bitwise-copied) to test
. After the move, the memory in t
is no longer relevant.
If memory is reclaimed when an owner goes out of scope, how does returning a value from a function prevent this?
It prevents this by moving the value to another location. If you just called create_struct();
without assigning the value to anything, it would indeed get dropped. By assigning it to a variable, you effectively extend its life.
Judging from the output of the first example, it's not copied because only one instance of Test was dropped
That's correct - it wasn't copied, it was moved. Unlike C++, Rust doesn't do implicit copies, you have to call .clone()
explicitly, and then that value is moved.
also that means the stack memory wasn't reclaimed in the inner block or what is happening here?
The stack memory gets reclaimed once the function returns. Before that, moving or dropping the value doesn't reclaim the memory due to how stack works.
Upvotes: 0