slot_cell/
lib.rs

1#![cfg_attr(not(test), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4
5use core::cell::Cell;
6use core::cell::UnsafeCell;
7use core::fmt::Debug;
8use core::mem::MaybeUninit;
9use core::panic::Location;
10use core::ptr;
11
12macro_rules! slot_panic {
13    ($self:ident, $msg:expr) => {
14        #[cfg(not(debug_assertions))]
15        {
16            panic!($msg);
17        }
18        #[cfg(debug_assertions)]
19        {
20            let loc = $self.last_modified.get();
21            panic!(
22                "{}\nLast modified at {}:{}:{}",
23                $msg,
24                loc.file(),
25                loc.line(),
26                loc.column()
27            );
28        }
29    };
30}
31
32#[allow(dead_code)]
33fn assert_send<T: Send>() {}
34#[allow(dead_code)]
35fn assert_sync<T: Sync>() {}
36#[allow(dead_code)]
37fn check_traits() {
38    assert_send::<SlotCell<i32>>();
39    // assert_sync::<SlotCell<i32>>(); // Will not compile
40}
41
42/// A cell type that enforces borrowing semantics (take/put) for interior mutability.
43///
44/// `SlotCell<T>` wraps a value that can be temporarily "taken out" and later "put back".
45/// This is useful for scenarios where you need to move a value out of a structure temporarily,
46/// perform operations on it, and then return it. In practice `SlotCell` fills the same role as
47/// `RefCell` and acts more like a "lockless mutex", while:
48/// - Since backed by a simple [`Cell`]
49///     - Potentially more memory efficient depending on alignment
50///     - Faster for small stack values (1 register, <= 8 bytes)
51///     - Comparable for medium stack values (2 - 3 registers, <= 24 bytes)
52///     - Slower for large stack values (Consider `RefCell` or
53///     moving data to heap if performance is the main concern)
54/// - **allowing owned access.**
55///
56/// Unlike `Cell<T>` or `Cell<Option<T>>`:
57/// - `T` does not need to implement `Copy`/`Clone`/`Default`, or a separate `T` needed, to take
58/// the value out.
59/// - Implements `Debug`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, and `Default` if `T` does.
60/// - Does not implement `Clone` or `Copy`, or require `T` to be `Clone` or `Copy` for certain operations.
61/// - Enforces a correct usage patterns that mimics "borrow semantics" with a runtime check.
62///
63/// Unlike `RefCell<T>`:
64/// - No borrow counting is used
65/// - Owned values rather references are returned
66/// - No multiple read references
67///
68/// High level
69/// - Owned values are used over guards with references/lifetimes. Thus,
70/// it is up to the programmer to follow semantics around taking and returning values
71/// or it will panic otherwise.
72/// - A value can only be taken once (until put back)
73/// - A value can only be put back when the slot is empty
74/// - A value can only be replaced when not already empty
75///
76/// In debug builds, `SlotCell` tracks the location of the last modification, providing
77/// helpful panic messages when usage rules are violated.
78///
79/// # Examples
80///
81/// ```
82/// # use slot_cell::SlotCell;
83///
84/// let cell = SlotCell::new(42);
85/// let value = cell.take();
86/// assert_eq!(value, 42);
87///
88/// // Put the value back
89/// cell.put(100);
90///
91/// // Take it again
92/// let value = cell.take();
93/// assert_eq!(value, 100);
94/// ```
95///
96/// # Panics
97///
98/// This type will panic if usage rules are violated (taking when empty, putting when full, etc.).
99/// In debug builds, panic messages include the location of the last modification.
100pub struct SlotCell<T> {
101    is_empty: Cell<bool>,
102    cell: UnsafeCell<MaybeUninit<T>>,
103    #[cfg(debug_assertions)]
104    last_modified: Cell<Location<'static>>,
105}
106
107impl<T> SlotCell<T> {
108    /// Creates a new `SlotCell` containing the given value.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// # use slot_cell::SlotCell;
114    ///
115    /// let cell = SlotCell::new(42);
116    /// assert!(!cell.is_empty());
117    /// ```
118    #[inline]
119    #[cfg_attr(debug_assertions, track_caller)]
120    pub fn new(val: T) -> Self {
121        Self {
122            is_empty: Cell::new(false),
123            cell: UnsafeCell::new(MaybeUninit::new(val)),
124            #[cfg(debug_assertions)]
125            last_modified: Cell::new(Location::caller().clone()),
126        }
127    }
128
129    /// Creates a new `SlotCell` that starts empty (without a value).
130    ///
131    /// This is useful for late initialization patterns where the value
132    /// will be provided later via `put()`.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// # use slot_cell::SlotCell;
138    ///
139    /// let cell: SlotCell<i32> = SlotCell::empty();
140    /// assert!(cell.is_empty());
141    ///
142    /// cell.put(42);
143    /// assert!(!cell.is_empty());
144    /// ```
145    #[inline]
146    #[cfg_attr(debug_assertions, track_caller)]
147    pub fn empty() -> Self {
148        Self {
149            is_empty: Cell::new(true),
150            cell: UnsafeCell::new(MaybeUninit::uninit()),
151            #[cfg(debug_assertions)]
152            last_modified: Cell::new(Location::caller().clone()),
153        }
154    }
155
156    /// Takes the value out of the cell, leaving it empty.
157    ///
158    /// After calling this method, the cell will be empty until `put()` is called.
159    ///
160    /// # Panics
161    ///
162    /// Panics if the slot is already empty. In debug builds,
163    /// the panic message includes the location of the last modification.
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// # use slot_cell::SlotCell;
169    ///
170    /// let cell = SlotCell::new(42);
171    /// let value = cell.take();
172    /// assert_eq!(value, 42);
173    /// ```
174    #[must_use]
175    #[inline]
176    #[cfg_attr(debug_assertions, track_caller)]
177    pub fn take(&self) -> T {
178        if self.is_empty.get() {
179            slot_panic!(
180                self,
181                "Attempted to `take` a value when the slot is already empty."
182            );
183        }
184        let val = self.take_unchecked();
185        #[cfg(debug_assertions)]
186        self.last_modified.set(Location::caller().clone());
187        val
188    }
189
190    #[inline(always)]
191    #[cfg_attr(debug_assertions, track_caller)]
192    fn take_unchecked(&self) -> T {
193        debug_assert!(!self.is_empty.get());
194        self.is_empty.set(true);
195        unsafe { ptr::read(self.cell.get()).assume_init() }
196    }
197
198    /// Checks whether the slot is currently empty.
199    ///
200    /// Returns `true` if the cell currently empty, `false` if it's filled.
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// # use slot_cell::SlotCell;
206    ///
207    /// let cell = SlotCell::new(42);
208    /// assert!(!cell.is_empty());  // Has value, not taken
209    ///
210    /// let _value = cell.take();
211    /// assert!(cell.is_empty());   // Now empty/taken
212    /// ```
213    #[inline]
214    pub fn is_empty(&self) -> bool {
215        self.is_empty.get()
216    }
217
218    /// Checks whether the slot is currently empty.
219    ///
220    /// Returns `true` if the cell currently contains a value, `false` if it's empty.
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// # use slot_cell::SlotCell;
226    ///
227    /// let cell = SlotCell::new(42);
228    /// assert!(cell.is_filled());  // Has value, not taken
229    ///
230    /// let _value = cell.take();
231    /// assert!(!cell.is_filled());   // Now empty/taken
232    /// ```
233    #[inline]
234    pub fn is_filled(&self) -> bool {
235        !self.is_empty.get()
236    }
237
238    /// Puts a value into the cell, filling an empty slot.
239    ///
240    /// This is used to return a value that was previously removed via `take()`,
241    /// or to initialize a cell created with `empty()`.
242    ///
243    /// # Panics
244    ///
245    /// Panics if the slot is already filled. `SlotCell` enforces that a value
246    /// must be taken before a new one can be put back. In debug builds,
247    /// the panic message includes the location of the last modification.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// # use slot_cell::SlotCell;
253    ///
254    /// let cell = SlotCell::empty();
255    /// cell.put(42); // Initialize empty slot
256    ///
257    /// let _ = cell.take();
258    /// cell.put(100); // Put back after taking
259    /// ```
260    #[inline]
261    #[cfg_attr(debug_assertions, track_caller)]
262    pub fn put(&self, val: T) {
263        if !self.is_empty.get() {
264            slot_panic!(
265                self,
266                "Attempted to `put` a value when the slot is already filled."
267            );
268        }
269        self.put_unchecked(val);
270        #[cfg(debug_assertions)]
271        self.last_modified.set(Location::caller().clone());
272    }
273
274    #[inline(always)]
275    #[cfg_attr(debug_assertions, track_caller)]
276    fn put_unchecked(&self, val: T) {
277        debug_assert!(self.is_empty.get());
278        unsafe {
279            ptr::write(self.cell.get(), MaybeUninit::new(val));
280        }
281        self.is_empty.set(false);
282    }
283
284    /// Replaces the current value in the cell with a new one.
285    ///
286    /// Unlike `put()`, this method requires the cell to currently contain a value.
287    /// It is used to update the contents without changing the "filled" state
288    /// of the slot.
289    ///
290    /// # Panics
291    ///
292    /// Panics if the slot is already empty. To fill an empty cell,
293    /// use `put()` instead. In debug builds, the panic message includes the
294    /// location of the last modification.
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// # use slot_cell::SlotCell;
300    ///
301    /// let cell = SlotCell::new(42);
302    /// cell.replace(100);
303    ///
304    /// assert_eq!(cell.take(), 100);
305    /// ```
306    #[inline]
307    #[cfg_attr(debug_assertions, track_caller)]
308    pub fn replace(&self, val: T) -> T {
309        if self.is_empty.get() {
310            slot_panic!(
311                self,
312                "Attempted to `replace` a value when the slot is already empty."
313            );
314        }
315        let val = unsafe { ptr::replace(self.cell.get(), MaybeUninit::new(val)).assume_init() };
316        #[cfg(debug_assertions)]
317        self.last_modified.set(Location::caller().clone());
318        val
319    }
320
321    /// Swaps the values between two full `SlotCell`s.
322    ///
323    /// This efficiently exchanges the contents of `self` and `other` without
324    /// requiring an intermediate `take()` or `put()` call.
325    ///
326    /// # Panics
327    ///
328    /// Panics if either `self` or `other` is currently empty. In debug
329    /// builds, the panic message includes the location of the last modification
330    /// for the empty cell.
331    ///
332    /// # Examples
333    ///
334    /// ```
335    /// # use slot_cell::SlotCell;
336    ///
337    /// let cell_a = SlotCell::new(1);
338    /// let cell_b = SlotCell::new(2);
339    ///
340    /// cell_a.swap(&cell_b);
341    ///
342    /// assert_eq!(cell_a.take(), 2);
343    /// assert_eq!(cell_b.take(), 1);
344    /// ```
345    #[inline]
346    #[cfg_attr(debug_assertions, track_caller)]
347    pub fn swap(&self, other: &Self) {
348        if ptr::eq(self, other) {
349            return;
350        }
351        if self.is_empty.get() {
352            slot_panic!(
353                self,
354                "Attempted to `swap` a value when this slot is already empty."
355            );
356        }
357        if other.is_empty.get() {
358            slot_panic!(
359                self,
360                "Attempted to `swap` a value when the other slot is already empty."
361            );
362        }
363        unsafe {
364            ptr::swap(self.cell.get(), other.cell.get());
365        }
366        #[cfg(debug_assertions)]
367        {
368            let location = Location::caller().clone();
369            self.last_modified.set(location.clone());
370            other.last_modified.set(location);
371        }
372    }
373
374    /// Executes a closure with a mutable reference to the value inside the cell.
375    ///
376    /// This method provides a way to modify the contents of the `SlotCell` or perform
377    /// operations on it without needing to manually call `take()` and `put()`.
378    ///
379    /// Because `SlotCell` works by moving values, this method internally takes the
380    /// value out of the cell, passes it to your closure, and automatically puts
381    /// it back once the closure returns.
382    ///
383    /// # Panics
384    ///
385    /// Panics if the cell is currently empty or if already filled when attempting to return
386    /// the value. In debug builds, the panic message includes the location of the last modification.
387    ///
388    /// # Examples
389    ///
390    /// ```
391    /// # use slot_cell::SlotCell;
392    ///
393    /// let cell = SlotCell::new(String::from("Hello"));
394    ///
395    /// cell.with(|s| {
396    ///     s.push_str(", World!");
397    /// });
398    ///
399    /// assert_eq!(cell.into_inner(), "Hello, World!");
400    /// ```
401    ///
402    /// It can also return a value from the closure:
403    ///
404    /// ```
405    /// # use slot_cell::SlotCell;
406    /// let cell = SlotCell::new(10);
407    /// let is_even = cell.with(|x| *x % 2 == 0);
408    /// assert!(is_even);
409    /// ```
410    #[inline]
411    #[cfg_attr(debug_assertions, track_caller)]
412    pub fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
413        let mut v = self.take();
414        let r = f(&mut v);
415        self.put(v);
416        r
417    }
418
419    /// Updates the value inside the cell by applying a transformation function.
420    ///
421    /// This method takes ownership of the value, passes it to the closure, and
422    /// puts the result back into the cell.
423    ///
424    /// Unlike [`with`](Self::with), which provides a mutable reference, `update`
425    /// allows you to consume the value and return a new one of the same type.
426    ///
427    /// # Panics
428    ///
429    /// Panics if the cell is currently empty or if already filled when attempting to return
430    /// the value. In debug builds, the panic message includes the location of the last modification.
431    ///
432    /// # Examples
433    ///
434    /// ```
435    /// # use slot_cell::SlotCell;
436    ///
437    /// let cell = SlotCell::new(5);
438    ///
439    /// // Multiply the value by 2
440    /// cell.update(|v| v * 2);
441    ///
442    /// assert_eq!(cell.take(), 10);
443    /// ```
444    ///
445    /// It can also be used to swap out owned data like a `String` or `Vec`:
446    ///
447    /// ```
448    /// # use slot_cell::SlotCell;
449    /// let cell = SlotCell::new(vec![1, 2, 3]);
450    ///
451    /// cell.update(|mut v| {
452    ///     v.push(4);
453    ///     v
454    /// });
455    ///
456    /// assert_eq!(cell.into_inner().len(), 4);
457    /// ```
458    #[inline]
459    #[cfg_attr(debug_assertions, track_caller)]
460    pub fn update(&self, f: impl FnOnce(T) -> T) {
461        let v = self.take();
462        let r = f(v);
463        self.put(r);
464    }
465
466    /// Unwraps the value, consuming the cell.
467    ///
468    /// # Panics
469    ///
470    /// Panics if `self` is empty. In debug
471    /// builds, the panic message includes the location of the last modification
472    /// for the empty cell.
473    ///
474    /// # Examples
475    ///
476    /// ```
477    /// # use slot_cell::SlotCell;
478    ///
479    /// let c = SlotCell::new(5);
480    /// let five = c.into_inner();
481    ///
482    /// assert_eq!(five, 5);
483    /// ```
484    #[inline]
485    #[cfg_attr(debug_assertions, track_caller)]
486    pub fn into_inner(self) -> T {
487        self.take()
488    }
489}
490
491impl<T> Drop for SlotCell<T> {
492    #[inline]
493    fn drop(&mut self) {
494        if self.is_empty.get() {
495            return;
496        }
497        self.is_empty.set(true);
498        unsafe {
499            (*self.cell.get()).assume_init_drop();
500        }
501    }
502}
503
504impl<T> Debug for SlotCell<T>
505where
506    T: Debug,
507{
508    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
509        let mut binding = f.debug_struct("SlotCell");
510        let r = if self.is_empty.get() {
511            binding.field("cell", &"EMPTY")
512        } else {
513            let val = self.take();
514            let r = binding.field("cell", &val);
515            self.put(val);
516            r
517        };
518        #[cfg(debug_assertions)]
519        let r = r.field("last_modified", &self.last_modified);
520        r.finish()
521    }
522}
523
524impl<T> Eq for SlotCell<T> where T: Eq {}
525
526impl<T> PartialEq for SlotCell<T>
527where
528    T: PartialEq,
529{
530    fn eq(&self, other: &Self) -> bool {
531        let self_is_empty = self.is_empty.get();
532        if core::ptr::eq(self, other) {
533            // Some types may not always be equal to themselves so we must check instead of just
534            // returning `true` (e.g. NaN != NaN)
535            if self_is_empty {
536                return true;
537            }
538            let val = self.take();
539            let eq = val == val;
540            self.put(val);
541            return eq;
542        }
543        let other_is_empty = other.is_empty.get();
544        if self_is_empty && other_is_empty {
545            return true;
546        }
547        if self_is_empty || other_is_empty {
548            return false;
549        }
550        let val_a = self.take();
551        let val_b = other.take();
552        let eq = val_a == val_b;
553        self.put(val_a);
554        other.put(val_b);
555        eq
556    }
557}
558
559impl<T> Ord for SlotCell<T>
560where
561    T: Ord,
562{
563    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
564        let self_is_empty = self.is_empty.get();
565        if core::ptr::eq(self, other) {
566            // We do not need to check the inner, since unlike `PartialOrd`, `Ord` implies Total Order.
567            // One of the mathematical requirements for Total Order is **Reflexivity**.
568            return core::cmp::Ordering::Equal;
569        }
570        let other_is_empty = other.is_empty.get();
571        if self_is_empty && other_is_empty {
572            return core::cmp::Ordering::Equal;
573        }
574        if self_is_empty {
575            return core::cmp::Ordering::Less;
576        }
577        if other_is_empty {
578            return core::cmp::Ordering::Greater;
579        }
580        let val_a = self.take();
581        let val_b = other.take();
582        let ord = val_a.cmp(&val_b);
583        self.put(val_a);
584        other.put(val_b);
585        ord
586    }
587}
588
589impl<T> PartialOrd for SlotCell<T>
590where
591    T: PartialOrd,
592{
593    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
594        let self_is_empty = self.is_empty.get();
595        if core::ptr::eq(self, other) {
596            // Some types may not always be ordered to themselves so we must check instead of just
597            // returning `Equal`
598            if self_is_empty {
599                return Some(core::cmp::Ordering::Equal);
600            }
601            let val = self.take();
602            let ord = val.partial_cmp(&val);
603            self.put(val);
604            return ord;
605        }
606        let other_is_empty = other.is_empty.get();
607        if self_is_empty && other_is_empty {
608            return Some(core::cmp::Ordering::Equal);
609        }
610        if self_is_empty {
611            return Some(core::cmp::Ordering::Less);
612        }
613        if other_is_empty {
614            return Some(core::cmp::Ordering::Greater);
615        }
616        let val_a = self.take();
617        let val_b = other.take();
618        let ord = val_a.partial_cmp(&val_b);
619        self.put(val_a);
620        other.put(val_b);
621        ord
622    }
623}
624
625impl<T> core::hash::Hash for SlotCell<T>
626where
627    T: core::hash::Hash,
628{
629    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
630        if self.is_empty.get() {
631            0usize.hash(state);
632        } else {
633            let val = self.take();
634            val.hash(state);
635            self.put(val);
636        }
637    }
638}
639
640impl<T> Default for SlotCell<T> {
641    #[inline]
642    #[cfg_attr(debug_assertions, track_caller)]
643    fn default() -> Self {
644        Self {
645            is_empty: Cell::new(true),
646            cell: UnsafeCell::new(MaybeUninit::uninit()),
647            #[cfg(debug_assertions)]
648            last_modified: Cell::new(Location::caller().clone()),
649        }
650    }
651}
652
653impl<T> From<T> for SlotCell<T> {
654    #[inline]
655    #[cfg_attr(debug_assertions, track_caller)]
656    fn from(value: T) -> Self {
657        Self::new(value)
658    }
659}
660
661impl<T> From<Option<T>> for SlotCell<T> {
662    #[cfg_attr(debug_assertions, track_caller)]
663    fn from(value: Option<T>) -> Self {
664        let (is_empty, cell) = match value {
665            Some(value) => (false, MaybeUninit::new(value)),
666            None => (true, MaybeUninit::uninit()),
667        };
668        Self {
669            is_empty: Cell::new(is_empty),
670            cell: UnsafeCell::new(cell),
671            #[cfg(debug_assertions)]
672            last_modified: Cell::new(Location::caller().clone()),
673        }
674    }
675}
676
677impl<T> From<SlotCell<T>> for Option<T> {
678    fn from(value: SlotCell<T>) -> Self {
679        if value.is_empty() {
680            None
681        } else {
682            Some(value.into_inner())
683        }
684    }
685}
686
687// /// We only do this drop check in debug mode since not checking will **not** cause `UB`.
688// /// But semantically a user should always return the value when taken before the cell is dropped.
689// /// If this panic is hit, it will likely inform the user of a logical inconsistency bug.
690// /// Failing early to more easily detect the bug.
691// #[cfg(debug_assertions)]
692// impl<T> Drop for SlotCell<T> {
693//     fn drop(&mut self) {
694//         if self.is_empty() {
695//             panic!("SlotCell was dropped while still taken. Value was never put back.\n{}", self.last_modified_msg());
696//         }
697//     }
698// }
699
700#[cfg(test)]
701mod tests {
702    use core::cmp::Ordering;
703    use std::rc::Rc;
704
705    use super::*;
706
707    #[test]
708    fn test_new_and_take() {
709        let cell = SlotCell::new(10);
710        assert!(!cell.is_empty());
711        assert_eq!(cell.take(), 10);
712        assert!(cell.is_empty());
713    }
714
715    #[test]
716    fn test_empty_and_put() {
717        let cell: SlotCell<i32> = SlotCell::empty();
718        assert!(cell.is_empty());
719        cell.put(20);
720        assert!(!cell.is_empty());
721        assert_eq!(cell.take(), 20);
722    }
723
724    #[test]
725    fn test_replace() {
726        let cell = SlotCell::new(1);
727        let old = cell.replace(2);
728        assert_eq!(old, 1);
729        assert_eq!(cell.take(), 2);
730    }
731
732    #[test]
733    fn test_swap() {
734        let a = SlotCell::new(1);
735        let b = SlotCell::new(2);
736        a.swap(&b);
737        assert_eq!(a.take(), 2);
738        assert_eq!(b.take(), 1);
739    }
740
741    #[test]
742    fn test_into_inner() {
743        let cell = SlotCell::new(String::from("hello"));
744        let s = cell.into_inner();
745        assert_eq!(s, "hello");
746    }
747
748    // --- Panic Tests ---
749
750    #[test]
751    #[should_panic]
752    fn test_panic_take_empty() {
753        let cell = SlotCell::new(5);
754        let _ = cell.take();
755        let _ = cell.take(); // Should panic
756    }
757
758    #[test]
759    #[should_panic]
760    fn test_panic_put_full() {
761        let cell = SlotCell::new(5);
762        cell.put(10);
763    }
764
765    #[test]
766    #[should_panic]
767    fn test_panic_replace_empty() {
768        let cell: SlotCell<i32> = SlotCell::empty();
769        cell.replace(10);
770    }
771
772    // --- PartialEq & Eq Tests ---
773
774    #[test]
775    fn test_partial_eq_basics() {
776        let cell_a = SlotCell::new(10);
777        let cell_b = SlotCell::new(10);
778        let cell_c = SlotCell::new(20);
779        let empty_a: SlotCell<i32> = SlotCell::empty();
780        let empty_b: SlotCell<i32> = SlotCell::empty();
781
782        assert_eq!(cell_a, cell_b); // Occupied equal
783        assert_ne!(cell_a, cell_c); // Occupied unequal
784        assert_eq!(empty_a, empty_b); // Empty equal
785        assert_ne!(cell_a, empty_a); // Occupied vs Empty
786    }
787
788    #[test]
789    fn test_partial_eq_nan_identity() {
790        let nan = f32::NAN;
791        let cell = SlotCell::new(nan);
792
793        // Standard Rust behavior: NaN != NaN
794        assert!(
795            cell != cell,
796            "SlotCell with NaN should not equal itself via pointer identity"
797        );
798
799        let empty: SlotCell<f32> = SlotCell::empty();
800        assert_eq!(empty, empty, "Empty cells should always equal themselves");
801    }
802
803    #[test]
804    fn test_partial_eq_no_panic_or_ub_on_cycles() {
805        struct Link(Rc<SlotCell<Link>>);
806
807        impl PartialEq for Link {
808            fn eq(&self, _other: &Self) -> bool {
809                *self.0 == *self.0
810            }
811        }
812
813        let cell_a = Rc::new(SlotCell::empty());
814        let cell_b = Rc::new(SlotCell::empty());
815
816        cell_a.put(Link(Rc::clone(&cell_a)));
817        cell_b.put(Link(Rc::clone(&cell_a)));
818
819        let _ = *cell_a == *cell_b;
820    }
821
822    // --- PartialOrd & Ord Tests ---
823
824    #[test]
825    fn test_ord_total_order() {
826        let small = SlotCell::new(10);
827        let large = SlotCell::new(20);
828        let empty = SlotCell::empty();
829
830        assert_eq!(small.cmp(&large), Ordering::Less);
831        assert_eq!(large.cmp(&small), Ordering::Greater);
832        assert_eq!(small.cmp(&small), Ordering::Equal);
833
834        assert_eq!(empty.cmp(&small), Ordering::Less);
835        assert_eq!(small.cmp(&empty), Ordering::Greater);
836        assert_eq!(empty.cmp(&empty), Ordering::Equal);
837    }
838
839    #[test]
840    fn test_partial_ord_nan() {
841        let nan_cell = SlotCell::new(f32::NAN);
842        let val_cell = SlotCell::new(1.0f32);
843
844        assert_eq!(nan_cell.partial_cmp(&val_cell), None);
845
846        assert_eq!(
847            nan_cell.partial_cmp(&nan_cell),
848            None,
849            "NaN cell identity should be None"
850        );
851    }
852
853    #[test]
854    fn test_ptr_identity_optimization() {
855        let cell = SlotCell::new(5);
856        assert_eq!(cell.cmp(&cell), Ordering::Equal);
857    }
858
859    // --- State Persistence Tests ---
860
861    #[test]
862    fn test_state_integrity_after_compare() {
863        let cell_a = SlotCell::new(100);
864        let cell_b = SlotCell::new(100);
865
866        let _ = cell_a == cell_b;
867        let _ = cell_a.cmp(&cell_b);
868
869        assert!(
870            !cell_a.is_empty.get(),
871            "Cell should be occupied after comparison"
872        );
873        assert!(
874            !cell_b.is_empty.get(),
875            "Cell should be occupied after comparison"
876        );
877
878        let val = cell_a.take_unchecked();
879        assert_eq!(val, 100);
880    }
881
882    // ---Other ---
883
884    #[test]
885    fn test_debug_format() {
886        let cell = SlotCell::new(42);
887        let debug_str = format!("{:?}", cell);
888        assert!(debug_str.contains("SlotCell"));
889        assert!(debug_str.contains("42"));
890    }
891
892    #[test]
893    fn test_slotcell_vs_refcell_size() {
894        use core::cell::Cell;
895        use core::mem::size_of;
896
897        type T = u32;
898
899        let slotcell_size = size_of::<SlotCell<T>>();
900        let cell_option_size = size_of::<Cell<Option<T>>>();
901
902        #[cfg(debug_assertions)]
903        {
904            let location_cell_size = size_of::<Cell<Location<'static>>>();
905
906            assert_eq!(
907                slotcell_size,
908                cell_option_size + location_cell_size,
909                "SlotCell<T> should be Cell<Option<T>> + debug tracking"
910            );
911        }
912
913        #[cfg(not(debug_assertions))]
914        {
915            assert_eq!(
916                slotcell_size, cell_option_size,
917                "SlotCell<T> should be exactly Cell<Option<T>> in release mode"
918            );
919
920            let refcell_size = size_of::<core::cell::RefCell<T>>();
921            assert!(
922                refcell_size > slotcell_size,
923                "RefCell<T> should be larger than SlotCell<T> in release mode. Got {} and {}",
924                refcell_size,
925                slotcell_size
926            );
927        }
928    }
929
930    #[test]
931    fn test_with_mutation() {
932        let cell = SlotCell::new(String::from("Rust"));
933
934        cell.with(|s| {
935            s.push_str(" Programming");
936        });
937
938        assert_eq!(cell.take(), "Rust Programming");
939    }
940
941    #[test]
942    fn test_with_return_value() {
943        let cell = SlotCell::new(42);
944
945        let is_even = cell.with(|v| *v % 2 == 0);
946
947        assert!(is_even);
948        assert_eq!(cell.take(), 42);
949    }
950
951    #[test]
952    fn test_update_transformation() {
953        let cell = SlotCell::new(10);
954
955        cell.update(|v| v + 5);
956
957        assert_eq!(cell.take(), 15);
958    }
959
960    #[test]
961    fn test_update_string_buffer() {
962        let cell = SlotCell::new(vec![1, 2]);
963
964        cell.update(|mut v| {
965            v.push(3);
966            v
967        });
968
969        assert_eq!(cell.take(), vec![1, 2, 3]);
970    }
971
972    #[test]
973    #[should_panic]
974    fn test_panic_with_empty() {
975        let cell: SlotCell<i32> = SlotCell::empty();
976        cell.with(|v| *v += 1);
977    }
978
979    #[test]
980    #[should_panic]
981    fn test_panic_update_empty() {
982        let cell: SlotCell<i32> = SlotCell::empty();
983        cell.update(|v| v + 1);
984    }
985
986    #[test]
987    fn test_with_panic_safety() {
988        let cell = SlotCell::new(vec![1, 2, 3]);
989
990        let result = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
991            cell.with(|_v| {
992                panic!("intentional panic");
993            });
994        }));
995
996        assert!(result.is_err());
997        assert!(cell.is_empty());
998    }
999
1000    #[test]
1001    fn test_update_panic_safety() {
1002        let cell = SlotCell::new(vec![1, 2, 3]);
1003
1004        let result = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
1005            cell.update(|_v| {
1006                panic!("intentional panic");
1007            });
1008        }));
1009
1010        assert!(result.is_err());
1011        assert!(cell.is_empty());
1012    }
1013}