Search code examples
rustbindingcompiler-constructionassign

What does the equal '=' actually do in rust (with/without 'let')?


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.
  1. 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.

  2. 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.

  3. 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?


Solution

  • 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.

    The Long Answer

    1. Whenever you specify a variable in your code, the compiler has to allocate memory for storing the variable's value at runtime. So for each of 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.
    2. 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.
    3. In 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.