Rust data structures with circular references (2021)
Indices, arenas, and “laundered” pointers
- Several comments critique “manual memory is fine, just use arenas + handles,” arguing it often just hides pointer arithmetic behind vector indices.
- Others reply that indices are still safer than raw pointers due to bounds checks and defined panic behavior.
- Memory savings from using small integer indices instead of pointers are highlighted as substantial in pointer-heavy structures.
- Using indices is seen as good for locality and serialization, but also as easy to misuse (“handle confusion,” logical bugs, leaks).
What counts as memory safety
- Long subthread debates whether bugs in index-based structures are “memory safety” issues.
- One side: out-of-bounds or “use index after free” in safe Rust yields panics or logic errors, not UB or RCE, so still memory-safe in Rust’s sense.
- Other side: you can still get two “owners” of the same logical object and serious semantic bugs, even if not UB.
- Participants stress that Rust equates memory safety with absence of UB; disagreement arises when people use broader notions.
Encapsulating unsafe: Rust vs C/C++ and older languages
- Strong view: C/C++ cannot enforce encapsulation of unsafe code; “if you know what you’re doing” is not a safety boundary.
- Counterpoint: with discipline and libraries, you can approximate this, but the language doesn’t help.
- Rust’s
unsafeand the requirement to explicitly mark unsafe calls are seen as culturally and technically important. - Historical notes mention earlier languages with explicit unsafe/checked modules and tainted binaries.
Techniques for circular references in Rust
- Besides the article’s vector-of-nodes-with-indices, people mention:
- The “Too Many Linked Lists” guide.
Rc/Weak+RefCell.Gc<T>-style pointers and third-party GC runtimes.- GhostCell / qcell patterns using higher-rank lifetimes and tokens.
- Some note that index-based deletion in a
Vec<T>can leak or require extra machinery (e.g.,Option<T>, free lists).
Are parent links / cyclic structures needed?
- One camp: avoid parent pointers and cycles when possible; many algorithms don’t truly need them, and cycles are hard to reason about.
- Others respond that some algorithms genuinely require parent or bidirectional links; if a language blocks that, it’s a limitation, not a virtue.
- There is a sense that Rust’s friction here reveals when cycles are actually necessary vs habitual.
GC vs Rust-style ownership
- Some argue a modern GC would suffice for most Rust use cases; Rust’s contortions around graphs show the cost of no GC.
- Others counter that:
- Many Rust users already know GC’d languages and chose Rust for predictability, performance, and no runtime.
- Even low-latency GCs still hurt determinism and cache behavior in high-performance or real-time-ish code.
- Discussion notes that many projects probably could tolerate GC, but developers value Rust’s other properties (type system, deterministic destruction, ownership-based concurrency).
Isoheaps and alternative memory models
- Isoheaps (per-type isolated heaps) are proposed as an alternative: similar guarantees to indices/arenas but faster and more ergonomic.
- Claims: isoheaps and index-based schemes both prevent RCE, though they don’t prevent all logical misuse.
- Some suggest that borrow-checker-style systems may be overkill compared to isoheap-based manual management.
Ergonomics, lifetimes, and language mix
- Complaints about Rust ergonomics: adding lifetimes is painful; patterns like
&'a Node<'a>are easy mistakes. - Some prefer using two languages (e.g., C for low-level, Go/Python for high-level) instead of bending Rust to all roles.
- Others enjoy Rust as a single language spanning high- and low-level code, citing its expressiveness, safety culture, and performance.