Skip to content

Commit 47f0360

Browse files
committed
iterable
1 parent 86ee26c commit 47f0360

4 files changed

Lines changed: 271 additions & 0 deletions

File tree

scattered-collect/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ wide = "1.3"
2424
divan = "0.1.21"
2525
scattered-collect = { path = ".", features = ["__internal"] }
2626

27+
[[example]]
28+
name = "scattered-collect-iterable"
29+
path = "examples/iterable.rs"
30+
2731
[[example]]
2832
name = "scattered-collect-intern-strings"
2933
path = "examples/intern_strings.rs"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//! Example for `ScatteredIterable`.
2+
use scattered_collect::{gather, iterable::ScatteredIterable, scatter};
3+
4+
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
5+
struct MyId(u32);
6+
7+
/// A scattered iterable of `MyId`.
8+
#[gather]
9+
static COLLECTION: ScatteredIterable<MyId>;
10+
11+
#[scatter(COLLECTION)]
12+
static ITEM_ONE: MyId = MyId(1);
13+
14+
#[scatter(COLLECTION)]
15+
static ITEM_TWO: MyId = MyId(2);
16+
17+
#[scatter(COLLECTION)]
18+
static ITEM_THREE: MyId = MyId(3);
19+
20+
fn main() {
21+
println!("COLLECTION len: {}", COLLECTION.len());
22+
println!("ITEM_ONE: {:?}", *ITEM_ONE);
23+
println!("ITEM_TWO: {:?}", *ITEM_TWO);
24+
println!("ITEM_THREE: {:?}", *ITEM_THREE);
25+
26+
println!("Entries (head → tail):");
27+
for item in &COLLECTION {
28+
println!(" - {:?}", item);
29+
}
30+
}

scattered-collect/src/iterable.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
//! A collection of items linked at runtime into a singly-linked list in
2+
//! constructor order.
3+
#![doc = concat!("```rust\n", include_str!("../examples/iterable.rs"), "\n```\n")]
4+
5+
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
6+
7+
/// One node in a [`ScatteredIterable`], with a `static` handle at each scatter site.
8+
pub struct Ref<T: 'static> {
9+
next: AtomicPtr<Ref<T>>,
10+
value: T,
11+
}
12+
13+
impl<T> Ref<T> {
14+
/// Create a new list node holding `value`.
15+
pub const fn new(value: T) -> Self {
16+
Self {
17+
next: AtomicPtr::new(core::ptr::null_mut()),
18+
value,
19+
}
20+
}
21+
}
22+
23+
impl<T> ::core::ops::Deref for Ref<T> {
24+
type Target = T;
25+
fn deref(&self) -> &Self::Target {
26+
&self.value
27+
}
28+
}
29+
30+
impl<T> ::core::fmt::Debug for Ref<T>
31+
where
32+
T: ::core::fmt::Debug,
33+
{
34+
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
35+
(**self).fmt(f)
36+
}
37+
}
38+
39+
/// Gather state for a [`ScatteredIterable`].
40+
#[doc(hidden)]
41+
pub struct __ScatteredIterableState<T: 'static> {
42+
head: AtomicPtr<Ref<T>>,
43+
len: AtomicUsize,
44+
}
45+
46+
impl<T> __ScatteredIterableState<T> {
47+
#[doc(hidden)]
48+
pub const fn new() -> Self {
49+
Self {
50+
head: AtomicPtr::new(core::ptr::null_mut()),
51+
len: AtomicUsize::new(0),
52+
}
53+
}
54+
}
55+
56+
/// Prepend `node` to `state` and increment the length.
57+
#[doc(hidden)]
58+
pub fn submit<T: 'static>(state: &__ScatteredIterableState<T>, node: &'static Ref<T>) {
59+
loop {
60+
let head = state.head.load(Ordering::Relaxed);
61+
node.next.store(head, Ordering::Relaxed);
62+
if state
63+
.head
64+
.compare_exchange_weak(
65+
head,
66+
core::ptr::from_ref(node).cast_mut(),
67+
Ordering::Release,
68+
Ordering::Relaxed,
69+
)
70+
.is_ok()
71+
{
72+
state.len.fetch_add(1, Ordering::Relaxed);
73+
return;
74+
}
75+
}
76+
}
77+
78+
/// Iterator over a [`ScatteredIterable`].
79+
pub struct Iter<'a, T: 'static> {
80+
current: *const Ref<T>,
81+
remaining: usize,
82+
_marker: ::core::marker::PhantomData<&'a T>,
83+
}
84+
85+
impl<'a, T: 'static> Iterator for Iter<'a, T> {
86+
type Item = &'a T;
87+
88+
fn next(&mut self) -> Option<Self::Item> {
89+
if self.current.is_null() {
90+
return None;
91+
}
92+
93+
// SAFETY: Nodes are `static` and remain valid for the program lifetime.
94+
// Iteration happens after all priority-0 constructors have finished linking.
95+
let node = unsafe { &*self.current };
96+
let item = &node.value;
97+
self.current = node.next.load(Ordering::Acquire);
98+
self.remaining = self.remaining.saturating_sub(1);
99+
Some(item)
100+
}
101+
102+
fn size_hint(&self) -> (usize, Option<usize>) {
103+
(self.remaining, Some(self.remaining))
104+
}
105+
}
106+
107+
impl<T: 'static> ExactSizeIterator for Iter<'_, T> {}
108+
109+
/// A collection of items linked at runtime into a singly-linked list.
110+
///
111+
/// Scatter sites register `static` [`Ref`] handles in priority-0 constructors.
112+
/// Iteration order is the reverse of constructor registration order (each item
113+
/// is prepended to the list).
114+
///
115+
/// For link-time contiguous storage in arbitrary link order, use
116+
/// [`crate::ScatteredSlice`]. For `static` handles backed by a link section,
117+
/// use [`crate::ScatteredReferencedSlice`]. For sorted data, use
118+
/// [`crate::ScatteredSortedSlice`] or [`crate::ScatteredSortedReferencedSlice`].
119+
/// For key lookup, use [`crate::ScatteredMap`].
120+
#[doc = concat!("```rust\n", include_str!("../examples/iterable.rs"), "\n```\n")]
121+
pub struct ScatteredIterable<T: 'static> {
122+
state: &'static __ScatteredIterableState<T>,
123+
}
124+
125+
impl<T: 'static> ScatteredIterable<T> {
126+
#[doc(hidden)]
127+
pub const fn new(state: &'static __ScatteredIterableState<T>) -> Self {
128+
Self { state }
129+
}
130+
131+
/// The number of items in the iterable.
132+
pub fn len(&self) -> usize {
133+
self.state.len.load(Ordering::Acquire)
134+
}
135+
136+
/// True if the iterable is empty.
137+
pub fn is_empty(&self) -> bool {
138+
self.len() == 0
139+
}
140+
}
141+
142+
impl<T: 'static> ::core::iter::IntoIterator for &'static ScatteredIterable<T> {
143+
type Item = &'static T;
144+
type IntoIter = Iter<'static, T>;
145+
146+
fn into_iter(self) -> Self::IntoIter {
147+
Iter {
148+
current: self.state.head.load(Ordering::Acquire),
149+
remaining: self.state.len.load(Ordering::Acquire),
150+
_marker: ::core::marker::PhantomData,
151+
}
152+
}
153+
}
154+
155+
/// Declare a scattered iterable.
156+
#[macro_export]
157+
#[doc(hidden)]
158+
macro_rules! __iterable_state {
159+
($name:ident) => {
160+
$crate::__support::ident_concat!( () (__ $name __state__) () )
161+
};
162+
}
163+
164+
/// Declare a scattered iterable.
165+
#[macro_export]
166+
#[doc(hidden)]
167+
macro_rules! __iterable {
168+
(gather $vis:vis $name:ident: $ty:ty) => {
169+
$crate::__iterable!(@gather $vis static $name: ScatteredIterable<$ty>;);
170+
};
171+
(@gather $(#[$meta:meta])* $vis:vis static $name:ident: $collection:ident < $ty:ty >;) => {
172+
$crate::__support::ident_concat!((#[doc(hidden)] #[macro_export] macro_rules!) (__ $name __iterable_private_macro__) ({
173+
($passthru:tt) => {
174+
$crate::__iterable!(@scatter $passthru);
175+
};
176+
}));
177+
178+
$crate::__support::ident_concat!((#[doc(hidden)] $vis use) (__ $name __iterable_private_macro__) (as $name;));
179+
180+
$crate::__support::ident_concat!((static ) (__ $name __state__) (: $crate::iterable::__ScatteredIterableState<$ty> = $crate::iterable::__ScatteredIterableState::new();));
181+
182+
$(#[$meta])*
183+
$vis static $name: $collection<$ty> = {
184+
$crate::iterable::ScatteredIterable::new(&$crate::__iterable_state!($name))
185+
};
186+
};
187+
(scatter $collection:ident => $vis:vis $name:ident: $ty:ty = $expr:expr) => {
188+
$collection ! (( $collection => $vis static $name: $ty = $expr; ));
189+
};
190+
(@scatter ($collection:ident => $(#[$imeta:meta])* $vis:vis static $name:ident: $ty:ty = $expr:expr;)) => {
191+
$(#[$imeta])*
192+
$vis static $name: $crate::iterable::Ref<$ty> = $crate::iterable::Ref::new($expr);
193+
194+
$crate::__support::ctor::declarative::ctor!(
195+
#[ctor(unsafe, anonymous, priority = 0)]
196+
fn __iterable_submit() {
197+
$crate::iterable::submit(&$crate::__iterable_state!($collection), &$name);
198+
}
199+
);
200+
};
201+
}
202+
203+
#[cfg(all(test, not(miri)))]
204+
mod tests {
205+
use crate::iterable::ScatteredIterable;
206+
207+
__iterable!(gather pub TEST_ITERABLE: u32);
208+
__iterable!(scatter TEST_ITERABLE => pub ITERABLE_ITEM_A: u32 = 1);
209+
__iterable!(scatter TEST_ITERABLE => pub ITERABLE_ITEM_B: u32 = 3);
210+
__iterable!(scatter TEST_ITERABLE => pub ITERABLE_ITEM_C: u32 = 2);
211+
212+
#[test]
213+
fn test_scattered_iterable() {
214+
assert_eq!(TEST_ITERABLE.len(), 3);
215+
assert!(!TEST_ITERABLE.is_empty());
216+
217+
assert_eq!(*ITERABLE_ITEM_A, 1);
218+
assert_eq!(*ITERABLE_ITEM_B, 3);
219+
assert_eq!(*ITERABLE_ITEM_C, 2);
220+
221+
let items: Vec<u32> = (&TEST_ITERABLE).into_iter().copied().collect();
222+
assert_eq!(items.len(), 3);
223+
assert!(items.contains(&1));
224+
assert!(items.contains(&2));
225+
assert!(items.contains(&3));
226+
}
227+
}

scattered-collect/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#![doc = include_str!("../README.md")]
22

33
pub mod hash;
4+
pub mod iterable;
45
pub mod map;
56
pub mod referenced_slice;
67
pub mod slice;
78
pub mod sorted_referenced_slice;
89
pub mod sorted_slice;
910

11+
pub use iterable::ScatteredIterable;
1012
pub use map::ScatteredMap;
1113
pub use referenced_slice::ScatteredReferencedSlice;
1214
pub use slice::ScatteredSlice;
@@ -91,6 +93,14 @@ macro_rules! __gather_parse {
9193
);
9294
};
9395

96+
(@done ($collection:ident) #[gather] $(#[$imeta:meta])* $vis:vis static $name:ident: ScatteredIterable < $ty:ty >; ) => {
97+
$crate::__iterable ! (
98+
@gather
99+
$(#[$imeta])*
100+
$vis static $name: $collection < $ty >;
101+
);
102+
};
103+
94104
(@done #[gather] $($rest:tt)* ) => {
95105
compile_error!("Unknown collection type");
96106
};

0 commit comments

Comments
 (0)