2

The phrase "when scope exits the values get automatically popped from stack" is repeated many times, but the example I provide here disproves the statement:

fn main() {
    let foo = foobar();
    println!("The address in main {:p}", &foo);
}

fn foobar() -> Employee {
    let emp = Employee {
        company: String::from("xyz"),
        name: String::from("somename"),
        age: 50,
    };
    println!("The address inside func {:p}", &emp);
    emp
}

#[derive(Debug)]
struct Employee {
    name: String,
    company: String,
    age: u32,
}

The output is:

The address inside func 0x7fffc34011e8
The address in main 0x7fffc34011e8

This makes sense. When I use Box to create the struct the address differs as I expected.

  1. If the function returns ownership (move) of the return value to the caller, then after the function execution the memory corresponds to that function gets popped which is not safe, then how is the struct created inside the function accessible even after the function exits?
  2. The same things happens when returning an array. Where are these elements stored in memory, whether in the stack or on the heap?
  3. Will the compiler do escape analysis at compile time and move the values to the heap like Go does?

I'm sure that Employee doesn't implement the Copy trait.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
chellathurai
  • 115
  • 1
  • 1
  • 8
  • 1
    See also [Can I efficiently return an object by value in Rust?](https://stackoverflow.com/q/27835375/155423); [Why does the address of an object change across methods?](https://stackoverflow.com/q/38302270/155423) – Shepmaster Sep 15 '20 at 16:00
  • *is repeated many times* — **citation needed** – Shepmaster Sep 15 '20 at 16:01
  • 1
    Being "popped" from the stack of the *abstract machine*, and being copied to a new location in the stack in actual execution on real hardware are two different things. You cannot prove or disprove anything about the behavior of the abstract machine by running code. There is no reason to assume that the address of a value will be changed when it is passed into or returned from a function. [Pointers are not integers](https://www.ralfj.de/blog/2018/07/24/pointers-and-bytes.html), and languages like Rust and C are not low-level languages. – trent Sep 15 '20 at 16:43

1 Answers1

2

In many languages, variables are just a convenient means for humans to name some values. Even if on a logical point of view we can assume that there is one specific storage for each specific variable, and we can reason about this in terms of copy, move... it does not imply that these copies and moves physically happen (and notably because of the optimizer). Moreover, when reading various documents about Rust, we often find the term binding instead of variable; this reinforces the idea that we just refer to a value that exists somewhere. It is exactly the same as writing let a=something(); then let b=a;, the again let c=b;... we simply change our mind about the name but no data is actually moved.

When it comes to debugging, the generated code is generally sub-optimal by giving each variable its own storage in order to inspect these variables in memory. This can be misleading about the true nature of the optimised code.

Back to your example, you detected that Rust decided to perform a kind of return-value-optimization (common C++ term nowadays) because it knows that a temporary value must appear in the calling context to provide the result, and this result comes from a local variable inside the function. So, instead of creating two different storages and copying or moving from one to another, it is better to use the same storage: the local variable is stored outside the function (where the result is expected). On the logical point of view it does not change anything but it is much more efficient. And when code inlining comes into play, no one can predict where our variables/values/bindings are actually stored.


Some comments below state that this return-value-optimisation can be counted on since it takes place in the Rust ABI. (I was not aware of that, still a beginner ;^)

prog-fh
  • 13,492
  • 1
  • 15
  • 30
  • It means that when the optimizer finds the return value in function it create those value on the stack which doesn't destroy even the function exits and points to that memory right? – chellathurai Sep 15 '20 at 16:03
  • @chellathurai `Employee`s space is allocated in `main`s stack frame, not `foobar`s. – Shepmaster Sep 15 '20 at 16:04
  • 1
    @chellathurai yes, and if you know C, it's like providing a function with a pointer parameter, in order to store the result in the *calling context* (even if nothing guarantees that this exactly happens). – prog-fh Sep 15 '20 at 16:05
  • can you provide any source that demystifies this **Employees space is allocated in mains stack frame, not foobar** – chellathurai Sep 15 '20 at 16:11
  • @prog-fh yah this is just for the sake of my understanding,that's why I am not able to find more sources related to it. – chellathurai Sep 15 '20 at 16:17
  • *does not have to be mandatory* — it is not. See my [earlier comment](https://stackoverflow.com/questions/63905350/how-can-the-memory-address-of-a-struct-be-the-same-inside-a-function-and-after-t#comment113005634_63905350) – Shepmaster Sep 15 '20 at 16:24
  • 1
    Did you read Shepmaster's link? [Can I efficiently return an object by value in Rust?](https://stackoverflow.com/q/27835375/155423) – John Kugelman Sep 15 '20 at 16:26
  • 1
    @prog-fh Probably not too many sources since the Rust ABI isn't stabilized, however its the same concept as whats in the [C++ Itanium ABI](https://itanium-cxx-abi.github.io/cxx-abi/abi.html#calls): "if [...criteria...] the caller passes an address as an implicit parameter. The callee then constructs the return value into this address." – kmdreko Sep 15 '20 at 16:27
  • @JohnKugelman yes I read it,that gives the detail about how the lower level stuffs are optimized in this case. – chellathurai Sep 15 '20 at 16:51