I have a simple example where the behaviour of Rust does not match my mental image, so I am wondering what am I missing:
fn make_local_int_ptr() -> *const i32 {
let a = 3;
&a
}
fn main() {
let my_ptr = make_local_int_ptr();
println!("{}", unsafe { *my_ptr } );
}
Result:
3
This is not what I would expect. Using the notation given in The Stack and the Heap, I would expect the stack frame to look like this:
Address | Name | Value
-----------------------
0 | a | 3
inside make_local_int_ptr()
, but after this line,
let my_ptr = make_local_int_ptr();
Since a
goes out of scope, I would expect the stack to get cleared, but it apparently does not.
Furthermore, if I define another variable between creating my_ptr
and printing the dereferenced value of it:
fn main() {
let my_ptr = make_local_int_ptr();
let b = 6;
println!("{}", b); // We have to use b otherwise Rust
// compiler ignores it (I think)
println!("{}", unsafe { *my_ptr } );
}
My output is:
6
0
Which again is not what I expected, I was thinking:
Address | Name | Value
-----------------------
0 | b | 6
In which case my output would be:
6
6
or even (in C++ and Go I was getting this result):
Address | Name | Value
-----------------------
1 | b | 6
0 | a | 3
In which case my output would be:
6
3
Why am I getting the output that I am getting? Why is returning a pointer to a local variable even allowed? The variable goes out of scope, and the value where the pointer is pointing to becomes unpredictable.
You shouldn't be returning a pointer to a local stack variable at all. Doing so is undefined behaviour, and the compiler is completely free to do whatever it wants.
When you say
unsafe
, you are promising the compiler that you will manually uphold all of its expected invariants... and then immediately breaking that promise.To put it bluntly: you're violating memory safety, all bets are off. The solution is to not do that.
To explain why you might be seeing this behaviour, however (again, this is undefined behaviour, nothing is guaranteed): the stack isn't "cleared" in the sense that its overwritten with zeroes; it's just not valid to read from it any longer.
Also, because the call to
make_local_int_ptr
is finished, the compiler has no reason to preserve its stack space, so it can re-use the space for anything. The0
is possibly due to the call toprintln!
?