I retrieve a raw pointer from a libc call (Linux if that matters) that I need to store in that struct. I want that struct to be in a OnceLock to avoid the process of a long object recreation every time I need it. That object is on a server code. Think "database handle".
I tried many things but the error is still the same : my pointer is not safe. I don't know how to work around that. The only solution so far is the object recreation (not good).
Any idea how to go around this ? I need this logic more or less.
I simplified the playground as much as I could.
Pointers are not Send or Sync by default, out of caution. You can unsafe impl Send/Sync for Handle without any safety concerns, but you will still need to use proper unsafe code to use the pointer, as well as the UnsafeCell. And in order to do that, you need to understand the rules of unsafe.
You've given reasons for why you're using OnceLock and why there is a raw pointer, but there are many things in the middle that need to be known in order to design a solution.
Do you need to change the pointer or any other fields after initializing?
Under what circumstances is the pointer null, dangling, or unaligned?
Do you need exclusive access to Records when using it?
The pointer may change overtime. It is not occurring frequently but may happen. The other fields will be change at the same time. We are talking maybe once a week. Too many changes in that struct is to be considered a flaw in the design.
The pointer will never be null. It is handled in a code section. But the pointer has to be initialized.
UnsafeCell<*mut Records> is used in the code instead of Arc<Mutex<*mut Records>>. It is just that when I tried with OnceLock, it broke.
version is an UnsafeCell because of &self and &mut self hell. I had an issue with a logic that called &mut self <-> &self I ended deciding that all my functions would be &self. I could get away with version being u32 though. I am ready to buy the light cost of indirection happening so unfrequently.
How do you change the pointer safely, so that the old pointer is not accidentally used due to a race condition?
Using UnsafeCell will not make it sound to mutate a static value, so I believe this is unsound. You could use an AtomicU32 safely for this.
Are you aware that mutable statics are unsound? In general, it is a big mistake to use unsafe code just to work around compiler errors. If you find yourself doing this, and you don't know how to do it safely, it is important to ask for help.
I think that I could move some functions outside of the impl to just export the values that are an issue in a long chain of logic.
So I would have the &mut self on the entry point of the code. Then, I would pass the data accordingly to my free standing functions. No more issue with the parallel borrow of mutable and immutable self.
This is the best I can do to avoid this.
But I need a handle that could be mutated inside. The handle itself will not change over the life of the program. Only its interior fields.
How can I achieve that ? I saw OnceLock as a way to have my handle live past my function.
I am open for a design change but I need a clue.
I could lead some stress tests and it works but if it's too borderline or subject to fail with a compiler version update, I am all ears.
When you need something that can be mutated without &mut access (that is interior mutable), use Mutex, RwLock, Atomic*, etc. All of these things are designed to be safe, with appropriate synchronization and API design.
UnsafeCell is the primitive that those things are built on top of. You should not use it yourself unless you have to because they don't cover the pattern you need. And even then, you should write a module that wraps the UnsafeCell in a safe type that does the things you want, without coupling it with the rest of your application any more than necessary — that way, it's easier to think about the soundness of the unsafe code, and easier to test it. It's really, really easy to write incorrect unsafe code, so you should always keep it small and isolated, with a well-defined safe API surface.
I assume you're referring to my comment about changing the pointer safely when it is accessed concurrently. Since I did suggest an atomic for the other issue I commented on.
When you need shared mutable state (this section of the book is worth reading), one solution is to wrap it in a Mutex or RwLock, not just an UnsafeCell. In your case a RwLock is more appropriate because the pointer changes very infrequently.
pub records: RwLock<Option<*mut Records>>,
Wrapping the entire handle in a RwLock (as you said above) is even better, since then you don't need to use an Atomic.
Ok, I spent 5 hours+ to modulate the design and now, no more unsafecells, only unsafe blocks around the pointer I get from the OS.
But that's fine, the pointer is guaranteed at the OS level (permissions).
We can consider this a job done.
Thanks for your advices, really really appreciated.