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?
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?
- 2 Why do you expect dangling pointers to panic? – dumbass Commented yesterday
- Expecting a panic is falsehood 7 or 8 programmers believe about undefined behavior – cafce25 Commented yesterday
2 Answers
Reset to default 5As 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.
What @prog-fh says.
I just wanted to add that when working with unsafe
code, it is highly recommended to use miri
during testing.
miri
is a linter that specifically tries to detect unsound code. It isn't guaranteed to find everything, but it's quite good at it.
To check your code with miri
, run:
cargo +nightly miri test
Then, you get the following error:
error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free)
--> ~\.rustup\toolchains\nightly-x86_64-pc-windows-gnu\lib\rustlib\src\rust\library\core\src\fmt\mod.rs:2653:1
|
2653 | fmt_refs! { Debug, Display, Octal, Binary, LowerHex, UpperHex, LowerExp, UpperExp }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (use-after-free)
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE on thread `test`:
...
note: inside `dangle`
--> src\main.rs:9:5
|
9 | println!("Invalid str: {}", dangling);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside `test`
--> src\main.rs:14:5
|
14 | dangle(); // it should panic, but does not
| ^^^^^^^^
note: inside closure
--> src\main.rs:13:10
|
12 | #[test]
| ------- in this procedural macro expansion
13 | fn test() {
| ^
= note: this error originates in the macro `fmt_refs` which comes from the expansion of the attribute macro `test` (in Nightly builds, run with -Z macro-backtrace for more info)