Skip to main content

cljrs_value/collections/
vector.rs

1use crate::Value;
2use crate::collections::share_charge::{ShareCharge, vector_path_bytes};
3
4/// An immutable persistent vector backed by `rpds::Vector`.
5///
6/// A vector may additionally be tagged as a *map entry* (see
7/// [`PersistentVector::map_entry`]): a two-element `[key val]` pair produced
8/// by seq'ing a map, `find`, or the `map-entry` builtin. Map entries behave
9/// exactly like vectors (equality, hashing, printing, indexing) — the tag
10/// only answers `map-entry?` — and, as in Clojure, any derived vector
11/// (`conj`, `assoc`, `pop`, ...) is a plain vector again.
12///
13/// A vector derived by `conj`, `assoc_nth`, or `pop` from a GC-allocated
14/// vector reports only its path-copied trie nodes from `gc_size_extra`; see
15/// `collections::share_charge` for the full rule.
16#[derive(Debug, Clone)]
17pub struct PersistentVector {
18    inner: rpds::VectorSync<Value>,
19    is_map_entry: bool,
20    charge: ShareCharge,
21}
22
23impl PersistentVector {
24    pub fn empty() -> Self {
25        Self {
26            inner: rpds::VectorSync::new_sync(),
27            is_map_entry: false,
28            charge: ShareCharge::full(),
29        }
30    }
31
32    pub fn from_vector(vector: rpds::VectorSync<Value>) -> Self {
33        Self {
34            inner: vector,
35            is_map_entry: false,
36            charge: ShareCharge::full(),
37        }
38    }
39
40    /// Build a `[key val]` pair tagged as a map entry.
41    pub fn map_entry(key: Value, val: Value) -> Self {
42        let mut inner = rpds::VectorSync::new_sync();
43        inner = inner.push_back(key);
44        inner = inner.push_back(val);
45        Self {
46            inner,
47            is_map_entry: true,
48            charge: ShareCharge::full(),
49        }
50    }
51
52    /// True only for vectors created via [`PersistentVector::map_entry`].
53    pub fn is_map_entry(&self) -> bool {
54        self.is_map_entry
55    }
56
57    pub fn count(&self) -> usize {
58        self.inner.len()
59    }
60
61    pub fn is_empty(&self) -> bool {
62        self.inner.is_empty()
63    }
64
65    /// Append a value. O(log n) amortized.
66    pub fn conj(&self, val: Value) -> Self {
67        self.derived(self.inner.push_back(val))
68    }
69
70    /// Return the element at `idx`, or `None` if out of bounds.
71    pub fn nth(&self, idx: usize) -> Option<&Value> {
72        self.inner.get(idx)
73    }
74
75    /// Last element.
76    pub fn peek(&self) -> Option<&Value> {
77        self.inner.last()
78    }
79
80    /// Return a new vector with element `idx` replaced, or appended if `idx == len`.
81    pub fn assoc_nth(&self, idx: usize, val: Value) -> Option<Self> {
82        if idx == self.inner.len() {
83            Some(self.conj(val))
84        } else {
85            Some(self.derived(self.inner.set(idx, val)?))
86        }
87    }
88
89    /// Remove the last element. Returns `None` if empty.
90    pub fn pop(&self) -> Option<Self> {
91        Some(self.derived(self.inner.drop_last()?))
92    }
93
94    /// Wrap `inner`, produced by one path-copying update of `self.inner`.
95    fn derived(&self, inner: rpds::VectorSync<Value>) -> Self {
96        let len = inner.len().max(self.inner.len());
97        Self {
98            charge: self.charge.derive(
99                self.inner.len(),
100                inner.len(),
101                vector_path_bytes::<Value>(len),
102            ),
103            inner,
104            is_map_entry: false,
105        }
106    }
107
108    /// Estimated bytes owned by this vector if it shared no trie nodes.
109    fn full_size_extra(&self) -> usize {
110        // Per element: Arc<T> allocation (16 overhead) + thin ptr in leaf node (8).
111        self.inner.len() * (24 + std::mem::size_of::<Value>())
112    }
113
114    /// What `gc_size_extra` would report, without marking this version
115    /// allocated.
116    #[cfg(test)]
117    fn charged(&self) -> usize {
118        self.charge.size_extra(self.full_size_extra())
119    }
120
121    /// Iterate over elements in index order.
122    pub fn iter(&self) -> impl Iterator<Item = &Value> {
123        self.inner.iter()
124    }
125
126    pub fn inner(&self) -> &rpds::VectorSync<Value> {
127        &self.inner
128    }
129}
130
131impl std::iter::FromIterator<Value> for PersistentVector {
132    fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
133        let mut v = rpds::VectorSync::new_sync();
134        for item in iter {
135            v = v.push_back(item);
136        }
137        Self {
138            inner: v,
139            is_map_entry: false,
140            charge: ShareCharge::full(),
141        }
142    }
143}
144
145impl PartialEq for PersistentVector {
146    fn eq(&self, other: &Self) -> bool {
147        if self.inner.len() != other.inner.len() {
148            return false;
149        }
150        self.iter().zip(other.iter()).all(|(a, b)| a == b)
151    }
152}
153
154impl cljrs_gc::Trace for PersistentVector {
155    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
156        for v in self.inner.iter() {
157            v.trace(visitor);
158        }
159    }
160
161    fn gc_size_extra(&self) -> usize {
162        self.charge.on_alloc(self.full_size_extra())
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::Value;
170
171    fn int(n: i64) -> Value {
172        Value::Long(n)
173    }
174
175    #[test]
176    fn test_empty() {
177        let v = PersistentVector::empty();
178        assert!(v.is_empty());
179        assert_eq!(v.count(), 0);
180        assert!(v.nth(0).is_none());
181    }
182
183    #[test]
184    fn test_conj_small() {
185        let v = PersistentVector::from_iter([int(1), int(2), int(3)]);
186        assert_eq!(v.count(), 3);
187        assert_eq!(v.nth(0), Some(&int(1)));
188        assert_eq!(v.nth(2), Some(&int(3)));
189    }
190
191    #[test]
192    fn test_conj_forces_tail_flush() {
193        let v = PersistentVector::from_iter((0..33).map(int));
194        assert_eq!(v.count(), 33);
195        for i in 0..33 {
196            assert_eq!(v.nth(i), Some(&int(i as i64)), "nth({i}) wrong");
197        }
198    }
199
200    #[test]
201    fn test_large() {
202        let n = 1025;
203        let v = PersistentVector::from_iter((0..n).map(|i| int(i as i64)));
204        assert_eq!(v.count(), n);
205        for i in 0..n {
206            assert_eq!(v.nth(i), Some(&int(i as i64)));
207        }
208    }
209
210    #[test]
211    fn test_peek() {
212        let v = PersistentVector::from_iter([int(1), int(2), int(3)]);
213        assert_eq!(v.peek(), Some(&int(3)));
214    }
215
216    #[test]
217    fn test_assoc_nth() {
218        let v = PersistentVector::from_iter([int(1), int(2), int(3)]);
219        let v2 = v.assoc_nth(1, int(99)).unwrap();
220        assert_eq!(v2.nth(0), Some(&int(1)));
221        assert_eq!(v2.nth(1), Some(&int(99)));
222        assert_eq!(v2.nth(2), Some(&int(3)));
223        // Original unchanged.
224        assert_eq!(v.nth(1), Some(&int(2)));
225    }
226
227    #[test]
228    fn test_pop() {
229        let v = PersistentVector::from_iter([int(1), int(2), int(3)]);
230        let v2 = v.pop().unwrap();
231        assert_eq!(v2.count(), 2);
232        assert_eq!(v2.nth(0), Some(&int(1)));
233        assert_eq!(v2.nth(1), Some(&int(2)));
234    }
235
236    #[test]
237    fn test_equality() {
238        let a = PersistentVector::from_iter([int(1), int(2)]);
239        let b = PersistentVector::from_iter([int(1), int(2)]);
240        let c = PersistentVector::from_iter([int(1), int(3)]);
241        assert_eq!(a, b);
242        assert_ne!(a, c);
243    }
244
245    #[test]
246    fn test_map_entry_flag() {
247        let e = PersistentVector::map_entry(int(1), int(2));
248        assert!(e.is_map_entry());
249        assert_eq!(e.count(), 2);
250        assert_eq!(e.nth(0), Some(&int(1)));
251        assert_eq!(e.nth(1), Some(&int(2)));
252        // Equal to a plain vector with the same elements.
253        assert_eq!(e, PersistentVector::from_iter([int(1), int(2)]));
254        // Plain constructors never produce map entries.
255        assert!(!PersistentVector::from_iter([int(1), int(2)]).is_map_entry());
256        // Derived vectors are plain vectors again.
257        assert!(!e.conj(int(3)).is_map_entry());
258        assert!(!e.assoc_nth(0, int(9)).unwrap().is_map_entry());
259        assert!(!e.pop().unwrap().is_map_entry());
260    }
261
262    #[test]
263    fn test_iter_order() {
264        let v = PersistentVector::from_iter((0..10).map(|i| int(i as i64)));
265        let items: Vec<_> = v.iter().cloned().collect();
266        assert_eq!(items, (0..10).map(|i| int(i as i64)).collect::<Vec<_>>());
267    }
268
269    #[test]
270    fn conj_on_allocated_vector_charges_log_n() {
271        let _alloc = crate::collections::share_charge::alloc_scope();
272        use cljrs_gc::GcPtr;
273
274        let path = vector_path_bytes::<Value>;
275        let mut charges = Vec::new();
276        for n in [1_000usize, 100_000] {
277            let base = PersistentVector::from_iter((0..n as i64).map(int));
278            let full = base.full_size_extra();
279            let mut cur = GcPtr::new(base);
280            assert_eq!(cur.get().charged(), full);
281            // Stay below 2n so no step re-charges the whole vector.
282            for i in 0..500 {
283                let next = GcPtr::new(cur.get().conj(int(i)));
284                let charge = next.get().charged();
285                assert_eq!(charge, path(n + i as usize + 1));
286                assert!(charge * 50 < full, "n={n}: {charge} vs full {full}");
287                cur = next;
288            }
289            charges.push(cur.get().charged());
290        }
291        // 100x the elements costs one or two extra trie levels per step.
292        assert!(charges[1] <= 2 * charges[0], "{charges:?}");
293    }
294
295    #[test]
296    fn assoc_and_pop_on_allocated_vector_charge_log_n() {
297        let _alloc = crate::collections::share_charge::alloc_scope();
298        use cljrs_gc::GcPtr;
299
300        let v = GcPtr::new(PersistentVector::from_iter((0..10_000).map(int)));
301        let set = v.get().assoc_nth(5, int(-1)).unwrap();
302        assert_eq!(set.charged(), vector_path_bytes::<Value>(10_000));
303        let popped = v.get().pop().unwrap();
304        assert_eq!(popped.charged(), vector_path_bytes::<Value>(10_000));
305    }
306
307    #[test]
308    fn conj_chain_recharges_in_full_on_doubling() {
309        let _alloc = crate::collections::share_charge::alloc_scope();
310        use cljrs_gc::GcPtr;
311
312        let mut cur = GcPtr::new(PersistentVector::from_iter((0..100).map(int)));
313        let mut full_at = Vec::new();
314        for i in 100..1_000 {
315            let next = GcPtr::new(cur.get().conj(int(i)));
316            if next.get().charged() == next.get().full_size_extra() {
317                full_at.push(next.get().count());
318            }
319            cur = next;
320        }
321        assert_eq!(full_at, vec![200, 400, 800]);
322    }
323
324    #[test]
325    fn unallocated_builder_chain_is_charged_in_full() {
326        let _alloc = crate::collections::share_charge::alloc_scope();
327        // A Rust-side loop whose intermediates never reach the GC heap owns
328        // every node of its result.
329        let mut v = PersistentVector::empty();
330        for i in 0..1_000 {
331            v = v.conj(int(i));
332        }
333        assert_eq!(v.charged(), v.full_size_extra());
334        // A clone shares its source's nodes and may outlive it.
335        let shared = cljrs_gc::GcPtr::new(v);
336        let copy = shared.get().clone();
337        assert_eq!(copy.charged(), copy.full_size_extra());
338    }
339}