[1] https://soft-dev.org/pubs/html/hughes_tratt__garbage_collect...
Either use unsafe and think about using raw pointers carefully, respecting the soundness rules, or truly redesign it using idiomatic Rust constructs. But don't hide complexity under the rug by using indexes instead of pointers, it's mostly the same thing.
I really enjoyed the write-up though, I learned a lot from it, not to discount that.
In this example, you wrap Gc types in Option, I think that having the Gc type be nullable instead would be an interesting experiment. Having to introduce a lot of places that branch both puts more into the instruction cache, and adds more places that the branch predictor has to track. Besides, you can always add in missing checks if you know that you have a sparse subgraph somewhere. Total potential micro optimization, but it's a bit of fun :-).
I also like how this shows how freelists are a smidge less efficient in safe Rust, as compared to unsafe Rust. In this solution, we have to encode the tag for the discriminated union, this is unnecessary! The unoccupied slots forms its own separate list, and so you know that you will always have unoccupied slots as long as you trace along that path. I assume that that will add up to the maximum alignment of bytes, as arrays need to be well-aligned? So, probably 8 extra bytes per element (you're storing stuff that probably references other stuff).
So maybe eliminate type and concurrency unsafeties also then in the next decades or so.