Self-referential structs: Which version is for you?

Writing self-referential structs in Rust is a complete nightmare; the borrow checker won't let you do it natively. Testing three common workarounds for this pattern. Which approach do you actually commit to?

Version 1: Unsafe & Raw Pointers

No dependencies; total control but requires manual safety audits.

struct SelfRef {
    data: String,
    slice: *const str,
    _pin: std::marker::PhantomPinned,
}

Version 2: Index Offsets

100% safe code; avoids lifetime issues entirely by storing ranges.

struct IndexRef {
    data: String,
    slice_range: std::ops::Range<usize>,
}

Version 3: Macro Crates (ouroboros)

Clean syntax; abstracts the pain away but introduces a heavy dependency.

#[ouroboros::self_referencing]
struct MacroRef {
    data: String,
    #[borrowed]
    slice: &str,
}

Offsets feel like a workaround that loses type expressiveness; raw pointers are an open invitation for UB. Is using a macro crate the only sane path forward; or do you just refactor the data flow to avoid this entirely?

Now tell me which one is for you?

I never had a need for a self-referential struct yet (although I understand async relies on it internally).

So I guess my answer is "none of them"... sort of.

There is an earlier post here:

Edit: I have used Rc to create recursive data structures, not sure if that counts.

You should probably link to the discussion on reddit (and any other sites). I forget if that suggestion is explicitly written down anywhere here.

Anyway, the brief version of my comment on reddit is basically “I prefer generics over macros, so I prefer yoke’s approach”.

Admittedly… I actually switched from yoke to manual unsafe in my formerly-largest usage of yoke, since that use case also required some unsafe for two other purposes (so what’s one more?).

I do not like either, I would prefer something like:

struct MacroRef {
    data: String,
    interest: Range,
}

Because other variants do not tell you that there is some part of your interest in the data.

I would do Iterator if it's posible.

I've never needed a self-referential struct, possibly because I almost always use indices whenever I get to that point, so self-referencing isn't even considered. If I had to, though, I would definitely go with ouroboros or yoke. Self-referential types are notoriously easy to screw up, so I would much rather use code that hundreds of people have vetted.