From d552df44df0ca1e65b405c24eebf50c7fd1272f3 Mon Sep 17 00:00:00 2001 From: MattesWhite Date: Fri, 9 Apr 2021 15:15:41 +0200 Subject: [PATCH 1/3] slice-dst: Add example for SliceWithHeader --- Cargo.lock | 56 +++++++++++++ crates/slice-dst/Cargo.toml | 3 + crates/slice-dst/examples/my_vec.rs | 126 ++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 crates/slice-dst/examples/my_vec.rs diff --git a/Cargo.lock b/Cargo.lock index 1c3e182..979f05e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,15 @@ version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" +[[package]] +name = "proc-macro2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +dependencies = [ + "unicode-xid", +] + [[package]] name = "ptr-union" version = "2.1.0" @@ -65,6 +74,15 @@ dependencies = [ "paste", ] +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rc-borrow" version = "1.4.0" @@ -81,6 +99,26 @@ dependencies = [ "slice-dst 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ref-cast" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -93,6 +131,7 @@ version = "1.5.1" dependencies = [ "autocfg", "erasable 1.2.1", + "ref-cast", ] [[package]] @@ -104,3 +143,20 @@ dependencies = [ "autocfg", "erasable 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] + +[[package]] +name = "syn" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/crates/slice-dst/Cargo.toml b/crates/slice-dst/Cargo.toml index e0a1d15..621c887 100644 --- a/crates/slice-dst/Cargo.toml +++ b/crates/slice-dst/Cargo.toml @@ -28,6 +28,9 @@ optional = true [build-dependencies] autocfg = "1.0.0" +[dev-dependencies] +ref-cast = "1.0" + [[test]] name = "smoke" path = "tests/smoke.rs" diff --git a/crates/slice-dst/examples/my_vec.rs b/crates/slice-dst/examples/my_vec.rs new file mode 100644 index 0000000..5220fc2 --- /dev/null +++ b/crates/slice-dst/examples/my_vec.rs @@ -0,0 +1,126 @@ +use std::{ + fmt::Display, + mem::{self, MaybeUninit}, + ops::Deref, +}; + +use ref_cast::RefCast; +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. +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 { + // Safe as only initialized values iterated. + std::ptr::drop_in_place(mem::transmute::<_, *mut T>(t)); + }; + }) + } +} + +impl Deref for MyVec { + type Target = MySlice; + + fn deref(&self) -> &Self::Target { + MySlice::ref_cast(&self.0) + } +} + +impl AsRef> for MyVec { + fn as_ref(&self) -> &MySlice { + &*self + } +} + +/// 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. +#[derive(RefCast)] +#[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 { + // Safe as only the 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); +} From c4d03186783f31a10dc35cf1a46299dc64003ed2 Mon Sep 17 00:00:00 2001 From: MattesWhite Date: Sun, 18 Apr 2021 11:50:16 +0200 Subject: [PATCH 2/3] Remove ref-cast from slice-dts example The implementation of a 'cast trait' opened the opportunity to violate `MyVec`'s contract, making the implementation unsound. --- Cargo.lock | 56 ----------------------------- crates/slice-dst/Cargo.toml | 3 -- crates/slice-dst/examples/my_vec.rs | 24 +++++++++---- 3 files changed, 18 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 979f05e..1c3e182 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,15 +56,6 @@ version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" -[[package]] -name = "proc-macro2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" -dependencies = [ - "unicode-xid", -] - [[package]] name = "ptr-union" version = "2.1.0" @@ -74,15 +65,6 @@ dependencies = [ "paste", ] -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - [[package]] name = "rc-borrow" version = "1.4.0" @@ -99,26 +81,6 @@ dependencies = [ "slice-dst 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ref-cast" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -131,7 +93,6 @@ version = "1.5.1" dependencies = [ "autocfg", "erasable 1.2.1", - "ref-cast", ] [[package]] @@ -143,20 +104,3 @@ dependencies = [ "autocfg", "erasable 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] - -[[package]] -name = "syn" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/crates/slice-dst/Cargo.toml b/crates/slice-dst/Cargo.toml index 621c887..e0a1d15 100644 --- a/crates/slice-dst/Cargo.toml +++ b/crates/slice-dst/Cargo.toml @@ -28,9 +28,6 @@ optional = true [build-dependencies] autocfg = "1.0.0" -[dev-dependencies] -ref-cast = "1.0" - [[test]] name = "smoke" path = "tests/smoke.rs" diff --git a/crates/slice-dst/examples/my_vec.rs b/crates/slice-dst/examples/my_vec.rs index 5220fc2..99b43ef 100644 --- a/crates/slice-dst/examples/my_vec.rs +++ b/crates/slice-dst/examples/my_vec.rs @@ -4,7 +4,6 @@ use std::{ ops::Deref, }; -use ref_cast::RefCast; use slice_dst::SliceWithHeader; /// Default capacity of [`MyVec`]. @@ -12,6 +11,10 @@ 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. @@ -59,7 +62,7 @@ impl Drop for MyVec { let len = self.len(); self.0.slice.iter_mut().take(len).for_each(|t| { unsafe { - // Safe as only initialized values iterated. + // SAFETY: `take(len)` ensures that only initialized elements are dropped. std::ptr::drop_in_place(mem::transmute::<_, *mut T>(t)); }; }) @@ -70,13 +73,23 @@ impl Deref for MyVec { type Target = MySlice; fn deref(&self) -> &Self::Target { - MySlice::ref_cast(&self.0) + self.as_ref() } } impl AsRef> for MyVec { fn as_ref(&self) -> &MySlice { - &*self + // 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()) } } } @@ -84,7 +97,6 @@ impl AsRef> for MyVec { /// /// We use the `ref-cast` crate to wrap the [`HeapData`] in our new-type /// which allows us to implement our own functions. -#[derive(RefCast)] #[repr(transparent)] struct MySlice(HeapData); @@ -97,7 +109,7 @@ impl MySlice { } fn iter(&self) -> impl Iterator { self.0.slice.iter().take(self.len()).map(|t| unsafe { - // Safe as only the initialized elements are iterated. + // SAFETY: `take(len)` ensures that only initialized elements are iterated. mem::transmute(t) }) } From 8caeb9b4daa9480a3a89edec660942ab316392a1 Mon Sep 17 00:00:00 2001 From: MattesWhite Date: Sun, 18 Apr 2021 12:09:08 +0200 Subject: [PATCH 3/3] Remove Cargo.lock as it is not required for crates Keeping Cargo.lock in git often causes unnecessary conflicts when merging branches. --- .gitignore | 1 + Cargo.lock | 106 ----------------------------------------------------- 2 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 Cargo.lock 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)", -]