I am confused when rust do the memcpy and calls the drop functions. I read some related pages but did not find a detailed description of this.
Here are some simple codes:
struct MyType {
name: String,
age: i32
}
impl MyType {
fn new() -> Self {
let tmp = MyType {
name: String::from("Joy"),
age: 1
};
let addr = &tmp as *const MyType as usize;
println!("Calling new.");
println!("tmp : name: {}, age: {}", tmp.name, tmp.age);
println!("addr: 0x{:X}\n",addr);
tmp
}
}
impl Drop for MyType {
fn drop(&mut self) {
println!("Calling drop.\n");
}
}
fn main() {
println!("");
let a = MyType{
name: String::from("Tom"),
age : 10
};
let addr = &a as *const MyType as usize;
println!(" a : name: {}, age: {}", a.name, a.age);
println!("addr: 0x{:X}\n",addr);
let mut b = a;
let addr = &b as *const MyType as usize;
println!(" b : name: {}, age: {}", b.name, b.age);
println!("addr: 0x{:X}\n",addr);
b = MyType::new();
let addr = &b as *const MyType as usize;
println!(" b : name: {}, age: {}", b.name, b.age);
println!("addr: 0x{:X}\n",addr);
let c = MyType::new();
let addr = &c as *const MyType as usize;
println!(" c : name: {}, age: {}", c.name, c.age);
println!("addr: 0x{:X}\n",addr);
b = c;
let addr = &b as *const MyType as usize;
println!(" b : name: {}, age: {}", b.name, b.age);
println!("addr: 0x{:X}\n",addr);
}
And outputs:
> Executing task: cargo run --package hello_world --bin hello_world <
Compiling hello_world v0.1.0 (/home/dji/proj_learn_rust/hello_world)
Finished dev [unoptimized + debuginfo] target(s) in 0.21s
Running `target/debug/hello_world`
a : name: Tom, age: 10
addr: 0x7FFDB8636AF0
b : name: Tom, age: 10
addr: 0x7FFDB8636BE0
Calling new.
tmp : name: Joy, age: 1
addr: 0x7FFDB8636CB8
Calling drop.
b : name: Joy, age: 1
addr: 0x7FFDB8636BE0
Calling new.
tmp : name: Joy, age: 1
addr: 0x7FFDB8636DB0
c : name: Joy, age: 1
addr: 0x7FFDB8636DB0
Calling drop.
b : name: Joy, age: 1
addr: 0x7FFDB8636BE0
Calling drop.
After let mut b = a;
, it seems that the addresses of a
and b
are not the same. Why not directly transfer the memory of a
to b
when executing the move operation, since a
is no longer valid? It seems that a shallow copy (as memcpy in C) was made when using the let
keyword, without calling drop function.
The addresses of c
and tmp
are the same. At this time, it seems that the real move was executed instead of the memcpy, without calling drop function.
But why does b = c;
call the drop function while let mut b = a;
and let c = MyType::new();
do not?
Is the let
avoiding drop calling?
Let's start with the short answer: the let
is not avoiding drop calling, it is used to introduce new variables.
=
without let
is simple variable assignment.
a
, b
, and c
, the compiler has to allocate a memory location.
That is the reason why a
and b
have different addresses.
let mut b = a
moved the value from memory location a
to memory location b
.
After that move, a
can no longer be used.c
and tmp
are not part of the same function.
Additionally, only one of c
and tmp
can be in scope at any time, so they can reuse the same memory location.let mut b = a
, b
has no value yet.
Therefore, there is nothing to drop
.
In b = MyType::new()
, however, b
already contains the value, that was assigend to it in let mut b = a
.
This value is then dropped.
You can verify this by changing the Drop
implementation of MyType
to print the name of the dropped value.
Similarly, c
does not yet contain a value in let c = MyType::new()
.
In b = c
, however, b
already contains the value assigned to it in b = MyType::new()
, which has to be dropped first.For further explanations, you can check the Rust reference for let statements and assignment expressions.
The documentation of the Drop
trait could also be interesting.