References and Borrowing

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!