Dangling References
A dangling reference (or dangling pointer) is a reference that points to a memory location whose value has already been deallocated, moved, or overwritten while the reference still exists. In C and C++, dereferencing a dangling pointer causes Undefined Behavior (use-after-free vulnerabilities). In Safe Rust, the compiler's lifetime and borrow checker statically prevents dangling references from ever being created.
Classic Scenarios That Would Create a Dangling Reference
1. Returning a Reference to a Local Stack Variable (E0515)
When a function finishes, its entire stack frame is popped and all local variables owned by that function are dropped. Returning &local would hand the caller an address on a destroyed stack frame:
fn dangle() -> &String {
let s = String::from("local");
&s
} <-- `s` is DROPPED here! The returned `&String` would point to freed memory!
// ❌ does not compile
fn make_dangling() -> &'static str {
let local_string = String::from("ephemeral");
&local_string
}
fn main() {}
error[E0515]: cannot return value referencing local variable `local_string`
--> src/main.rs:3:5
|
3 | &local_string
| ^^^^^^^^^^^^^ returns a value referencing data owned by the current function
error: aborting due to 1 previous error
Fix: Return the owned String by value (fn make_owned() -> String { String::from("ephemeral") }) so ownership transfers to the caller instead of dropping at the end of the function.
2. Outer Reference Borrowing From a Shorter Inner Block (E0597)
If a reference variable is declared in an outer scope and assigned a borrow of a variable inside an inner { ... } block, the inner variable is destroyed at } while the outer reference remains in scope:
// ❌ does not compile
fn main() {
let outer_ref: &i32;
{
let short_lived = 99;
outer_ref = &short_lived;
} // `short_lived` is dropped here!
println!("Dangling read: {outer_ref}");
}
error[E0597]: `short_lived` does not live long enough
--> src/main.rs:5:21
|
4 | let short_lived = 99;
| ----------- binding `short_lived` declared here
5 | outer_ref = &short_lived;
| ^^^^^^^^^^^^ borrowed value does not live long enough
6 | }
| - `short_lived` dropped here while still borrowed
7 |
8 | println!("Dangling read: {outer_ref}");
| --------- borrow later used here
error: aborting due to 1 previous error
3. Temporary Values That Are Dropped Too Soon (E0716)
When an expression produces a temporary value that is not bound to a let variable—such as &String::from("temp").as_str() or &vec![1, 2, 3][0]—and you try to store that sub-reference in a variable, the temporary String or Vec is dropped at the semicolon ; at the end of the let statement!
// ❌ does not compile
fn main() {
// `String::from(" hello ")` is a temporary dropped at the `;` on line 3!
let trimmed: &str = String::from(" hello ").trim();
println!("{trimmed}");
}
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:3:25
|
3 | let trimmed: &str = String::from(" hello ").trim();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
4 | println!("{trimmed}");
| ------- borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
3 ~ let binding = String::from(" hello ");
4 ~ let trimmed: &str = binding.trim();
|
error: aborting due to 1 previous error
Fix: Bind the owned String to a named let variable first (let raw = String::from(" hello "); let trimmed = raw.trim();) so the owner lives until the end of the block!