Skip to main content

miniextendr_api/altrep_data/iter/
list.rs

1//! A lazy list iterator with an owned, GC-visible cache.
2
3use std::cell::{Cell, OnceCell, RefCell};
4
5use crate::altrep_data::{AltListData, AltrepLen, AltrepSerialize};
6use crate::externalptr::PoolRoot;
7use crate::{R_xlen_t, SEXP, SEXPTYPE, SexpExt};
8
9/// Iterator-backed list data with a rooted cache of the yielded R objects.
10///
11/// Construction does not advance the iterator or allocate R storage. The first
12/// in-bounds read allocates one list of the declared length, and each yielded
13/// object goes into it before the next call to `Iterator::next`. So the
14/// iterator can return fresh, unprotected objects, as in
15/// `(0..n).map(|i| vec![i].into_sexp())`. It remains responsible for rooting
16/// any R objects it keeps between calls.
17///
18/// The cache list is rooted in the same pool as owning `ExternalPtr` handles.
19/// The adapter roots it itself, so it is safe before it is attached to an
20/// ALTREP object, and releases it on drop. That pool releases in O(1) in any
21/// order: R's finalizers drop ALTREP owners newest first, whatever order their
22/// caches were created in.
23///
24/// Reads generate only the requested prefix. Elements past the end of a short
25/// source read as R `NULL`; the source is dropped on its first `None` and never
26/// resumed. An infinite source is consumed only up to the declared length.
27///
28/// `Clone` copies the cache list, sharing its elements as R's
29/// `shallow_duplicate()` does, and clones the remaining iterator without
30/// advancing it. Serialization realizes the declared prefix, `NULL`s included,
31/// and restoration needs no `I`. Delegate these data traits from a concrete
32/// `#[derive(AltrepList)]` class with
33/// `#[altrep(manual, serialize, duplicate = "clone")]`.
34///
35/// The adapter is `!Send + !Sync`: build, read, clone and drop it on R's main
36/// thread.
37///
38/// ```ignore
39/// use miniextendr_api::{IntoR, altrep_data::IterListData};
40/// let data = IterListData::from_exact_iter((0..5).map(|x| vec![x, x + 1].into_sexp()));
41/// ```
42pub struct IterListData<I: Iterator<Item = SEXP>> {
43    len: usize,
44    iter: RefCell<Option<I>>,
45    cached: Cell<usize>,
46    cache: OnceCell<PoolRoot>,
47}
48
49/// An in-bounds list index as R's index type.
50fn r_index(i: usize) -> R_xlen_t {
51    R_xlen_t::try_from(i).expect("ALTREP list length exceeds R_xlen_t")
52}
53
54impl<I: Iterator<Item = SEXP>> IterListData<I> {
55    /// Create a lazy adapter for exactly the declared prefix.
56    pub fn from_iter(iter: I, len: usize) -> Self {
57        Self {
58            len,
59            iter: RefCell::new(Some(iter)),
60            cached: Cell::new(0),
61            cache: OnceCell::new(),
62        }
63    }
64
65    fn cache(&self) -> SEXP {
66        self.cache
67            .get_or_init(|| unsafe { PoolRoot::new(SEXP::alloc_list(r_index(self.len))) })
68            .get()
69    }
70}
71
72impl<I: ExactSizeIterator<Item = SEXP>> IterListData<I> {
73    /// Create a lazy adapter using the iterator's remaining length.
74    pub fn from_exact_iter(iter: I) -> Self {
75        let len = iter.len();
76        Self::from_iter(iter, len)
77    }
78}
79
80impl<I: Iterator<Item = SEXP>> AltrepLen for IterListData<I> {
81    fn altrep_len(&self) -> usize {
82        self.len
83    }
84}
85
86impl<I: Iterator<Item = SEXP>> AltListData for IterListData<I> {
87    fn elt(&self, i: usize) -> SEXP {
88        if i >= self.len {
89            return SEXP::nil();
90        }
91        // Hold the borrow during cache allocation and next(): a reentrant read
92        // fails here instead of advancing the same source twice.
93        let mut iter = self.iter.borrow_mut();
94        let cache = self.cache();
95        while self.cached.get() <= i {
96            let Some(value) = iter.as_mut().and_then(Iterator::next) else {
97                *iter = None;
98                break;
99            };
100            // Nothing allocates between next() returning and this store.
101            cache.set_vector_elt(r_index(self.cached.get()), value);
102            self.cached.set(self.cached.get() + 1);
103        }
104        if self.cached.get() == self.len {
105            *iter = None;
106        }
107        cache.vector_elt(r_index(i))
108    }
109}
110
111impl<I: Iterator<Item = SEXP> + Clone> Clone for IterListData<I> {
112    fn clone(&self) -> Self {
113        let cache = OnceCell::new();
114        if let Some(values) = self.cache.get() {
115            // The copy fills its own slots as its iterator advances. R copies
116            // an element both lists hold before changing it.
117            let _ = cache.set(unsafe { PoolRoot::new(values.get().shallow_duplicate()) });
118        }
119        Self {
120            len: self.len,
121            iter: RefCell::new(self.iter.borrow().clone()),
122            cached: Cell::new(self.cached.get()),
123            cache,
124        }
125    }
126}
127
128impl<I: Iterator<Item = SEXP>> AltrepSerialize for IterListData<I> {
129    fn serialized_state(&self) -> SEXP {
130        if self.len != 0 {
131            self.elt(self.len - 1);
132        }
133        // A list of its own, so the caller may change it.
134        self.cache().shallow_duplicate()
135    }
136
137    fn unserialize(state: SEXP) -> Option<Self> {
138        if state.type_of() != SEXPTYPE::VECSXP {
139            return None;
140        }
141        // R hands over a list it just read, so it becomes the cache as is.
142        let len = state.len();
143        Some(Self {
144            len,
145            iter: RefCell::new(None),
146            cached: Cell::new(len),
147            cache: OnceCell::from(unsafe { PoolRoot::new(state) }),
148        })
149    }
150}