fn dangle() {
fn get_str<'a>(s: *const String) -> &'a str {
unsafe { &*s }
}
let s = String::from("hello");
let dangling = get_str(&s);
drop(s);
println!("Invalid str: {}", dangling);
}
#[test]
fn test() {
dangle(); // it should panic, but does not
}
fn main() {
dangle(); // panics
}
The main()
function panics, as I expected. But why does the test()
function run successfully?
As described on this page, your usage of unsafe
is unsound (this was done on purpose in the question) and consequently leads to undefined behaviour.
Undefined behaviour does not mean that the code will necessarily fail or panic.
On the contrary, it could do anything, including working perfectly!
On my computer, the main program does not panic; it simply displays a weird string and when testing it does not panic either.
Even if, on my computer, the pointer does not point to an inaccessible address after drop()
, the bytes at this address have been reused for something else, which looks incorrect when sent to standard output.
This behaviour can change from time to time, depending on anything in the system (the system, the version of the compiler...).
That's why unsound code must be avoided; we can only detect and fix a problem by chance (if it exhibits an obvious incorrect behaviour, such as panicking). If, on the other hand, the problem simply causes data corruption, it could be detected much later during the execution, and it would be very difficult to find what was the real cause of the problem (dangling pointer here). This situation is quite common in C. Safe Rust prevents those pitfalls from happening; unsafe Rust requires the developer to uphold certain guarantees instead.