Skip to main content

trait_obj_list/
lib.rs

1#![allow(rustdoc::redundant_explicit_links)] // needed for cargo-rdme
2//! A list of objects that all implement a trait, stored in one buffer.
3//!
4//! Provides the [`DynList`](crate::DynList) data structure. This can be thought
5//! of a `Vec<T>` where `T` is some `dyn Trait`. Different implementors of the
6//! trait can be added to the vec, and iterating the vector will return `dyn Trait`
7//! references.
8//!
9//! Unlike, for example, a `Vec<Box<dyn Trait>>`, a `DynList` will store all of
10//! the values in one flat array, allowing for more efficient allocation and
11//! iteration.
12//!
13//! The tradeoff is that a `DynList` is not as flexible as a `Vec` in how it can
14//! be accessed. Because items in the list take up a variable amount of size,
15//! random access is not as efficient, and items can only be pushed onto the end
16//! of the list.
17//!
18//! ```
19//! # use trait_obj_list::DynList;
20//! # use std::any::Any;
21//! let mut list = DynList::<dyn Any>::new();
22//! list.push(123i32);
23//! list.push(String::from("Hello world!"));
24//! list.push(true);
25//!
26//! for item in list.iter() {
27//!     if let Some(item) = item.downcast_ref::<i32>() {
28//!         println!("{}", *item + 7);
29//!     } else if let Some(item) = item.downcast_ref::<String>() {
30//!         println!("Text: {item}");
31//!     } else if let Some(item) = item.downcast_ref::<bool>() {
32//!         println!("{}", !*item);
33//!     }
34//! }
35//! ```
36//!
37//! # List Types
38//!
39//! The second generic argument to `DynList` is the internal structure, to select
40//! the tradeoffs between storage size and access. The following types are available:
41//!
42//! * [`DoubleLinked`](crate::types::DoubleLinked): The default. Stores the trait metadata, trait data, and a
43//!   length at the end. Allows iterating the list in both directions, but has slightly more
44//!   overhead than `SingleLinked`.
45//! * [`SingleLinked`](crate::types::SingleLinked): Stores the trait metadata and data, and nothing else. This
46//!   is the most comapact representation, but only allows iteration in list order.
47//!
48//! # Stable Rust Limitations
49//!
50//! On stable Rust, the `ptr_meta` feature is required. This uses the [`ptr_meta`](::ptr_meta)
51//! crate to get the metadata of wide pointers, and so only works with traits that
52//! implement [`ptr_meta::Pointee`](::ptr_meta::Pointee). `ptr_meta` provides an implementation for `Any`
53//! and `Error` (plus their `Send` and `Sync` variants), but others will need to
54//! use the `ptr_meta::pointee` attribute.
55//!
56//! Additionally, [`DynList::push`](crate::DynList::push) requires that the pushed type implement
57//! [`FromImpl`](crate::FromImpl). The trait can be implemented for all sized types implementing
58//! a certain trait via the [`impl_from_impl`](crate::impl_from_impl) macro.
59//!
60//! On nightly Rust, the `ptr_meta` feature can be omitted, in which case the
61//! `ptr_metadata` and `unsize` unstable features are used to lift these limitations.
62//! In this case, `DynList` will work with any trait and type.
63//!
64//! # Safety
65//!
66//! This crate uses `unsafe` a lot, due to having to manage allocations manually and work
67//! with dynamically sized types. To help ensure soundness, the test suite is checked with
68//! miri, so that any unsound accesses are caught and fixed.
69
70#![no_std]
71#![cfg_attr(not(feature = "ptr_meta"), feature(ptr_metadata))]
72#![cfg_attr(not(feature = "ptr_meta"), feature(unsize))]
73extern crate alloc;
74
75pub mod iters;
76#[cfg_attr(feature = "ptr_meta", path = "ptr.stable.rs")]
77#[cfg_attr(not(feature = "ptr_meta"), path = "ptr.nightly.rs")]
78mod ptr;
79pub mod types;
80
81use core::alloc::Layout;
82use core::mem::MaybeUninit;
83use core::ptr::NonNull;
84
85#[cfg(feature = "ptr_meta")]
86pub use ::ptr_meta;
87
88use crate::types::ListType;
89use crate::types::ListTypeRev;
90
91/// Trait for converting a sized type to an unsized one.
92///
93/// On stable Rust (with the `ptr_meta` crate feature), it can
94/// be implemented via the [`impl_from_impl`]
95/// macro. On nightly, it is implemented for all [`Unsize`](core::marker::Unsize),
96/// and the `impl_from_impl` macro does nothing.
97///
98/// # Safety
99///
100/// `from_impl` and `from_impl_mut` must return the argument, coerced to the unsized type.
101pub unsafe trait FromImpl<T> {
102	fn from_impl(value: &T) -> &Self;
103	fn from_impl_mut(value: &mut T) -> &mut Self;
104}
105
106/// A list of objects that all implement a trait.
107///
108/// See the crate documentation for more info.
109pub struct DynList<T, Typ = types::DoubleLinked>
110where
111	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + ?Sized,
112	Typ: ListType<T>,
113{
114	/// Pointer to the vector
115	data: NonNull<MaybeUninit<u8>>,
116	/// End of the valid region of the vector, in bytes
117	end: usize,
118	/// Capacity of the vector, in bytes
119	capacity: usize,
120	typ: Typ,
121	_pd: core::marker::PhantomData<(T::Metadata, T)>,
122}
123
124impl<T, Typ> DynList<T, Typ>
125where
126	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + ?Sized,
127	Typ: ListType<T>,
128{
129	/// Creates an empty list.
130	///
131	/// Does not allocate.
132	pub fn new() -> Self {
133		let typ = Typ::default();
134		Self {
135			data: NonNull::without_provenance(typ.starting_align().try_into().unwrap()),
136			end: 0,
137			capacity: 0,
138			typ: Typ::default(),
139			_pd: Default::default(),
140		}
141	}
142
143	/// Gets the number of items in the list.
144	pub fn len(&self) -> usize {
145		self.typ.num_items()
146	}
147
148	/// Checks if the list is empty.
149	pub fn is_empty(&self) -> bool {
150		self.end == 0
151	}
152
153	unsafe fn realloc_to(&mut self, new_capacity: usize) {
154		let old_layout =
155			Layout::from_size_align(self.capacity, Layout::new::<T::Metadata>().align()).unwrap();
156		let new_layout =
157			Layout::from_size_align(new_capacity, Layout::new::<T::Metadata>().align())
158				.expect("allocation too large");
159
160		let new_ptr = if new_capacity == 0 {
161			if self.capacity != 0 {
162				unsafe {
163					alloc::alloc::dealloc(self.data.cast().as_ptr(), old_layout);
164				}
165			}
166			core::ptr::without_provenance_mut(self.typ.starting_align())
167		} else if self.capacity == 0 {
168			unsafe { alloc::alloc::alloc(new_layout) }
169		} else {
170			unsafe { alloc::alloc::realloc(self.data.cast().as_ptr(), old_layout, new_capacity) }
171		};
172
173		self.data = NonNull::new(new_ptr as *mut _)
174			.unwrap_or_else(|| alloc::alloc::handle_alloc_error(new_layout));
175		self.capacity = new_capacity;
176	}
177
178	fn ensure_capacity(&mut self, additional: usize) {
179		if self.end + additional > self.capacity {
180			let new_cap = self
181				.end
182				.checked_add(additional)
183				.and_then(|v| v.checked_mul(2))
184				.expect("Allocation too large");
185			unsafe { self.realloc_to(new_cap) };
186		}
187		debug_assert!(self.end + additional <= self.capacity);
188	}
189
190	/// Moves a dyn trait from a pointer and pushes it to the end of the list.
191	///
192	/// This moves the data out of the passed-in pointer, rendering it no longer
193	/// dereferenceable.
194	///
195	/// # Safety
196	///
197	/// The pointer must be dereferenceable.
198	pub unsafe fn push_ptr(&mut self, value: *mut T) {
199		unsafe {
200			let size = self.typ.entry_size(Layout::for_value(&*value));
201			self.ensure_capacity(size);
202			self.typ.write_entry(self.data.as_ptr(), self.end, value);
203			self.end += size;
204		}
205	}
206
207	/// Moves a dyn trait out of a box and pushes it to the end of the list.
208	///
209	/// In addition to adding items from boxes, it also does not require the
210	/// dyn trait object to implement [`FromImpl`].
211	pub fn push_boxed(&mut self, value: alloc::boxed::Box<T>) {
212		unsafe {
213			let layout = Layout::for_value(&*value);
214			let ptr = alloc::boxed::Box::into_raw(value);
215			self.push_ptr(ptr);
216			alloc::alloc::dealloc(ptr as *mut _, layout);
217		}
218	}
219
220	/// Pushes an element onto the list.
221	///
222	/// This requires that the trait object implements [`FromImpl`] for `U`.
223	/// On nightly, it is implemented for all trait implementations, but on stable requires
224	/// using the [`impl_from_impl`] macro on the trait.
225	///
226	/// See also [`Self::push_boxed`] and [`Self::push_ptr`].
227	pub fn push<U>(&mut self, value: U)
228	where
229		T: FromImpl<U>,
230	{
231		unsafe {
232			let mut value = MaybeUninit::new(value);
233			let ptr = FromImpl::from_impl_mut(value.assume_init_mut()) as *mut T;
234			self.push_ptr(ptr);
235		}
236	}
237
238	/// Moves all items from another `DynList` into this one, adding them to the end in order.
239	pub fn extend_from_other<Typ2>(&mut self, other: DynList<T, Typ2>)
240	where
241		Typ2: ListType<T>,
242	{
243		for p in other.into_ptrs_iter() {
244			unsafe {
245				self.push_ptr(p);
246			}
247		}
248	}
249
250	/// Clears the list, removing all elements.
251	///
252	/// The list capacity is retained.
253	pub fn clear(&mut self) {
254		if let Some(c) = self.cursor() {
255			c.drop_after();
256		}
257	}
258
259	/// Shrinks the capacity to hold exactly what is stored in the list.
260	pub fn shrink_to_fit(&mut self) {
261		unsafe {
262			self.realloc_to(self.end);
263		}
264	}
265
266	/// Iterates through items in this list
267	pub fn iter<'a>(&'a self) -> iters::Iter<'a, T, Typ> {
268		iters::Iter {
269			offset: 0,
270			index: 0,
271			rev_index: self.typ.num_items(),
272			rev_offset: self.end,
273			list: self,
274		}
275	}
276
277	/// Iterates through items in this list mutably.
278	///
279	/// Note: if using with `dyn Any`, review [Rust's advice about calling its methods][1]!
280	/// You will need to derference the item before calling a method from `Any`.
281	///
282	/// [1]: https://doc.rust-lang.org/std/any/index.html#smart-pointers-and-dyn-any
283	pub fn iter_mut<'a>(&'a mut self) -> iters::IterMut<'a, T, Typ> {
284		iters::IterMut {
285			offset: 0,
286			index: 0,
287			rev_index: self.typ.num_items(),
288			rev_offset: self.end,
289			list: self,
290		}
291	}
292
293	/// Move elements out of the list, yielding pointers to them.
294	///
295	/// The pointers are valid until the iterator is dropped, at which point they
296	/// are all deallocated.
297	///
298	/// Yielded pointers will not have their contents dropped, so it is safe to
299	/// move the data out of them. Any remaining unyielded pointers will have their
300	/// contents dropped once the iterator is dropped.
301	pub fn into_ptrs_iter(self) -> iters::IntoPtrsIter<T, Typ> {
302		iters::IntoPtrsIter {
303			list: self,
304			offset: 0,
305			index: 0,
306		}
307	}
308
309	/// Moves elements out of the list, moving them into boxes.
310	pub fn into_box_iter(self) -> iters::IntoBoxedIter<T, Typ> {
311		iters::IntoBoxedIter {
312			inner: self.into_ptrs_iter(),
313		}
314	}
315
316	/// Creates a cursor located at the beginning of the list.
317	///
318	/// Unlike an iterator, the `Cursor` is lending, but provides the [`iters::Cursor::drop_after`]
319	/// method for dropping a section of the list.
320	///
321	/// Returns `None` if the list is empty.
322	pub fn cursor<'a>(&'a mut self) -> Option<iters::Cursor<'a, T, Typ>> {
323		if self.is_empty() {
324			return None;
325		}
326		Some(iters::Cursor {
327			offset: 0,
328			index: 0,
329			list: self,
330		})
331	}
332}
333
334impl<T, Typ> DynList<T, Typ>
335where
336	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + ?Sized,
337	Typ: ListTypeRev<T>,
338{
339	/// Creates a cursor located at the end of the list.
340	///
341	/// Returns `None` if the list is empty.
342	pub fn cursor_end<'a>(&'a mut self) -> Option<iters::Cursor<'a, T, Typ>> {
343		if self.is_empty() {
344			return None;
345		}
346		let mut cursor = iters::Cursor {
347			offset: self.end,
348			index: self.typ.num_items(),
349			list: self,
350		};
351		cursor.move_prev();
352		Some(cursor)
353	}
354}
355
356unsafe impl<T, Typ> Send for DynList<T, Typ>
357where
358	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + Send + ?Sized,
359	Typ: ListType<T> + Send,
360{
361}
362unsafe impl<T, Typ> Sync for DynList<T, Typ>
363where
364	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + Sync + ?Sized,
365	Typ: ListType<T> + Sync,
366{
367}
368
369impl<T, Typ> core::ops::Drop for DynList<T, Typ>
370where
371	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + ?Sized,
372	Typ: ListType<T>,
373{
374	fn drop(&mut self) {
375		self.clear();
376		unsafe {
377			self.realloc_to(0);
378		}
379	}
380}
381
382impl<T, Typ> Default for DynList<T, Typ>
383where
384	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + ?Sized,
385	Typ: ListType<T>,
386{
387	fn default() -> Self {
388		Self::new()
389	}
390}
391
392impl<U, T, Typ> Extend<U> for DynList<T, Typ>
393where
394	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + FromImpl<U> + ?Sized,
395	Typ: ListType<T>,
396{
397	fn extend<I: IntoIterator<Item = U>>(&mut self, iter: I) {
398		let iter = iter.into_iter();
399		// preallocate
400		let single_size = self.typ.entry_size(Layout::new::<U>());
401		self.ensure_capacity(iter.size_hint().0.saturating_mul(single_size));
402		// push
403		for v in iter {
404			self.push(v);
405		}
406	}
407}
408
409impl<T, Typ> core::fmt::Debug for DynList<T, Typ>
410where
411	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + core::fmt::Debug + ?Sized,
412	Typ: ListType<T>,
413{
414	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
415		f.debug_list().entries(self.iter()).finish()
416	}
417}
418
419impl<T, Typ1, U, Typ2> PartialEq<DynList<U, Typ2>> for DynList<T, Typ1>
420where
421	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + PartialEq<U> + ?Sized,
422	U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized,
423	Typ1: ListType<T>,
424	Typ2: ListType<U>,
425{
426	fn eq(&self, other: &DynList<U, Typ2>) -> bool {
427		let mut i1 = self.iter();
428		let mut i2 = other.iter();
429		while let Some(v1) = i1.next() {
430			let Some(v2) = i2.next() else { return false };
431			if *v1 != *v2 {
432				return false;
433			}
434		}
435		i2.next().is_none()
436	}
437}
438
439impl<T, Typ> Eq for DynList<T, Typ>
440where
441	T: ptr::Pointee<Metadata = ptr::DynMetadata<T>> + Eq + ?Sized,
442	Typ: ListType<T>,
443{
444}