It depends on various things, including optimizations taken place. In this very situation, there is nothing passed at all to those functions - Foo is zero sized, so it is just semantical, with no inpact of generated code. If Foo would be small, it would be copied to the another function - copy would be as cheap as copying pointer, but it would be cheaper to access it (address of the variable would be just an offset to the stack head).
The tricky things would (or may) happen if Foo is big. I believe, that in such case you can expect, is that the function would get just a pointer to stack - it is 100% guaranteed, that:
- the former stack frame lives longer than its child, so it is 100% safe to do this
- the child may take ownership and drop the object after all, because parent know not to do this.
You may check this actually happens using Compiler Explorer mentioned before - just add some data to Foo, but bigger than twice a register size, eg
[i32; 100] (tried also with
Vec<i32>, but the whole allocation makes things less readable).
Also there is something like return value optimization. In general it is very similar - if you are returning big struct, you may expect, that you would actually pass its pointer to the function, so it would be filled by this function in place it would be actually used - obviously only if compiler thinks it is worth to perform the optimization (i believe that struct being twice register size big is a treshold in C++ - I am not sure about Rust, but it turns out it happens if I added
[u32; 100] to your struct)
The issue is obviously how Rust handle complex situations. It may get tricky when you call your function like:
let mut foo = Foo::new();
foo = level1(foo);
because input and output pointers are the same. In this case it is very simple, because it is just memcpy from one location to another (I am supprised there is no check if the pointers are the same, but maybe it is checked by memcpy). However if there is a case that the result is constructed using source in some steps it just may get tricky, but hopefully compiler would do this for you. TBH I expect this to be handled in exactly same way as in clang - I suppose those optimizations are just delegated to be handled by llvm.