Advanced Lifetimes and Borrowing

Self-Referential Structure Challenges

A self-referential structure is a struct that stores both an owned value and a reference (or pointer) pointing into that same struct's own fields. Safe Rust rejects self-referential structs because every value in Rust is movable by default, and moving a struct in memory invalidates any internal pointers that point into the struct's own stack address.

Why Moving Invalidates Internal Pointers

When a struct is moved (assigned to another variable, passed by value to a function, or returned from a constructor), the compiler performs a shallow bitwise memcpy of the struct's bytes to a new stack address.

If field slice points to an array stored inline inside field inline_buffer at stack address 0x1000, moving the struct to 0x2000 copies inline_buffer to 0x2000, but the pointer inside slice still points to the old, deallocated stack address 0x1000!

Before Move (Struct at Stack Address 0x1000):
+---------------------------------------+
| 0x1000: inline_buf: ['R', 'u', 's', 't'] <--+
| 0x1004: ptr: 0x1000 ------------------------+
+---------------------------------------+

After Bitwise Move to Stack Address 0x2000:
+---------------------------------------+
| 0x2000: inline_buf: ['R', 'u', 's', 't']
| 0x2004: ptr: 0x1000 ------------------------+ (Dangling pointer to old stack frame 0x1000!)
+---------------------------------------+

How the Borrow Checker Rejects Naive Self-References (E0597 / E0505)

Attempting to construct a struct holding both data: String and view: &'a str and then populating view with &self.data locks the struct in an eternal borrow for 'a, preventing the struct from ever being moved or dropped:

// ❌ does not compile
struct SelfRef<'a> {
    data: String,
    view: Option<&'a str>,
}

fn make_self_ref<'a>() -> SelfRef<'a> {
    let mut item = SelfRef {
        data: String::from("hello world"),
        view: None,
    };
    item.view = Some(&item.data[0..5]);
    // Error: moving `item` out of the function while `item.data` is borrowed!
    item
}

fn main() {}
error[E0515]: cannot return value referencing local data `item.data`
  --> src/main.rs:14:5
   |
12 |     item.view = Some(&item.data[0..5]);
   |                       --------- `item.data` is borrowed here
13 |     // Error: moving `item` out of the function while `item.data` is borrowed!
14 |     item
   |     ^^^^ returns a value referencing data owned by the current function

Safe Alternatives to Self-Referential Structs

Before reaching for Pin<P> or raw pointers, redesign the data structure using one of three safe patterns that remain valid across moves:

Pattern How It Works Example
1. Store relative indices / Range<usize> Store (start, end) byte offsets instead of &str, and slice &self.data[self.range.clone()] on demand Token spans in lexers (Range<usize>)
2. Separate owner and view Keep the owner (String or Arena) in an outer variable and pass &'a str to a separate View<'a> struct let doc = ...; let view = DocView::new(&doc);
3. Shared reference counting (Arc / Rc) Store Arc<str> + sub-range or bytes::Bytes so sub-slices share ownership of the heap buffer Network packet buffers
use std::ops::Range;

struct IndexedSpan {
    text: String,
    highlight: Range<usize>,
}

impl IndexedSpan {
    fn new(text: String, highlight: Range<usize>) -> Self {
        assert!(text.get(highlight.clone()).is_some(), "invalid UTF-8 range");
        Self { text, highlight }
    }

    fn highlighted(&self) -> &str {
        &self.text[self.highlight.clone()]
    }
}

fn main() {
    let span = IndexedSpan::new(String::from("Rust memory model"), 5..11);
    // `span` can be moved freely without invalidating `highlight`:
    let moved_span = span;
    println!("highlighted: {:?}", moved_span.highlighted());
}
highlighted: "memory"