diff --git a/.gitignore b/.gitignore index ea8c4bf..96ef6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 1c3e182..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,106 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "autocfg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" - -[[package]] -name = "erasable" -version = "1.2.1" -dependencies = [ - "autocfg", - "either", - "scopeguard", -] - -[[package]] -name = "erasable" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f11890ce181d47a64e5d1eb4b6caba0e7bae911a356723740d058a5d0340b7d" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "paste" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", -] - -[[package]] -name = "paste-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" - -[[package]] -name = "ptr-union" -version = "2.1.0" -dependencies = [ - "autocfg", - "erasable 1.2.1", - "paste", -] - -[[package]] -name = "rc-borrow" -version = "1.4.0" -dependencies = [ - "autocfg", - "erasable 1.2.1", -] - -[[package]] -name = "rc-box" -version = "1.1.1" -dependencies = [ - "erasable 1.2.1", - "slice-dst 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "slice-dst" -version = "1.5.1" -dependencies = [ - "autocfg", - "erasable 1.2.1", -] - -[[package]] -name = "slice-dst" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1a6721a6d7c2997cea654e3eda6a827432c5dd0a0ed923ddd9b1d691203412" -dependencies = [ - "autocfg", - "erasable 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] diff --git a/crates/slice-dst/examples/my_vec.rs b/crates/slice-dst/examples/my_vec.rs new file mode 100644 index 0000000..99b43ef --- /dev/null +++ b/crates/slice-dst/examples/my_vec.rs @@ -0,0 +1,138 @@ +use std::{ + fmt::Display, + mem::{self, MaybeUninit}, + ops::Deref, +}; + +use slice_dst::SliceWithHeader; + +/// Default capacity of [`MyVec`]. +const MY_VEC_DEFAULT_CAPACITY: usize = 4; + +/// On the heap we will store the number of used elements in the slice (length) +/// and a slice of (maybe uninitialized) values. +/// +/// For the sake of simplicity of this example the metadata is just a [`usize`]. +/// However, in real use cases the metadata might be more complex than a +/// [`Copy`] type. +type HeapData = SliceWithHeader>; + +/// Our [`Vec`] implementation. +/// +/// _Note:_ In contrast to [`std::vec::Vec`] this stores its length on the heap. +struct MyVec(Box>); + +impl MyVec { + /// Empty [`MyVec`] with [default capacity](MY_VEC_DEFAULT_CAPACITY). + fn new() -> Self { + let inner = SliceWithHeader::new( + 0, + (0..MY_VEC_DEFAULT_CAPACITY).map(|_| MaybeUninit::uninit()), + ); + Self(inner) + } + /// Double the capacity of [`MyVec`]. + /// + /// Initialized elements are copied to the new allocated slice. + fn grow(&mut self) { + // Create an `ExactSizeIterator` double the size as the previous capacity. + let iter = (0..2 * self.capacity()).map(|_| MaybeUninit::uninit()); + // Allocate a new DST. + let new = Self(SliceWithHeader::new(self.0.header, iter)); + let mut old = mem::replace(self, new); + for idx in 0..old.0.header { + // Swap old, initialized values with new, uninitialized ones. + mem::swap(&mut self.0.slice[idx], &mut old.0.slice[idx]) + } + // Reset length to prevent drop of uninitialized values. + old.0.header = 0; + } + fn push(&mut self, element: T) { + if self.len() == self.capacity() { + self.grow(); + } + let len = &mut self.0.header; + self.0.slice[*len] = MaybeUninit::new(element); + *len += 1; + } +} + +impl Drop for MyVec { + fn drop(&mut self) { + let len = self.len(); + self.0.slice.iter_mut().take(len).for_each(|t| { + unsafe { + // SAFETY: `take(len)` ensures that only initialized elements are dropped. + std::ptr::drop_in_place(mem::transmute::<_, *mut T>(t)); + }; + }) + } +} + +impl Deref for MyVec { + type Target = MySlice; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl AsRef> for MyVec { + fn as_ref(&self) -> &MySlice { + // SAFETY: This is only safe because `MySlice` is a 'new-type' struct + // that wraps the inner data of `MyVec`. Furthermore, `MySlice` has + // `#[repr(transparent)]` which ensures that layout and alignment are + // the same as `HeapData` directly. + // + // A more sophisticated and safe way to perform such tasks is to use + // the derive macro from `ref-cast` crate. However, as it implements a + // trait this would users enable to always cast `&MySlice` to + // `&HeapData` which in turn would allow to modify the length, breaking + // the contract that ensures the safety of `MyVec`. + unsafe { mem::transmute(self.0.as_ref()) } + } +} + +/// The slice we get from a [`MyVec`]. +/// +/// We use the `ref-cast` crate to wrap the [`HeapData`] in our new-type +/// which allows us to implement our own functions. +#[repr(transparent)] +struct MySlice(HeapData); + +impl MySlice { + fn len(&self) -> usize { + self.0.header + } + fn capacity(&self) -> usize { + self.0.slice.len() + } + fn iter(&self) -> impl Iterator { + self.0.slice.iter().take(self.len()).map(|t| unsafe { + // SAFETY: `take(len)` ensures that only initialized elements are iterated. + mem::transmute(t) + }) + } +} + +/// As [`MyVec`] implements [`Deref`] we can pass in a `&MyVec`. +fn print_my_vec(slice: &MySlice) { + for (idx, t) in slice.iter().enumerate() { + println!("{}. element: {}", idx, t); + } +} + +fn main() { + let mut my_vec = MyVec::new(); + assert_eq!(MY_VEC_DEFAULT_CAPACITY, my_vec.capacity()); + assert_eq!(0, my_vec.len()); + + my_vec.push("one"); + my_vec.push("two"); + my_vec.push("three"); + my_vec.push("four"); + my_vec.push("five"); + assert_eq!(2 * MY_VEC_DEFAULT_CAPACITY, my_vec.capacity()); + assert_eq!(5, my_vec.len()); + print_my_vec(&my_vec); +}