Vec uses unsafe in its retain and dedup_by implementation to allow itself to temporarily have a "hole" in the Vec's data.
In safe code, Rust doesn't allow breaking unique ownership or having uninitialized memory, so every element in a vector or slice must be valid at all times. It also must stay valid in case any function panics and interrupts the loop.
For safe code there's vec.swap_remove, vec.swap, and std::mem::swap, std::mem::replace that help moving data in slices without invalidating the items.
If you coerce to a slice of Cell as outlined in this article, you can keep keep an index to a target cell to write to while iterating over the rest of the slice.