Understanding the implication of i32 being "copy"?

So my question about the code below comes down to: why does 0x7fff0b5e2e74 appear twice in the output?

fn main () {

    let mut a = "Rogers".to_string();
    let mut b = 3;
    
    println!("{:p}", &a);
    println!("{:p}", &b);


    fn nothing(snape:String) -> String {
        nothing2(snape)
    }
    
    fn nothing2(y:String) -> String {
        y
    }
    
    fn nada(snape:i32)-> i32 {
        println!("{:p}", &snape);
        nada2(snape)
    }
    
    fn nada2(y:i32)-> i32 {
        println!("{:p}", &y);
        y
    }
    
    a = nothing(a);
    b = nada(b);
    
    println!("{:p}", &a);
    println!("{:p}", &b); 
    
    // I expected b after the call to nada to have a new address because i32 is copy. 
    // Is this a compiler optimization or am I missing something?
    
//  Compiling playground v0.0.1 (/playground)
//    Finished dev [unoptimized + debuginfo] target(s) in 0.55s
//     Running `target/debug/playground`
// Standard Output
// 0x7fff0b5e2e50
// 0x7fff0b5e2e74
// 0x7fff0b5e2dac
// 0x7fff0b5e2d4c
// 0x7fff0b5e2e50
// 0x7fff0b5e2e74

}

(Playground)

Output:

0x7fff0b5e2e50
0x7fff0b5e2e74
0x7fff0b5e2dac
0x7fff0b5e2d4c
0x7fff0b5e2e50
0x7fff0b5e2e74

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.55s
     Running `target/debug/playground`

    let mut b = 3;
    println!("&b {:p}", &b);
    fn nada(snape:i32)-> i32 { /* ... */ }
    b = nada(b);
    println!("&b {:p}", &b); 
    
    // I expected b after the call to nada to have a new address because i32 is copy. 
    // Is this a compiler optimization or am I missing something?

When you passed b to nada you passed a copy, so the local b variable was not uninitialized. You didn't move the b variable. Then you overwrote b with the result. But you still didn't move b.

1 Like

I thought a "copy" passed to nada would have a different address than the original. And indeed the addresses printed from within nada and nada2 are different. So why does b end up with the original address at the end of main? It was assigned the return value from nada. My results seems like i32 behaves the same as String.

Thinking about it. I now understand you are saying b in main always has the same address. It's the value that is changing. So even if I completely returned something different from nada, my println! results would be the same. The only way I could change the "address" of b in main would be by another let b = ... statement. I verified that. Thanks for clearing up a not-so-bright experiment on my part.

I think you figured it out while I was writing the below, but since I wrote it, I'll post it anyway.


Variable b in main occupies a position on the stack. [1]

    b = nada(b);
//           ^    Part 1

A copy of b is made to elsewhere on the stack for the call to nada.

    b = nada(b);
//      ^^^^^ ^   Part 2

nada executes and returns a value somehow, let's say in a register.

    b = nada(b);
//  ^^^^          Part 3

The returned value is copied into b's stack location.


b's stack location never changed.

The values that get passed around don't have some sort of inherent address identity that goes with them via copies and moves. Not every value is boxed with a fixed address.

There are some other examples in this chapter of the book.


  1. Well it doesn't have to, it could be in a register or completely optimized out in a typical program, but you're basically forcing it to be on the stack by examining its address. ↩ī¸Ž

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.