stele/append/
writer.rs

1use core::marker::PhantomData;
2
3use crate::{sync::Arc, ReadHandle, Stele};
4
5/// The writer for a [`Stele`]
6///
7/// This is the only type capable of writing to the underlying [`Stele`] and so some limitaions are in place:
8///
9/// - ## Why is [`WriteHandle`] Send but !Sync?
10///
11/// This must be `!Sync` because while you can safely reserve a slot to avoid write-write conflicts
12/// in any one memory location using [`fetch_add`](core::sync::atomic::AtomicUsize::fetch_add),
13/// there can still be a race where a concurrent push while a previous push is still allocating can
14/// segfault as readers can see the new length before memory is written.
15///
16/// - ## Why can I only append?
17///
18/// Append-only concurrent data structures avoid a major problem in the concurrent data structure space:
19/// Memory Reclamation. By only allowing appending elements and not mutation or removal, there cannot be a read-write data race,
20/// and all data is reclaimed if and only if there are no more handles left,
21/// at which point there cannot be any way to access the data inside and therefore we leave no dangling references.
22#[derive(Debug)]
23pub struct WriteHandle<T> {
24    pub(crate) handle: Arc<Stele<T>>,
25    pub(crate) _unsync: PhantomData<*mut T>,
26}
27
28//SAFETY: WriteHandle only provides immutable references to its contents and uses atomic operations internally
29//so as long as the type of its items are both Send and Sync it is safe to implement Send
30unsafe impl<T> Send for WriteHandle<T> where T: Send + Sync {}
31
32impl<T> WriteHandle<T> {
33    /// Pushes a new item on to the end of the [`Stele`], allocating a new block of memory if necessary
34    pub fn push(&self, val: T) {
35        //SAFETY: WriteHandle is neither Sync nor Clone so only one exists at a time
36        //and can only be used by one thread at a time
37        unsafe {
38            self.handle.push(val);
39        };
40    }
41
42    /// Creates a new [`ReadHandle`]
43    #[must_use]
44    pub fn new_read_handle(&self) -> ReadHandle<T> {
45        ReadHandle::from(&self.handle)
46    }
47
48    /// Reads the value at the given index
49    ///
50    /// # Panic
51    ///
52    /// This function panics in debug if the given index is out of bounds.
53    #[must_use]
54    pub fn read(&self, idx: usize) -> &T {
55        self.handle.read(idx)
56    }
57
58    /// Attempts to read the value at the index and returns [`Some`] if the value exists, and [`None`] otherwise
59    #[must_use]
60    pub fn try_read(&self, idx: usize) -> Option<&T> {
61        self.handle.try_read(idx)
62    }
63
64    /// Returns the current length of the underlying [`Stele`]
65    ///
66    /// Note:
67    /// By calling this through the [`WriteHandle`], you hold the only handle that can change the
68    /// length and therefore this information is accurate until the next call to [`push`](WriteHandle::push)
69    #[must_use]
70    pub fn len(&self) -> usize {
71        self.handle.len()
72    }
73
74    /// Returns whether the underlying [`Stele`] is empty or not
75    ///
76    /// Note:
77    /// By calling this through the [`WriteHandle`], you hold the only handle that can change the
78    /// length and therefore this information is accurate until the first call to [`push`](WriteHandle::push) if it
79    /// returned `true`, and will remain accurate again after that as a [`Stele`] cannot remove elements
80    #[must_use]
81    pub fn is_empty(&self) -> bool {
82        self.handle.is_empty()
83    }
84}
85
86impl<T: Copy> WriteHandle<T> {
87    /// Get provides a way to get an owned copy of a value inside a [`Stele`]
88    /// provided the type `T` implements [`Copy`]
89    ///
90    /// # Panic
91    ///
92    /// This function panics in debug if the given index is out of bounds
93    #[must_use]
94    pub fn get(&self, idx: usize) -> T {
95        self.handle.get(idx)
96    }
97}