{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreih7mrmwl6mjs7cftml64b4jiqb33zrzg3gyiucowv3fiodoxta77e",
    "uri": "at://did:plc:ivbknywyskln22er3nkssdhl/app.bsky.feed.post/3mlhd6ys4pcd2"
  },
  "path": "/t/include-racy-reads-in-rust-memory-model-with-maybeinvalid-t/24289#post_1",
  "publishedAt": "2026-05-09T21:25:22.000Z",
  "site": "https://internals.rust-lang.org",
  "tags": [
    "previous post",
    "deliberate UB",
    "RFC"
  ],
  "textContent": "_Disclaimer: This post has nothing to do with previous post mentioning`MaybeInvalid`_.\n\nUnsafe code guidelines contains a deliberate UB section mentioning the SeqLock issue, i.e. SeqLock algorithm isn’t compatible with Rust memory model. In fact, SeqLock relies on a racy read, which is known to be valid only after a subsequent check.\n\nThe document mentions two possible solutions:\n\n  * (a) adopt LLVM's handling of memory races (then the problematic read would merely return undef instead of UB due to a data race)\n  * (b) add bytewise atomic memcpy and using that instead of the non-atomic volatile load.\n\n\n\nThere is currently a RFC opened about solution (b). I would like to explore a path closer to solution (a).\n\nIt would be materialized by the following types/functions:\n\n\n    // in core::mem\n    #[lang = \"maybe_invalid\"]\n    #[derive(Copy)]\n    #[repr(transparent)]\n    pub union MaybeInvalid<T> {\n        invalid: (),\n        value: ManuallyDrop<T>,\n    }\n\n    impl<T> MaybeInvalid<T> {\n        pub fn assume_valid(self) -> T { /* .. */ }\n    }\n\n    // in core::ptr\n    pub unsafe fn read_maybe_invalid<T>(ptr: *const T) -> MaybeInvalid<T> { /* .. */ }\n\n\nConcretely, it would mean to defer the UB of a racy read to `MaybeInvalid::assume_valid` call. SeqLock implementation would then become:\n\n\n    pub struct SeqLock<T> {\n        seq: AtomicUsize,\n        data: UnsafeCell<T>,\n    }\n\n    unsafe impl<T: Copy + Send> Sync for SeqLock<T> {}\n\n    impl<T> SeqLock<T> {\n        /// Safety: Only call from one thread.\n        pub unsafe fn write(&self, value: T) {\n            self.seq.fetch_add(1, Relaxed);\n            fence(Release);\n            unsafe { ptr::write(self.data.get(), value) }\n            self.seq.fetch_add(1, Release);\n        }\n\n        pub fn read(&self) -> T {\n            loop {\n                let s1 = self.seq.load(Acquire);\n                let data = unsafe { ptr::read_maybe_invalid(self.data.get()) };\n                fence(Acquire);\n                let s2 = self.seq.load(Relaxed);\n                if s1 & 1 == 0 && s1 == s2 {\n                    return unsafe { data.assume_valid() };\n                }\n            }\n        }\n    }\n\n\nFor SeqLock, `MaybeInvalid::assume_valid` would be called after ensuring there was no data race. Compared to RFC 3301, I see the following advantages:\n\n  * The API is simpler: one type, one method, one function.\n  * It doesn't reuse `MaybeUninit`, as initialization is not the issue here, avoiding confusion.\n  * Writes remain non-racing plain writes, so `assume_valid` cannot return torn values. This is in my opinion the biggest advantage, as it removes an entire class of problems (for example the `Drop` issue of RFC 3301).\n  * The semantic is closer of what SeqLock algorithm is built on: a read which may be invalid, and is assumed valid after an atomic check.\n  * This model is de facto already supported by LLVM, so there would be no change on this side.\n\n\n\nOf course, the drawbacks are significant:\n\n  * It requires to modify the Rust memory model, which is, I assume, quite a blocker by itself.\n  * Modifying Rust memory model might impact interoperability with C/C++, as Rust memory model is inherited from C++.\n  * (I've realized it while writing SeqLock implementation) racy reads, but also plain writes should interact with fences as atomics do; the consequences of including plain writes here may be bigger than I would have expected.\n\n\n\nI'm not an expert in the domain, so I may be completely off the mark here. Moreover, the fence issue made me reconsider my will of having `MaybeInvalid` compared to RFC 3301. But this post has the merit of discussion option (a) of unsafe code guidelines.",
  "title": "Include racy reads in Rust memory model with `MaybeInvalid<T>`"
}