I am trying to use a Vec
as part of a shared state struct that will only be used within a single thread (ie. it doesn't have to be Sync
).
Normally, I understand that the recommendation would be to use RefCell<Vec<T>>
. However, I would strongly prefer to avoid this if possible, since accessing to the vector occurs on a hot path very frequently, and RefCell
does impose a performance penalty. I expect a lot of reads, each of which requires immutably borrowing the RefCell
, which is expensive. (And I cannot hold on to a std::cell::Ref
between operations in my use case).
I did consider using Cell<Vec<T>>
, but it doesn't seem to optimize perfectly. For example, even when disabling bound checks using get_unchecked()
(Godbolt):
pub unsafe fn cell_vec_get_unchecked(v: &Cell<Vec<i32>>, i: usize) -> i32 {
let vec = v.take();
let res = unsafe { *vec.get_unchecked(i) };
v.set(vec);
res
}
I am considering making my own UnsafeCell<Vec<T>>
wrapper which enforces safety simply by being !Sync
and preventing reentrancy; ie., it behaves similarly to Cell<Vec<T>>
, except we skip the steps of v.take()
and v.set(vec)
. However, in the case of pushing a value, which can potentially reallocate, I am wondering if this is still sound?
I believe the only way this could be unsound is if the allocator itself somehow triggers a recursive call on my shared state, while it is in the middle of a push
operation. How reasonable is it to assume that this will not happen? GlobalAlloc
is unsafe to implement, and requires that the allocator is infallible (and doesn't panic), which gives me some confidence -- however, it doesn't explicitly spell out that, for example, allocators can't have user-specified callback code which runs during allocation; in my case, this could trigger UB if the user registers a callback which runs during a push
operation and accesses the shared vector.