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