From 4aaceb817e26d624aa7d1b61f21cb919a669f5e1 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 11 Feb 2025 15:23:50 -0500 Subject: [PATCH 001/110] Initial implementation (still a draft) --- README.md | 2 + src/convenience/as_ref.rs | 34 ++++++++ src/convenience/borrow.rs | 34 ++++++++ src/convenience/deref.rs | 34 ++++++++ src/convenience/mod.rs | 12 +++ src/lib.rs | 62 ++++++++++++++- src/owner.rs | 16 ++++ src/pair.rs | 158 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 src/convenience/as_ref.rs create mode 100644 src/convenience/borrow.rs create mode 100644 src/convenience/deref.rs create mode 100644 src/convenience/mod.rs create mode 100644 src/owner.rs create mode 100644 src/pair.rs diff --git a/README.md b/README.md index 1858d95..e249f76 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ Safe API for generic self-referential pairs of owner and dependent. +TODO: description and "basic API overview" (like https://crates.io/crates/takecell) + # DO NOT USE THIS LIBRARY As of right now, I have absolutely no idea whether or not this API is sound. You diff --git a/src/convenience/as_ref.rs b/src/convenience/as_ref.rs new file mode 100644 index 0000000..b43acfd --- /dev/null +++ b/src/convenience/as_ref.rs @@ -0,0 +1,34 @@ +use std::{convert::AsRef, marker::PhantomData}; + +use crate::{Owner, Pair}; + +struct AsRefOwner, T>(D, PhantomData); + +impl, T> Owner for AsRefOwner { + type Dependent<'a> + = &'a T + where + Self: 'a; + + fn make_dependent(&self) -> Self::Dependent<'_> { + self.0.as_ref() + } +} + +pub struct AsRefPair, T>(Pair>); + +impl, T> AsRefPair { + pub fn new(owner: D) -> Self { + Self(Pair::new(AsRefOwner(owner, PhantomData))) + } + + pub fn get_owner(&self) -> &D { + &self.0.get_owner().0 + } + pub fn get_dependent(&self) -> &T { + self.0.get_dependent() + } + pub fn into_owner(self) -> D { + self.0.into_owner().0 + } +} diff --git a/src/convenience/borrow.rs b/src/convenience/borrow.rs new file mode 100644 index 0000000..23376c9 --- /dev/null +++ b/src/convenience/borrow.rs @@ -0,0 +1,34 @@ +use std::{borrow::Borrow, marker::PhantomData}; + +use crate::{Owner, Pair}; + +struct BorrowOwner, T>(D, PhantomData); + +impl, T> Owner for BorrowOwner { + type Dependent<'a> + = &'a T + where + Self: 'a; + + fn make_dependent(&self) -> Self::Dependent<'_> { + self.0.borrow() + } +} + +pub struct BorrowPair, T>(Pair>); + +impl, T> BorrowPair { + pub fn new(owner: D) -> Self { + Self(Pair::new(BorrowOwner(owner, PhantomData))) + } + + pub fn get_owner(&self) -> &D { + &self.0.get_owner().0 + } + pub fn get_dependent(&self) -> &T { + self.0.get_dependent() + } + pub fn into_owner(self) -> D { + self.0.into_owner().0 + } +} diff --git a/src/convenience/deref.rs b/src/convenience/deref.rs new file mode 100644 index 0000000..065a514 --- /dev/null +++ b/src/convenience/deref.rs @@ -0,0 +1,34 @@ +use std::ops::Deref; + +use crate::{Owner, Pair}; + +struct DerefOwner(D); + +impl Owner for DerefOwner { + type Dependent<'a> + = &'a D::Target + where + Self: 'a; + + fn make_dependent(&self) -> Self::Dependent<'_> { + &self.0 + } +} + +pub struct DerefPair(Pair>); + +impl DerefPair { + pub fn new(owner: D) -> Self { + Self(Pair::new(DerefOwner(owner))) + } + + pub fn get_owner(&self) -> &D { + &self.0.get_owner().0 + } + pub fn get_dependent(&self) -> &D::Target { + self.0.get_dependent() + } + pub fn into_owner(self) -> D { + self.0.into_owner().0 + } +} diff --git a/src/convenience/mod.rs b/src/convenience/mod.rs new file mode 100644 index 0000000..809b2dd --- /dev/null +++ b/src/convenience/mod.rs @@ -0,0 +1,12 @@ +mod as_ref; +mod borrow; +mod deref; + +// TODO: all of these should be well-documented to match Pair's docs, and should +// expose the same (well, except swapping out the Owner trait bound) API. +// TODO: also the structure of the generics is less than ideal (currently +// thinking something like where O: AsRef or something) + +pub use as_ref::AsRefPair; +pub use borrow::BorrowPair; +pub use deref::DerefPair; diff --git a/src/lib.rs b/src/lib.rs index ffe2342..5e1795b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,61 @@ -//! Not very interesting yet +mod convenience; +mod owner; +mod pair; + +pub use convenience::{AsRefPair, BorrowPair, DerefPair}; +pub use owner::Owner; +pub use pair::Pair; + +// TODO: *extensive* testing, including: +// - Property-based testing +// - Fuzzing (possible? I kinda want "type fuzzing" which seems... hard) +// - Test against weird cases like contravariant types, "Oisann" types, weird +// Drop impls, impure Deref impls, etc. +// - https://docs.rs/trybuild test cases demonstrating that misuses of your API don't compile +// - All under MIRI + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn my_test() { +// let m1 = ""; +// let thing = Pair::new_deref("Hi".to_string()); +// let d1 = thing.get_dependent(); +// let o1 = thing.get_owner_deref(); +// let d2 = thing.get_dependent(); +// let d3 = thing.get_dependent(); +// println!("{d3}{m1}{d2}{o1}{d1}"); +// let s: String = thing.into_owner_deref(); +// drop(s); + +// let thing = Pair::new_deref(vec![1, 2, 3, 4]); +// println!("{:?}", thing.get_dependent()); + +// // panic!(); +// } +// } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sandbox() { + let m1 = ""; + let thing = DerefPair::new("Hi".to_string()); + let d1 = thing.get_dependent(); + let o1 = thing.get_owner(); + let d2 = thing.get_dependent(); + let d3 = thing.get_dependent(); + println!("{d3}{m1}{d2}{o1}{d1}"); + let s: String = thing.into_owner(); + drop(s); + + let thing = DerefPair::new(vec![1, 2, 3, 4]); + println!("{:?}", thing.get_dependent()); + + panic!(); + } +} diff --git a/src/owner.rs b/src/owner.rs new file mode 100644 index 0000000..d0bf882 --- /dev/null +++ b/src/owner.rs @@ -0,0 +1,16 @@ +/// A type which can act as the "owner" of some data, and can produce some +/// dependent type which borrows from `Self`. +/// +/// This trait defines the "owner"/"dependent" relationship for use by the +/// [`Pair`](crate::pair) struct, as well as the function used to create the +/// dependent from a reference to the owner. +pub trait Owner { + /// The dependent type, which borrows from the owner. + type Dependent<'a> + where + Self: 'a; + + /// Constructs the [`Dependent`](Owner::Dependent) from a reference to the + /// owner. + fn make_dependent(&self) -> Self::Dependent<'_>; +} diff --git a/src/pair.rs b/src/pair.rs new file mode 100644 index 0000000..3d0dc9b --- /dev/null +++ b/src/pair.rs @@ -0,0 +1,158 @@ +use std::{marker::PhantomData, ptr::NonNull}; + +use crate::Owner; + +// SAFETY: `Pair` has no special thread-related invariants or requirements, so +// sending a `Pair` to another thread could only cause problems if sending +// either the owner or the dependent to another thread could cause problems +// (since both are semantically moved with and made accessible through the +// `Pair`). +unsafe impl Send for Pair +where + O: Send, + for<'a> O::Dependent<'a>: Send, +{ +} + +// SAFETY: `Pair` has no special thread-related invariants or requirements, so +// sharing a reference to a `Pair` across multiple threads could only cause +// problems if sharing a reference to either the owner or the dependent across +// multiple threads could cause problems (since references to both are made +// accessible through references to the `Pair`). +unsafe impl Sync for Pair +where + O: Sync, + for<'a> O::Dependent<'a>: Sync, +{ +} + +/// A self-referential pair containing both some [`Owner`] and it's +/// [`Dependent`](Owner::Dependent). +/// +/// The owner must be provided to construct a [`Pair`], and the dependent is +/// immediately created (borrowing from the owner). Both are stored together in +/// the pair, which heap-allocates the owner so that the pair itself may be +/// moved freely without invalidating any references stored inside the +/// dependent. +/// +/// Conceptually, the pair itself has ownership over the owner `O`, the owner is +/// immutably borrowed by the dependent for the lifetime of the pair, and the +/// dependent is owned by the pair and valid for the pair's lifetime. +pub struct Pair { + // Derived from a Box + // Immutably borrowed by `self.dependent` from construction until drop + owner: NonNull, + + // Type-erased Box> + dependent: NonNull<()>, + + // Need invariance over O. + // TODO: explain why (coherence check allows different impls for fn ptrs) + prevent_covariance: PhantomData<*mut O>, +} + +/// Creates a [`NonNull`] from [`Box`]. The returned NonNull TODO: +/// describe it's properties you can assume (like dereferencability and valid +/// ways to recover the Box) +fn non_null_from_box(value: Box) -> NonNull { + // See: https://github.com/rust-lang/rust/issues/47336#issuecomment-586578713 + NonNull::from(Box::leak(value)) +} + +impl Pair { + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will + /// be computed through [`Owner::make_dependent`] during this construction. + /// + /// If you already have a [`Box`]ed owner, consider [`Pair::new_from_box`] + /// to avoid redundant reallocation. + pub fn new(owner: O) -> Self + where + O: Sized, + { + Self::new_from_box(Box::new(owner)) + } + + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will + /// be computed through [`Owner::make_dependent`] during this construction. + /// + /// If you have an unboxed `O` and only box it for this function, consider + /// the convenience constructor [`Pair::new`], which boxes the owner for + /// you to reduce clutter in your code. + pub fn new_from_box(owner: Box) -> Self { + // Convert owner into a NonNull, so we are no longer restricted by the + // invariants of Box (like noalias) + let owner = non_null_from_box(owner); + + // Borrow `owner` to construct `dependent`. This borrow conceptually + // lasts from now until drop, where we will drop `dependent` and then + // drop owner. + // SAFETY: TODO + let dependent = unsafe { owner.as_ref() }.make_dependent(); + + // Type-erase dependent so it's inexpressible self-referential lifetime + // goes away (we know that it's borrowing self.owner immutably from + // construction (now) until drop) + let dependent: NonNull> = non_null_from_box(Box::new(dependent)); + let dependent: NonNull<()> = dependent.cast(); + + Self { + owner, + dependent, + prevent_covariance: PhantomData, + } + } + + /// Returns a reference to the owner. + pub fn get_owner(&self) -> &O { + // SAFETY: TODO + unsafe { self.owner.as_ref() } + } + + /// Returns a reference to the dependent. + pub fn get_dependent(&self) -> &O::Dependent<'_> { + // SAFETY: TODO + unsafe { self.dependent.cast().as_ref() } + } + + /// Consumes the [`Pair`], dropping the dependent and returning the owner. + /// + /// If you don't need the returned owner in a [`Box`], consider the + /// convenience method [`Pair::into_owner`], which moves the owner out of + /// the box for you to reduce clutter in your code. + pub fn into_boxed_owner(self) -> Box { + // SAFETY: TODO + drop(unsafe { Box::from_raw(self.dependent.cast::>().as_ptr()) }); + + // SAFETY: TODO + let boxed_owner = unsafe { Box::from_raw(self.owner.as_ptr()) }; + + std::mem::forget(self); + + boxed_owner + } + + /// Consumes the [`Pair`], dropping the dependent and returning the owner. + /// + /// If you manually box the returned owner for your own purposes, consider + /// [`Pair::into_boxed_owner`] to avoid redundant reallocation. + pub fn into_owner(self) -> O + where + O: Sized, + { + *self.into_boxed_owner() + } +} + +/// The [`Drop`] implementation for [`Pair`] will drop both the dependent and +/// the owner, in that order. +impl Drop for Pair { + fn drop(&mut self) { + // Call `Drop::drop` on the dependent `O::Dependent<'_>` + // SAFETY: TODO + drop(unsafe { Box::from_raw(self.dependent.cast::>().as_ptr()) }); + + // Call `Drop::drop` on the owner `Box` + // SAFETY: TODO + drop(unsafe { Box::from_raw(self.owner.as_ptr()) }); + } +} From fc6583ae6ba8bf0ba17318fe06c021e358c663d6 Mon Sep 17 00:00:00 2001 From: Isaac Chen <79286236+ijchen@users.noreply.github.com> Date: Tue, 11 Feb 2025 22:04:06 -0500 Subject: [PATCH 002/110] fixed typo --- src/pair.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pair.rs b/src/pair.rs index 3d0dc9b..d930da4 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -26,7 +26,7 @@ where { } -/// A self-referential pair containing both some [`Owner`] and it's +/// A self-referential pair containing both some [`Owner`] and its /// [`Dependent`](Owner::Dependent). /// /// The owner must be provided to construct a [`Pair`], and the dependent is From c894aabda800718a137d6eaeb07e9ad8a4c2df91 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 11 Feb 2025 17:09:32 -0500 Subject: [PATCH 003/110] fixed panic unsafety in Pair::into_boxed_owner --- src/pair.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index d930da4..ecd84ae 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, ptr::NonNull}; +use std::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; use crate::Owner; @@ -120,13 +120,16 @@ impl Pair { /// convenience method [`Pair::into_owner`], which moves the owner out of /// the box for you to reduce clutter in your code. pub fn into_boxed_owner(self) -> Box { - // SAFETY: TODO - drop(unsafe { Box::from_raw(self.dependent.cast::>().as_ptr()) }); + // TODO: explain why this is necessary (double free from dropping self), + // and why it's important that it comes before the dependent drop (its + // destructor may panic) + let this = ManuallyDrop::new(self); // SAFETY: TODO - let boxed_owner = unsafe { Box::from_raw(self.owner.as_ptr()) }; + drop(unsafe { Box::from_raw(this.dependent.cast::>().as_ptr()) }); - std::mem::forget(self); + // SAFETY: TODO + let boxed_owner = unsafe { Box::from_raw(this.owner.as_ptr()) }; boxed_owner } From 1a6c5c91439d74b620f9c45c5e66433ed9dba9ec Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 11 Feb 2025 18:21:26 -0500 Subject: [PATCH 004/110] wrote comment explaining ManuallyDrop in into_boxed_owner --- src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++++++------------ src/pair.rs | 11 ++++++++--- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5e1795b..566bf7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,18 +43,46 @@ mod tests { #[test] fn sandbox() { - let m1 = ""; - let thing = DerefPair::new("Hi".to_string()); - let d1 = thing.get_dependent(); - let o1 = thing.get_owner(); - let d2 = thing.get_dependent(); - let d3 = thing.get_dependent(); - println!("{d3}{m1}{d2}{o1}{d1}"); - let s: String = thing.into_owner(); - drop(s); - - let thing = DerefPair::new(vec![1, 2, 3, 4]); - println!("{:?}", thing.get_dependent()); + // let m1 = ""; + // let thing = DerefPair::new("Hi".to_string()); + // let d1 = thing.get_dependent(); + // let o1 = thing.get_owner(); + // let d2 = thing.get_dependent(); + // let d3 = thing.get_dependent(); + // println!("{d3}{m1}{d2}{o1}{d1}"); + // let s: String = thing.into_owner(); + // drop(s); + + // let thing = DerefPair::new(vec![1, 2, 3, 4]); + // println!("{:?}", thing.get_dependent()); + + struct Foo; + struct Bar; + impl Drop for Foo { + fn drop(&mut self) { + println!("Dropping Foo"); + } + } + impl Drop for Bar { + fn drop(&mut self) { + println!("Dropping Bar"); + panic!(); + } + } + + impl Owner for Foo { + type Dependent<'a> + = Bar + where + Self: 'a; + + fn make_dependent(&self) -> Self::Dependent<'_> { + Bar + } + } + + let pair = Pair::new(Foo); + pair.into_owner(); panic!(); } diff --git a/src/pair.rs b/src/pair.rs index ecd84ae..40d4edd 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -120,9 +120,14 @@ impl Pair { /// convenience method [`Pair::into_owner`], which moves the owner out of /// the box for you to reduce clutter in your code. pub fn into_boxed_owner(self) -> Box { - // TODO: explain why this is necessary (double free from dropping self), - // and why it's important that it comes before the dependent drop (its - // destructor may panic) + // Prevent dropping `self` at the end of this scope - otherwise, the + // Pair drop implementation would attempt to drop the owner and + // dependent, which would be... not good (unsound). + // + // It's important that we do this before calling the dependent's drop, + // since a panic in that drop would otherwise cause at least a double + // panic, and potentially even unsoundness (although that part I'm less + // sure of) let this = ManuallyDrop::new(self); // SAFETY: TODO From 3be6e74e45d15dfae1fb35b62887518559a194bd Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 12 Feb 2025 16:17:04 -0500 Subject: [PATCH 005/110] improving documentation --- src/lib.rs | 68 ++++++++++++++++++++++++++--------------------------- src/pair.rs | 31 ++++++++++++++++++------ 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 566bf7f..c30beca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,47 +43,47 @@ mod tests { #[test] fn sandbox() { - // let m1 = ""; - // let thing = DerefPair::new("Hi".to_string()); - // let d1 = thing.get_dependent(); - // let o1 = thing.get_owner(); - // let d2 = thing.get_dependent(); - // let d3 = thing.get_dependent(); - // println!("{d3}{m1}{d2}{o1}{d1}"); - // let s: String = thing.into_owner(); - // drop(s); + let m1 = ""; + let thing = DerefPair::new("Hi".to_string()); + let d1 = thing.get_dependent(); + let o1 = thing.get_owner(); + let d2 = thing.get_dependent(); + let d3 = thing.get_dependent(); + println!("{d3}{m1}{d2}{o1}{d1}"); + let s: String = thing.into_owner(); + drop(s); // let thing = DerefPair::new(vec![1, 2, 3, 4]); // println!("{:?}", thing.get_dependent()); - struct Foo; - struct Bar; - impl Drop for Foo { - fn drop(&mut self) { - println!("Dropping Foo"); - } - } - impl Drop for Bar { - fn drop(&mut self) { - println!("Dropping Bar"); - panic!(); - } - } + // struct Foo; + // struct Bar; + // impl Drop for Foo { + // fn drop(&mut self) { + // println!("Dropping Foo"); + // } + // } + // impl Drop for Bar { + // fn drop(&mut self) { + // println!("Dropping Bar"); + // panic!(); + // } + // } - impl Owner for Foo { - type Dependent<'a> - = Bar - where - Self: 'a; + // impl Owner for Foo { + // type Dependent<'a> + // = Bar + // where + // Self: 'a; - fn make_dependent(&self) -> Self::Dependent<'_> { - Bar - } - } + // fn make_dependent(&self) -> Self::Dependent<'_> { + // Bar + // } + // } - let pair = Pair::new(Foo); - pair.into_owner(); + // let pair = Pair::new(Foo); + // pair.into_owner(); - panic!(); + // panic!(); } } diff --git a/src/pair.rs b/src/pair.rs index 40d4edd..d342bda 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -46,14 +46,26 @@ pub struct Pair { // Type-erased Box> dependent: NonNull<()>, - // Need invariance over O. - // TODO: explain why (coherence check allows different impls for fn ptrs) + // Need invariance over O - if we were covariant or contravariant, two + // different `O`s with two different `Owner` impls (and importantly, two + // different Dependent associated types) which have a subtype/supertype + // relationship could be incorrectly treated as substitutable in a Pair. + // That would lead to effectively an unchecked transmute of each field, + // which would obviously be unsound. + // + // Coherence doesn't help us here, since there are types which are able to + // have different impls of the same trait, but also have a subtype/supertype + // relationship (namely, `fn(&'static T)` and `for<'a> fn(&'a T)` ) prevent_covariance: PhantomData<*mut O>, } -/// Creates a [`NonNull`] from [`Box`]. The returned NonNull TODO: -/// describe it's properties you can assume (like dereferencability and valid -/// ways to recover the Box) +/// Creates a [`NonNull`] from [`Box`]. The returned NonNull is the same +/// pointer as the Box, and therefore comes with all of Box's representation +/// guarantees: +/// - The returned NonNull will be suitably aligned for T +/// - The returned NonNull will point to a valid T +/// - The returned NonNull was allocated with the [`Global`](std::alloc::Global) +/// allocator and a valid [`Layout`](std::alloc::Layout) for `T`. fn non_null_from_box(value: Box) -> NonNull { // See: https://github.com/rust-lang/rust/issues/47336#issuecomment-586578713 NonNull::from(Box::leak(value)) @@ -80,13 +92,18 @@ impl Pair { /// you to reduce clutter in your code. pub fn new_from_box(owner: Box) -> Self { // Convert owner into a NonNull, so we are no longer restricted by the - // invariants of Box (like noalias) + // aliasing requirements of Box let owner = non_null_from_box(owner); // Borrow `owner` to construct `dependent`. This borrow conceptually // lasts from now until drop, where we will drop `dependent` and then // drop owner. - // SAFETY: TODO + + // SAFETY: `owner` was just converted from a valid Box, and inherits the + // alignment and validity guarantees of Box. Additionally, the value + // behind the pointer is currently not borrowed at all - this marks the + // beginning of a shared borrow which will last until the returned + // `Pair` is dropped. let dependent = unsafe { owner.as_ref() }.make_dependent(); // Type-erase dependent so it's inexpressible self-referential lifetime From 9fee9b6b6a6c895c2b6cdca9a988b130eec274b3 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 12 Feb 2025 16:26:03 -0500 Subject: [PATCH 006/110] wrote safety justification for Pair::get_owner --- src/pair.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pair.rs b/src/pair.rs index d342bda..e784182 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -121,7 +121,13 @@ impl Pair { /// Returns a reference to the owner. pub fn get_owner(&self) -> &O { - // SAFETY: TODO + // SAFETY: `self.owner` was originally converted from a valid Box, and + // inherited the alignment and validity guarantees of Box - and neither + // our code nor any of our exposed APIs could have invalidated those + // since construction. Additionally, the value behind the pointer is + // currently in a shared borrow state (no exclusive borrows, no other + // code assuming unique ownership), and will be until the Pair is + // dropped. Here, we only add another shared borrow. unsafe { self.owner.as_ref() } } From f97b0eaffd047b50c426ccf820f418e7563e3f3b Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 12 Feb 2025 16:41:22 -0500 Subject: [PATCH 007/110] wrote safety justification for Pair::get_dependent --- src/pair.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index e784182..570b008 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -133,8 +133,17 @@ impl Pair { /// Returns a reference to the dependent. pub fn get_dependent(&self) -> &O::Dependent<'_> { - // SAFETY: TODO - unsafe { self.dependent.cast().as_ref() } + // SAFETY: `self.dependent` was originally converted from a valid + // Box>, and type-erased to a NonNull<()>. As such, it + // inherited the alignment and validity guarantees of Box (for an + // O::Dependent<'_>) - and neither our code nor any of our exposed APIs + // could have invalidated those since construction. Additionally, the + // value behind the pointer is currently either not borrowed at all, or + // in a shared borrow state (no exclusive borrows, no other code + // assuming unique ownership), and will remain in one of those two + // states until the Pair is dropped. Here, we only either create the + // first shared borrow, or add another. + unsafe { self.dependent.cast::>().as_ref() } } /// Consumes the [`Pair`], dropping the dependent and returning the owner. From 0fbe312cc27df9ff9a6938d84e39ac71cf7208ea Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 12 Feb 2025 16:53:10 -0500 Subject: [PATCH 008/110] wrote safety justification for Pair::into_boxed_owner --- src/pair.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index 570b008..8ed5a44 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -162,10 +162,16 @@ impl Pair { // sure of) let this = ManuallyDrop::new(self); - // SAFETY: TODO + // SAFETY: `this.dependent` was originally created from a Box, and never + // invalidated since then. Because we took ownership of `self`, we know + // there are no outstanding borrows to the dependent. Therefore, + // reconstructing the original Box> is okay. drop(unsafe { Box::from_raw(this.dependent.cast::>().as_ptr()) }); - // SAFETY: TODO + // SAFETY: `this.owner` was originally created from a Box, and never + // invalidated since then. Because we took ownership of `self`, and we + // just dropped the dependent, we know there are no outstanding borrows + // to owner. Therefore, reconstructing the original Box is okay. let boxed_owner = unsafe { Box::from_raw(this.owner.as_ptr()) }; boxed_owner From 405a0375fdb56231053639cc5b614c43657146f3 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 12 Feb 2025 17:02:34 -0500 Subject: [PATCH 009/110] wrote safety justification for ::drop --- src/pair.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index 8ed5a44..96128d5 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -194,11 +194,19 @@ impl Pair { impl Drop for Pair { fn drop(&mut self) { // Call `Drop::drop` on the dependent `O::Dependent<'_>` - // SAFETY: TODO + + // SAFETY: `self.dependent` was originally created from a Box, and never + // invalidated since then. Because we are in drop, we know there are no + // outstanding borrows to the dependent. Therefore, reconstructing the + // original Box> is okay. drop(unsafe { Box::from_raw(self.dependent.cast::>().as_ptr()) }); // Call `Drop::drop` on the owner `Box` - // SAFETY: TODO + + // SAFETY: `this.owner` was originally created from a Box, and never + // invalidated since then. Because we are in drop, and we just dropped + // the dependent, we know there are no outstanding borrows to owner. + // Therefore, reconstructing the original Box is okay. drop(unsafe { Box::from_raw(self.owner.as_ptr()) }); } } From d8bd6baad9b89f0c93444e1bb3d99316b67ebd0e Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 12 Feb 2025 17:30:06 -0500 Subject: [PATCH 010/110] added to-do comment for fallible 'try' API --- src/pair.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pair.rs b/src/pair.rs index 96128d5..347cc0a 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -72,6 +72,9 @@ fn non_null_from_box(value: Box) -> NonNull { } impl Pair { + // TODO: expose a `try_new` and `try_new_from_box` API (will need updates to + // the Owner trait) + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// From c566efcd131a1c7a8f888005e568d91ed978268f Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 16 Feb 2025 13:25:54 -0500 Subject: [PATCH 011/110] updated with_dependent to fix soundness hole - added with_dependent_mut --- src/pair.rs | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index 347cc0a..5e31424 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -134,19 +134,26 @@ impl Pair { unsafe { self.owner.as_ref() } } - /// Returns a reference to the dependent. - pub fn get_dependent(&self) -> &O::Dependent<'_> { - // SAFETY: `self.dependent` was originally converted from a valid - // Box>, and type-erased to a NonNull<()>. As such, it - // inherited the alignment and validity guarantees of Box (for an - // O::Dependent<'_>) - and neither our code nor any of our exposed APIs - // could have invalidated those since construction. Additionally, the - // value behind the pointer is currently either not borrowed at all, or - // in a shared borrow state (no exclusive borrows, no other code - // assuming unique ownership), and will remain in one of those two - // states until the Pair is dropped. Here, we only either create the - // first shared borrow, or add another. - unsafe { self.dependent.cast::>().as_ref() } + /// TODO + pub fn with_dependent<'a, F: for<'b> FnOnce(&'a O::Dependent<'b>) -> T, T>( + &'a self, + f: F, + ) -> T { + // SAFETY: TODO + let dependent = unsafe { self.dependent.cast::>().as_ref() }; + + f(dependent) + } + + /// TODO + pub fn with_dependent_mut<'a, F: for<'b> FnOnce(&'a mut O::Dependent<'b>) -> T, T>( + &'a mut self, + f: F, + ) -> T { + // SAFETY: TODO + let dependent = unsafe { self.dependent.cast::>().as_mut() }; + + f(dependent) } /// Consumes the [`Pair`], dropping the dependent and returning the owner. From 1f7fc85cef713ec7e948be464fd7efd6417efd58 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 16 Feb 2025 15:48:54 -0500 Subject: [PATCH 012/110] cut lifetime GATs --- src/convenience/as_ref.rs | 34 --------------- src/convenience/borrow.rs | 34 --------------- src/convenience/deref.rs | 34 --------------- src/convenience/mod.rs | 12 ------ src/lib.rs | 88 +++++++++++++++++++-------------------- src/owner.rs | 30 ++++++++++--- src/pair.rs | 46 +++++++++++++------- 7 files changed, 100 insertions(+), 178 deletions(-) delete mode 100644 src/convenience/as_ref.rs delete mode 100644 src/convenience/borrow.rs delete mode 100644 src/convenience/deref.rs delete mode 100644 src/convenience/mod.rs diff --git a/src/convenience/as_ref.rs b/src/convenience/as_ref.rs deleted file mode 100644 index b43acfd..0000000 --- a/src/convenience/as_ref.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::{convert::AsRef, marker::PhantomData}; - -use crate::{Owner, Pair}; - -struct AsRefOwner, T>(D, PhantomData); - -impl, T> Owner for AsRefOwner { - type Dependent<'a> - = &'a T - where - Self: 'a; - - fn make_dependent(&self) -> Self::Dependent<'_> { - self.0.as_ref() - } -} - -pub struct AsRefPair, T>(Pair>); - -impl, T> AsRefPair { - pub fn new(owner: D) -> Self { - Self(Pair::new(AsRefOwner(owner, PhantomData))) - } - - pub fn get_owner(&self) -> &D { - &self.0.get_owner().0 - } - pub fn get_dependent(&self) -> &T { - self.0.get_dependent() - } - pub fn into_owner(self) -> D { - self.0.into_owner().0 - } -} diff --git a/src/convenience/borrow.rs b/src/convenience/borrow.rs deleted file mode 100644 index 23376c9..0000000 --- a/src/convenience/borrow.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::{borrow::Borrow, marker::PhantomData}; - -use crate::{Owner, Pair}; - -struct BorrowOwner, T>(D, PhantomData); - -impl, T> Owner for BorrowOwner { - type Dependent<'a> - = &'a T - where - Self: 'a; - - fn make_dependent(&self) -> Self::Dependent<'_> { - self.0.borrow() - } -} - -pub struct BorrowPair, T>(Pair>); - -impl, T> BorrowPair { - pub fn new(owner: D) -> Self { - Self(Pair::new(BorrowOwner(owner, PhantomData))) - } - - pub fn get_owner(&self) -> &D { - &self.0.get_owner().0 - } - pub fn get_dependent(&self) -> &T { - self.0.get_dependent() - } - pub fn into_owner(self) -> D { - self.0.into_owner().0 - } -} diff --git a/src/convenience/deref.rs b/src/convenience/deref.rs deleted file mode 100644 index 065a514..0000000 --- a/src/convenience/deref.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::ops::Deref; - -use crate::{Owner, Pair}; - -struct DerefOwner(D); - -impl Owner for DerefOwner { - type Dependent<'a> - = &'a D::Target - where - Self: 'a; - - fn make_dependent(&self) -> Self::Dependent<'_> { - &self.0 - } -} - -pub struct DerefPair(Pair>); - -impl DerefPair { - pub fn new(owner: D) -> Self { - Self(Pair::new(DerefOwner(owner))) - } - - pub fn get_owner(&self) -> &D { - &self.0.get_owner().0 - } - pub fn get_dependent(&self) -> &D::Target { - self.0.get_dependent() - } - pub fn into_owner(self) -> D { - self.0.into_owner().0 - } -} diff --git a/src/convenience/mod.rs b/src/convenience/mod.rs deleted file mode 100644 index 809b2dd..0000000 --- a/src/convenience/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod as_ref; -mod borrow; -mod deref; - -// TODO: all of these should be well-documented to match Pair's docs, and should -// expose the same (well, except swapping out the Owner trait bound) API. -// TODO: also the structure of the generics is less than ideal (currently -// thinking something like where O: AsRef or something) - -pub use as_ref::AsRefPair; -pub use borrow::BorrowPair; -pub use deref::DerefPair; diff --git a/src/lib.rs b/src/lib.rs index c30beca..8a5d672 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,6 @@ -mod convenience; mod owner; mod pair; -pub use convenience::{AsRefPair, BorrowPair, DerefPair}; pub use owner::Owner; pub use pair::Pair; @@ -37,53 +35,53 @@ pub use pair::Pair; // } // } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - #[test] - fn sandbox() { - let m1 = ""; - let thing = DerefPair::new("Hi".to_string()); - let d1 = thing.get_dependent(); - let o1 = thing.get_owner(); - let d2 = thing.get_dependent(); - let d3 = thing.get_dependent(); - println!("{d3}{m1}{d2}{o1}{d1}"); - let s: String = thing.into_owner(); - drop(s); +// #[test] +// fn sandbox() { +// let m1 = ""; +// let thing = DerefPair::new("Hi".to_string()); +// let d1 = thing.get_dependent(); +// let o1 = thing.get_owner(); +// let d2 = thing.get_dependent(); +// let d3 = thing.get_dependent(); +// println!("{d3}{m1}{d2}{o1}{d1}"); +// let s: String = thing.into_owner(); +// drop(s); - // let thing = DerefPair::new(vec![1, 2, 3, 4]); - // println!("{:?}", thing.get_dependent()); +// // let thing = DerefPair::new(vec![1, 2, 3, 4]); +// // println!("{:?}", thing.get_dependent()); - // struct Foo; - // struct Bar; - // impl Drop for Foo { - // fn drop(&mut self) { - // println!("Dropping Foo"); - // } - // } - // impl Drop for Bar { - // fn drop(&mut self) { - // println!("Dropping Bar"); - // panic!(); - // } - // } +// // struct Foo; +// // struct Bar; +// // impl Drop for Foo { +// // fn drop(&mut self) { +// // println!("Dropping Foo"); +// // } +// // } +// // impl Drop for Bar { +// // fn drop(&mut self) { +// // println!("Dropping Bar"); +// // panic!(); +// // } +// // } - // impl Owner for Foo { - // type Dependent<'a> - // = Bar - // where - // Self: 'a; +// // impl Owner for Foo { +// // type Dependent<'a> +// // = Bar +// // where +// // Self: 'a; - // fn make_dependent(&self) -> Self::Dependent<'_> { - // Bar - // } - // } +// // fn make_dependent(&self) -> Self::Dependent<'_> { +// // Bar +// // } +// // } - // let pair = Pair::new(Foo); - // pair.into_owner(); +// // let pair = Pair::new(Foo); +// // pair.into_owner(); - // panic!(); - } -} +// // panic!(); +// } +// } diff --git a/src/owner.rs b/src/owner.rs index d0bf882..42bc305 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -4,13 +4,33 @@ /// This trait defines the "owner"/"dependent" relationship for use by the /// [`Pair`](crate::pair) struct, as well as the function used to create the /// dependent from a reference to the owner. -pub trait Owner { +pub trait Owner<'owner> { /// The dependent type, which borrows from the owner. - type Dependent<'a> - where - Self: 'a; + type Dependent; /// Constructs the [`Dependent`](Owner::Dependent) from a reference to the /// owner. - fn make_dependent(&self) -> Self::Dependent<'_>; + fn make_dependent(&'owner self) -> Self::Dependent; } + +// impl<'any> Owner<'any> for String { +// type Dependent = &'any str; + +// fn make_dependent(&'any self) -> Self::Dependent { +// self +// } +// } +// impl<'any, T> Owner<'any> for Vec { +// type Dependent = &'any [T]; + +// fn make_dependent(&'any self) -> Self::Dependent { +// self +// } +// } +// impl<'any, T: std::ops::Deref> Owner<'any> for T { +// type Dependent = &'any T::Target; + +// fn make_dependent(&'any self) -> Self::Dependent { +// self +// } +// } diff --git a/src/pair.rs b/src/pair.rs index 5e31424..9d3cd81 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -1,3 +1,15 @@ +// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +// # # +// # !!! WARNING !!! # +// # # +// # SAFETY COMMENTS WERE WRITTEN BEFORE SUBSTANTIAL CHANGES # +// # TO THE `Owner` TRAIT WERE MADE, AND MAY BE INCORRECT! # +// # # +// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +// p.s. I hope u like my cool ASCII box <3 + +// TODO: all comments are potentially out of date due to Owner trait updates. + use std::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; use crate::Owner; @@ -7,10 +19,10 @@ use crate::Owner; // either the owner or the dependent to another thread could cause problems // (since both are semantically moved with and made accessible through the // `Pair`). -unsafe impl Send for Pair +unsafe impl Owner<'any> + ?Sized> Send for Pair where O: Send, - for<'a> O::Dependent<'a>: Send, + for<'any> >::Dependent: Send, { } @@ -19,10 +31,10 @@ where // problems if sharing a reference to either the owner or the dependent across // multiple threads could cause problems (since references to both are made // accessible through references to the `Pair`). -unsafe impl Sync for Pair +unsafe impl Owner<'any>> Sync for Pair where O: Sync, - for<'a> O::Dependent<'a>: Sync, + for<'any> >::Dependent: Sync, { } @@ -38,7 +50,7 @@ where /// Conceptually, the pair itself has ownership over the owner `O`, the owner is /// immutably borrowed by the dependent for the lifetime of the pair, and the /// dependent is owned by the pair and valid for the pair's lifetime. -pub struct Pair { +pub struct Pair Owner<'any> + ?Sized> { // Derived from a Box // Immutably borrowed by `self.dependent` from construction until drop owner: NonNull, @@ -71,7 +83,7 @@ fn non_null_from_box(value: Box) -> NonNull { NonNull::from(Box::leak(value)) } -impl Pair { +impl Owner<'any> + ?Sized> Pair { // TODO: expose a `try_new` and `try_new_from_box` API (will need updates to // the Owner trait) @@ -112,7 +124,7 @@ impl Pair { // Type-erase dependent so it's inexpressible self-referential lifetime // goes away (we know that it's borrowing self.owner immutably from // construction (now) until drop) - let dependent: NonNull> = non_null_from_box(Box::new(dependent)); + let dependent: NonNull = non_null_from_box(Box::new(dependent)); let dependent: NonNull<()> = dependent.cast(); Self { @@ -135,23 +147,29 @@ impl Pair { } /// TODO - pub fn with_dependent<'a, F: for<'b> FnOnce(&'a O::Dependent<'b>) -> T, T>( + // TODO: The compiler will allow us to elide this generic vvvv lifetime + // what does it default to? This feels suspicious to me. vvvv + pub fn with_dependent<'a, F: for<'b> FnOnce(&'a >::Dependent) -> T, T>( &'a self, f: F, ) -> T { // SAFETY: TODO - let dependent = unsafe { self.dependent.cast::>().as_ref() }; + let dependent = unsafe { self.dependent.cast::().as_ref() }; f(dependent) } /// TODO - pub fn with_dependent_mut<'a, F: for<'b> FnOnce(&'a mut O::Dependent<'b>) -> T, T>( + pub fn with_dependent_mut< + 'a, + F: for<'b> FnOnce(&'a mut >::Dependent) -> T, + T, + >( &'a mut self, f: F, ) -> T { // SAFETY: TODO - let dependent = unsafe { self.dependent.cast::>().as_mut() }; + let dependent = unsafe { self.dependent.cast::().as_mut() }; f(dependent) } @@ -176,7 +194,7 @@ impl Pair { // invalidated since then. Because we took ownership of `self`, we know // there are no outstanding borrows to the dependent. Therefore, // reconstructing the original Box> is okay. - drop(unsafe { Box::from_raw(this.dependent.cast::>().as_ptr()) }); + drop(unsafe { Box::from_raw(this.dependent.cast::().as_ptr()) }); // SAFETY: `this.owner` was originally created from a Box, and never // invalidated since then. Because we took ownership of `self`, and we @@ -201,7 +219,7 @@ impl Pair { /// The [`Drop`] implementation for [`Pair`] will drop both the dependent and /// the owner, in that order. -impl Drop for Pair { +impl Owner<'any> + ?Sized> Drop for Pair { fn drop(&mut self) { // Call `Drop::drop` on the dependent `O::Dependent<'_>` @@ -209,7 +227,7 @@ impl Drop for Pair { // invalidated since then. Because we are in drop, we know there are no // outstanding borrows to the dependent. Therefore, reconstructing the // original Box> is okay. - drop(unsafe { Box::from_raw(self.dependent.cast::>().as_ptr()) }); + drop(unsafe { Box::from_raw(self.dependent.cast::().as_ptr()) }); // Call `Drop::drop` on the owner `Box` From 756238a6ef89a06d06cfaf213526ff64380d0a7f Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 17 Feb 2025 18:59:45 -0500 Subject: [PATCH 013/110] genuinely gross generics gymnastics guaranteed to gather gags --- src/as_ref.rs | 36 +++++++++++++++++++ src/deref.rs | 27 ++++++++++++++ src/lib.rs | 99 ++++++++++++++++++++++++++++----------------------- src/owner.rs | 18 +++++++--- src/pair.rs | 53 ++++++++++++++++++--------- 5 files changed, 168 insertions(+), 65 deletions(-) create mode 100644 src/as_ref.rs create mode 100644 src/deref.rs diff --git a/src/as_ref.rs b/src/as_ref.rs new file mode 100644 index 0000000..98d926a --- /dev/null +++ b/src/as_ref.rs @@ -0,0 +1,36 @@ +use std::{convert::AsRef, marker::PhantomData}; + +use crate::{HasDependent, Owner, Pair}; + +struct AsRefOwner(O, PhantomData); + +impl<'any, O: AsRef, D> HasDependent<'any> for AsRefOwner { + type Dependent = &'any D; +} + +impl, D> Owner for AsRefOwner { + fn make_dependent(&self) -> >::Dependent { + self.0.as_ref() + } +} + +pub struct AsRefPair, D>(Pair>); + +impl, D> AsRefPair { + pub fn new(owner: O) -> Self { + Self(Pair::new(AsRefOwner(owner, PhantomData))) + } + + pub fn get_owner(&self) -> &O { + &self.0.get_owner().0 + } + pub fn with_dependent<'a, F: for<'b> FnOnce(&'b D) -> U, U>(&'a self, f: F) -> U { + self.0.with_dependent(|dependent| f(dependent)) + } + pub fn get_dependent(&self) -> &D { + self.0.with_dependent(|dependent| dependent) + } + pub fn into_owner(self) -> O { + self.0.into_owner().0 + } +} diff --git a/src/deref.rs b/src/deref.rs new file mode 100644 index 0000000..f1b16c9 --- /dev/null +++ b/src/deref.rs @@ -0,0 +1,27 @@ +use std::ops::Deref; + +use crate::{HasDependent, Owner, Pair}; + +pub struct DerefOwner(pub O); + +impl<'any, O: Deref> HasDependent<'any> for DerefOwner { + type Dependent = &'any O::Target; +} +impl Owner for DerefOwner { + fn make_dependent(&self) -> >::Dependent { + &self.0 + } +} + +impl Pair> { + pub fn new_deref(owner: O) -> Self { + Self::new(DerefOwner(owner)) + } + + pub fn with_dependent_deref T, T>(&self, f: F) -> T { + self.with_dependent(|dependent| f(dependent)) + } + pub fn get_dependent_deref(&self) -> &O::Target { + self.with_dependent(|dependent| dependent) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8a5d672..66953b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,10 @@ +mod as_ref; +mod deref; mod owner; mod pair; -pub use owner::Owner; +pub use as_ref::AsRefPair; +pub use owner::{HasDependent, Owner}; pub use pair::Pair; // TODO: *extensive* testing, including: @@ -12,6 +15,15 @@ pub use pair::Pair; // - https://docs.rs/trybuild test cases demonstrating that misuses of your API don't compile // - All under MIRI +// impl<'any> ForLt<'any> for String { +// type Dependent = &'any str; +// } +// impl Owner for String { +// fn make_dependent(&self) -> >::Dependent { +// self +// } +// } + // #[cfg(test)] // mod tests { // use super::*; @@ -35,53 +47,52 @@ pub use pair::Pair; // } // } -// #[cfg(test)] -// mod tests { -// use super::*; +#[cfg(test)] +mod tests { + use super::*; -// #[test] -// fn sandbox() { -// let m1 = ""; -// let thing = DerefPair::new("Hi".to_string()); -// let d1 = thing.get_dependent(); -// let o1 = thing.get_owner(); -// let d2 = thing.get_dependent(); -// let d3 = thing.get_dependent(); -// println!("{d3}{m1}{d2}{o1}{d1}"); -// let s: String = thing.into_owner(); -// drop(s); + #[test] + fn sandbox() { + let thing = Pair::new_deref("Hi".to_string()); + let d1 = thing.with_dependent(|dep| dep); + let o1 = &thing.get_owner().0; + let d2 = thing.with_dependent(|dep| dep); + let d3 = thing.with_dependent(|dep| dep); + println!("{d3}{d2}{o1}{d1}"); + let s: String = thing.into_owner().0; + drop(s); -// // let thing = DerefPair::new(vec![1, 2, 3, 4]); -// // println!("{:?}", thing.get_dependent()); + // let thing = DerefPair::new(vec![1, 2, 3, 4]); + // println!("{:?}", thing.get_dependent()); -// // struct Foo; -// // struct Bar; -// // impl Drop for Foo { -// // fn drop(&mut self) { -// // println!("Dropping Foo"); -// // } -// // } -// // impl Drop for Bar { -// // fn drop(&mut self) { -// // println!("Dropping Bar"); -// // panic!(); -// // } -// // } + // struct Foo; + // struct Bar; + // impl Drop for Foo { + // fn drop(&mut self) { + // println!("Dropping Foo"); + // } + // } + // impl Drop for Bar { + // fn drop(&mut self) { + // println!("Dropping Bar"); + // panic!(); + // } + // } -// // impl Owner for Foo { -// // type Dependent<'a> -// // = Bar -// // where -// // Self: 'a; + // impl Owner for Foo { + // type Dependent<'a> + // = Bar + // where + // Self: 'a; -// // fn make_dependent(&self) -> Self::Dependent<'_> { -// // Bar -// // } -// // } + // fn make_dependent(&self) -> Self::Dependent<'_> { + // Bar + // } + // } -// // let pair = Pair::new(Foo); -// // pair.into_owner(); + // let pair = Pair::new(Foo); + // pair.into_owner(); -// // panic!(); -// } -// } + panic!(); + } +} diff --git a/src/owner.rs b/src/owner.rs index 42bc305..2dac88b 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -1,16 +1,24 @@ +// See: https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats +pub trait HasDependent<'a, ForImpliedBound: Sealed = Bounds<&'a Self>> { + type Dependent; +} +mod sealed { + pub trait Sealed {} + pub struct Bounds(std::marker::PhantomData); + impl Sealed for Bounds {} +} +use sealed::{Bounds, Sealed}; + /// A type which can act as the "owner" of some data, and can produce some /// dependent type which borrows from `Self`. /// /// This trait defines the "owner"/"dependent" relationship for use by the /// [`Pair`](crate::pair) struct, as well as the function used to create the /// dependent from a reference to the owner. -pub trait Owner<'owner> { - /// The dependent type, which borrows from the owner. - type Dependent; - +pub trait Owner: for<'any> HasDependent<'any> { /// Constructs the [`Dependent`](Owner::Dependent) from a reference to the /// owner. - fn make_dependent(&'owner self) -> Self::Dependent; + fn make_dependent(&self) -> >::Dependent; } // impl<'any> Owner<'any> for String { diff --git a/src/pair.rs b/src/pair.rs index 9d3cd81..705325a 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -12,17 +12,17 @@ use std::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; -use crate::Owner; +use crate::{owner::HasDependent, Owner}; // SAFETY: `Pair` has no special thread-related invariants or requirements, so // sending a `Pair` to another thread could only cause problems if sending // either the owner or the dependent to another thread could cause problems // (since both are semantically moved with and made accessible through the // `Pair`). -unsafe impl Owner<'any> + ?Sized> Send for Pair +unsafe impl Send for Pair where O: Send, - for<'any> >::Dependent: Send, + for<'any> >::Dependent: Send, { } @@ -31,10 +31,10 @@ where // problems if sharing a reference to either the owner or the dependent across // multiple threads could cause problems (since references to both are made // accessible through references to the `Pair`). -unsafe impl Owner<'any>> Sync for Pair +unsafe impl Sync for Pair where O: Sync, - for<'any> >::Dependent: Sync, + for<'any> >::Dependent: Sync, { } @@ -50,7 +50,7 @@ where /// Conceptually, the pair itself has ownership over the owner `O`, the owner is /// immutably borrowed by the dependent for the lifetime of the pair, and the /// dependent is owned by the pair and valid for the pair's lifetime. -pub struct Pair Owner<'any> + ?Sized> { +pub struct Pair { // Derived from a Box // Immutably borrowed by `self.dependent` from construction until drop owner: NonNull, @@ -83,7 +83,7 @@ fn non_null_from_box(value: Box) -> NonNull { NonNull::from(Box::leak(value)) } -impl Owner<'any> + ?Sized> Pair { +impl Pair { // TODO: expose a `try_new` and `try_new_from_box` API (will need updates to // the Owner trait) @@ -124,7 +124,8 @@ impl Owner<'any> + ?Sized> Pair { // Type-erase dependent so it's inexpressible self-referential lifetime // goes away (we know that it's borrowing self.owner immutably from // construction (now) until drop) - let dependent: NonNull = non_null_from_box(Box::new(dependent)); + let dependent: NonNull<>::Dependent> = + non_null_from_box(Box::new(dependent)); let dependent: NonNull<()> = dependent.cast(); Self { @@ -149,12 +150,16 @@ impl Owner<'any> + ?Sized> Pair { /// TODO // TODO: The compiler will allow us to elide this generic vvvv lifetime // what does it default to? This feels suspicious to me. vvvv - pub fn with_dependent<'a, F: for<'b> FnOnce(&'a >::Dependent) -> T, T>( + pub fn with_dependent<'a, F: for<'b> FnOnce(&'a >::Dependent) -> U, U>( &'a self, f: F, - ) -> T { + ) -> U { // SAFETY: TODO - let dependent = unsafe { self.dependent.cast::().as_ref() }; + let dependent = unsafe { + self.dependent + .cast::<>::Dependent>() + .as_ref() + }; f(dependent) } @@ -162,14 +167,18 @@ impl Owner<'any> + ?Sized> Pair { /// TODO pub fn with_dependent_mut< 'a, - F: for<'b> FnOnce(&'a mut >::Dependent) -> T, + F: for<'b> FnOnce(&'a mut >::Dependent) -> T, T, >( &'a mut self, f: F, ) -> T { // SAFETY: TODO - let dependent = unsafe { self.dependent.cast::().as_mut() }; + let dependent = unsafe { + self.dependent + .cast::<>::Dependent>() + .as_mut() + }; f(dependent) } @@ -194,7 +203,13 @@ impl Owner<'any> + ?Sized> Pair { // invalidated since then. Because we took ownership of `self`, we know // there are no outstanding borrows to the dependent. Therefore, // reconstructing the original Box> is okay. - drop(unsafe { Box::from_raw(this.dependent.cast::().as_ptr()) }); + drop(unsafe { + Box::from_raw( + this.dependent + .cast::<>::Dependent>() + .as_ptr(), + ) + }); // SAFETY: `this.owner` was originally created from a Box, and never // invalidated since then. Because we took ownership of `self`, and we @@ -219,7 +234,7 @@ impl Owner<'any> + ?Sized> Pair { /// The [`Drop`] implementation for [`Pair`] will drop both the dependent and /// the owner, in that order. -impl Owner<'any> + ?Sized> Drop for Pair { +impl Drop for Pair { fn drop(&mut self) { // Call `Drop::drop` on the dependent `O::Dependent<'_>` @@ -227,7 +242,13 @@ impl Owner<'any> + ?Sized> Drop for Pair { // invalidated since then. Because we are in drop, we know there are no // outstanding borrows to the dependent. Therefore, reconstructing the // original Box> is okay. - drop(unsafe { Box::from_raw(self.dependent.cast::().as_ptr()) }); + drop(unsafe { + Box::from_raw( + self.dependent + .cast::<>::Dependent>() + .as_ptr(), + ) + }); // Call `Drop::drop` on the owner `Box` From 2a5c581663df19b7e4571ae6bd5921687ce66f22 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 17 Feb 2025 21:16:21 -0500 Subject: [PATCH 014/110] refactored convenience pair wrappers --- src/as_ref.rs | 36 ------------------------------------ src/convenience/as_ref.rs | 24 ++++++++++++++++++++++++ src/convenience/borrow.rs | 24 ++++++++++++++++++++++++ src/convenience/deref.rs | 24 ++++++++++++++++++++++++ src/convenience/mod.rs | 7 +++++++ src/deref.rs | 27 --------------------------- src/lib.rs | 18 +++++++++--------- src/ref_owner.rs | 29 +++++++++++++++++++++++++++++ 8 files changed, 117 insertions(+), 72 deletions(-) delete mode 100644 src/as_ref.rs create mode 100644 src/convenience/as_ref.rs create mode 100644 src/convenience/borrow.rs create mode 100644 src/convenience/deref.rs create mode 100644 src/convenience/mod.rs delete mode 100644 src/deref.rs create mode 100644 src/ref_owner.rs diff --git a/src/as_ref.rs b/src/as_ref.rs deleted file mode 100644 index 98d926a..0000000 --- a/src/as_ref.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::{convert::AsRef, marker::PhantomData}; - -use crate::{HasDependent, Owner, Pair}; - -struct AsRefOwner(O, PhantomData); - -impl<'any, O: AsRef, D> HasDependent<'any> for AsRefOwner { - type Dependent = &'any D; -} - -impl, D> Owner for AsRefOwner { - fn make_dependent(&self) -> >::Dependent { - self.0.as_ref() - } -} - -pub struct AsRefPair, D>(Pair>); - -impl, D> AsRefPair { - pub fn new(owner: O) -> Self { - Self(Pair::new(AsRefOwner(owner, PhantomData))) - } - - pub fn get_owner(&self) -> &O { - &self.0.get_owner().0 - } - pub fn with_dependent<'a, F: for<'b> FnOnce(&'b D) -> U, U>(&'a self, f: F) -> U { - self.0.with_dependent(|dependent| f(dependent)) - } - pub fn get_dependent(&self) -> &D { - self.0.with_dependent(|dependent| dependent) - } - pub fn into_owner(self) -> O { - self.0.into_owner().0 - } -} diff --git a/src/convenience/as_ref.rs b/src/convenience/as_ref.rs new file mode 100644 index 0000000..ef6f3a2 --- /dev/null +++ b/src/convenience/as_ref.rs @@ -0,0 +1,24 @@ +use std::convert::AsRef; + +use crate::{ref_owner::RefOwner, Pair}; + +pub struct AsRefPair, D: ?Sized>(Pair>); + +impl, D: ?Sized> AsRefPair { + pub fn new(owner: O) -> Self { + Self(Pair::new(RefOwner::new(owner, |owner| owner.as_ref()))) + } + + pub fn get_owner(&self) -> &O { + self.0.get_owner().owner() + } + pub fn with_dependent<'a, F: for<'b> FnOnce(&'b D) -> T, T>(&'a self, f: F) -> T { + self.0.with_dependent(|dependent| f(dependent)) + } + pub fn get_dependent(&self) -> &D { + self.0.with_dependent(|dependent| dependent) + } + pub fn into_owner(self) -> O { + self.0.into_owner().into_owner() + } +} diff --git a/src/convenience/borrow.rs b/src/convenience/borrow.rs new file mode 100644 index 0000000..c522804 --- /dev/null +++ b/src/convenience/borrow.rs @@ -0,0 +1,24 @@ +use std::borrow::Borrow; + +use crate::{ref_owner::RefOwner, Pair}; + +pub struct BorrowPair, D: ?Sized>(Pair>); + +impl, D: ?Sized> BorrowPair { + pub fn new(owner: O) -> Self { + Self(Pair::new(RefOwner::new(owner, |owner| owner.borrow()))) + } + + pub fn get_owner(&self) -> &O { + self.0.get_owner().owner() + } + pub fn with_dependent<'a, F: for<'b> FnOnce(&'b D) -> T, T>(&'a self, f: F) -> T { + self.0.with_dependent(|dependent| f(dependent)) + } + pub fn get_dependent(&self) -> &D { + self.0.with_dependent(|dependent| dependent) + } + pub fn into_owner(self) -> O { + self.0.into_owner().into_owner() + } +} diff --git a/src/convenience/deref.rs b/src/convenience/deref.rs new file mode 100644 index 0000000..3095579 --- /dev/null +++ b/src/convenience/deref.rs @@ -0,0 +1,24 @@ +use std::ops::Deref; + +use crate::{ref_owner::RefOwner, Pair}; + +pub struct DerefPair(Pair>); + +impl DerefPair { + pub fn new(owner: O) -> Self { + Self(Pair::new(RefOwner::new(owner, |owner| owner))) + } + + pub fn get_owner(&self) -> &O { + self.0.get_owner().owner() + } + pub fn with_dependent<'a, F: for<'b> FnOnce(&'b O::Target) -> T, T>(&'a self, f: F) -> T { + self.0.with_dependent(|dependent| f(dependent)) + } + pub fn get_dependent(&self) -> &O::Target { + self.0.with_dependent(|dependent| dependent) + } + pub fn into_owner(self) -> O { + self.0.into_owner().into_owner() + } +} diff --git a/src/convenience/mod.rs b/src/convenience/mod.rs new file mode 100644 index 0000000..38c0eea --- /dev/null +++ b/src/convenience/mod.rs @@ -0,0 +1,7 @@ +mod as_ref; +mod borrow; +mod deref; + +pub use as_ref::AsRefPair; +pub use borrow::BorrowPair; +pub use deref::DerefPair; diff --git a/src/deref.rs b/src/deref.rs deleted file mode 100644 index f1b16c9..0000000 --- a/src/deref.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::ops::Deref; - -use crate::{HasDependent, Owner, Pair}; - -pub struct DerefOwner(pub O); - -impl<'any, O: Deref> HasDependent<'any> for DerefOwner { - type Dependent = &'any O::Target; -} -impl Owner for DerefOwner { - fn make_dependent(&self) -> >::Dependent { - &self.0 - } -} - -impl Pair> { - pub fn new_deref(owner: O) -> Self { - Self::new(DerefOwner(owner)) - } - - pub fn with_dependent_deref T, T>(&self, f: F) -> T { - self.with_dependent(|dependent| f(dependent)) - } - pub fn get_dependent_deref(&self) -> &O::Target { - self.with_dependent(|dependent| dependent) - } -} diff --git a/src/lib.rs b/src/lib.rs index 66953b4..c11bfe0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ -mod as_ref; -mod deref; +mod convenience; mod owner; mod pair; +mod ref_owner; -pub use as_ref::AsRefPair; +pub use convenience::{AsRefPair, BorrowPair, DerefPair}; pub use owner::{HasDependent, Owner}; pub use pair::Pair; @@ -53,13 +53,13 @@ mod tests { #[test] fn sandbox() { - let thing = Pair::new_deref("Hi".to_string()); - let d1 = thing.with_dependent(|dep| dep); - let o1 = &thing.get_owner().0; - let d2 = thing.with_dependent(|dep| dep); - let d3 = thing.with_dependent(|dep| dep); + let thing = DerefPair::new("Hi".to_string()); + let d1 = thing.get_dependent(); + let o1 = thing.get_owner(); + let d2 = thing.with_dependent(|dep| dep.to_string()); + let d3 = thing.get_dependent(); println!("{d3}{d2}{o1}{d1}"); - let s: String = thing.into_owner().0; + let s: String = thing.into_owner(); drop(s); // let thing = DerefPair::new(vec![1, 2, 3, 4]); diff --git a/src/ref_owner.rs b/src/ref_owner.rs new file mode 100644 index 0000000..c026dbf --- /dev/null +++ b/src/ref_owner.rs @@ -0,0 +1,29 @@ +use crate::{HasDependent, Owner}; + +pub(crate) struct RefOwner { + owner: O, + f: fn(&O) -> &D, +} + +impl RefOwner { + pub fn new(owner: O, f: fn(&O) -> &D) -> Self { + Self { owner, f } + } + + pub fn owner(&self) -> &O { + &self.owner + } + + pub fn into_owner(self) -> O { + self.owner + } +} + +impl<'any, O, D: ?Sized> HasDependent<'any> for RefOwner { + type Dependent = &'any D; +} +impl Owner for RefOwner { + fn make_dependent(&self) -> >::Dependent { + (self.f)(&self.owner) + } +} From 2fd917ee86c6d6a7ee0ee2aced1eea10afea10dc Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 17 Feb 2025 21:44:07 -0500 Subject: [PATCH 015/110] moved ref_owner to convenience --- src/convenience/as_ref.rs | 4 +++- src/convenience/borrow.rs | 4 +++- src/convenience/deref.rs | 4 +++- src/convenience/mod.rs | 2 ++ src/{ => convenience}/ref_owner.rs | 2 +- src/lib.rs | 1 - 6 files changed, 12 insertions(+), 5 deletions(-) rename src/{ => convenience}/ref_owner.rs (92%) diff --git a/src/convenience/as_ref.rs b/src/convenience/as_ref.rs index ef6f3a2..760d9a5 100644 --- a/src/convenience/as_ref.rs +++ b/src/convenience/as_ref.rs @@ -1,6 +1,8 @@ use std::convert::AsRef; -use crate::{ref_owner::RefOwner, Pair}; +use crate::Pair; + +use super::RefOwner; pub struct AsRefPair, D: ?Sized>(Pair>); diff --git a/src/convenience/borrow.rs b/src/convenience/borrow.rs index c522804..267e491 100644 --- a/src/convenience/borrow.rs +++ b/src/convenience/borrow.rs @@ -1,6 +1,8 @@ use std::borrow::Borrow; -use crate::{ref_owner::RefOwner, Pair}; +use crate::Pair; + +use super::RefOwner; pub struct BorrowPair, D: ?Sized>(Pair>); diff --git a/src/convenience/deref.rs b/src/convenience/deref.rs index 3095579..ad5c4dd 100644 --- a/src/convenience/deref.rs +++ b/src/convenience/deref.rs @@ -1,6 +1,8 @@ use std::ops::Deref; -use crate::{ref_owner::RefOwner, Pair}; +use crate::Pair; + +use super::RefOwner; pub struct DerefPair(Pair>); diff --git a/src/convenience/mod.rs b/src/convenience/mod.rs index 38c0eea..5420037 100644 --- a/src/convenience/mod.rs +++ b/src/convenience/mod.rs @@ -1,7 +1,9 @@ mod as_ref; mod borrow; mod deref; +mod ref_owner; pub use as_ref::AsRefPair; pub use borrow::BorrowPair; pub use deref::DerefPair; +pub use ref_owner::RefOwner; diff --git a/src/ref_owner.rs b/src/convenience/ref_owner.rs similarity index 92% rename from src/ref_owner.rs rename to src/convenience/ref_owner.rs index c026dbf..73ec54d 100644 --- a/src/ref_owner.rs +++ b/src/convenience/ref_owner.rs @@ -1,6 +1,6 @@ use crate::{HasDependent, Owner}; -pub(crate) struct RefOwner { +pub struct RefOwner { owner: O, f: fn(&O) -> &D, } diff --git a/src/lib.rs b/src/lib.rs index c11bfe0..da72307 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ mod convenience; mod owner; mod pair; -mod ref_owner; pub use convenience::{AsRefPair, BorrowPair, DerefPair}; pub use owner::{HasDependent, Owner}; From cdcb851e55087265e7fe35ebd7625aa311b0fdaf Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 19 Feb 2025 17:36:39 -0500 Subject: [PATCH 016/110] updated Owner trait, improved documentation --- src/convenience/ref_owner.rs | 11 +- src/lib.rs | 2 +- src/owner.rs | 89 +++++++---- src/pair.rs | 299 ++++++++++++++++++++++++++++------- 4 files changed, 311 insertions(+), 90 deletions(-) diff --git a/src/convenience/ref_owner.rs b/src/convenience/ref_owner.rs index 73ec54d..44010a7 100644 --- a/src/convenience/ref_owner.rs +++ b/src/convenience/ref_owner.rs @@ -23,7 +23,14 @@ impl<'any, O, D: ?Sized> HasDependent<'any> for RefOwner { type Dependent = &'any D; } impl Owner for RefOwner { - fn make_dependent(&self) -> >::Dependent { - (self.f)(&self.owner) + type Context = (); + + type Err = std::convert::Infallible; + + fn make_dependent( + &self, + (): Self::Context, + ) -> Result<>::Dependent, Self::Err> { + Ok((self.f)(&self.owner)) } } diff --git a/src/lib.rs b/src/lib.rs index da72307..5c41335 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,6 @@ mod tests { // let pair = Pair::new(Foo); // pair.into_owner(); - panic!(); + // panic!(); } } diff --git a/src/owner.rs b/src/owner.rs index 2dac88b..6d33529 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -1,44 +1,69 @@ -// See: https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats -pub trait HasDependent<'a, ForImpliedBound: Sealed = Bounds<&'a Self>> { - type Dependent; -} +/// Used to prevent implementors of [`HasDependent`] from overriding the +/// `ForImpliedBounds` generic type from its default. mod sealed { + /// The `ForImpliedBounds` generic type for + /// [`HasDependent`](super::HasDependent) should not be overridden from its + /// default. pub trait Sealed {} + /// The `ForImpliedBounds` generic type for + /// [`HasDependent`](super::HasDependent) should not be overridden from its + /// default. pub struct Bounds(std::marker::PhantomData); impl Sealed for Bounds {} } use sealed::{Bounds, Sealed}; -/// A type which can act as the "owner" of some data, and can produce some -/// dependent type which borrows from `Self`. +/// Defines the dependent type for the [`Owner`] trait. /// -/// This trait defines the "owner"/"dependent" relationship for use by the -/// [`Pair`](crate::pair) struct, as well as the function used to create the -/// dependent from a reference to the owner. -pub trait Owner: for<'any> HasDependent<'any> { - /// Constructs the [`Dependent`](Owner::Dependent) from a reference to the - /// owner. - fn make_dependent(&self) -> >::Dependent; +/// Semantically, you can think of this like a lifetime Generic Associated Type +/// (GAT) in the `Owner` trait - the two behave very similarly, and serve the +/// same role in defining a [`Dependent`](HasDependent::Dependent) type, generic +/// over some lifetime. +/// +/// A real GAT is not used due to limitations in the current Rust compiler. For +/// the technical details on this, I recommend Sabrina Jewson's blog post on +/// [The Better Alternative to Lifetime GATs]. +/// +/// [The Better Alternative to Lifetime GATs]: https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats +pub trait HasDependent<'owner, ForImpliedBound: Sealed = Bounds<&'owner Self>> { + /// The dependent type, borrowing from an owner. This type is what is + /// returned from [`Owner::make_dependent`]. + type Dependent; } -// impl<'any> Owner<'any> for String { -// type Dependent = &'any str; - -// fn make_dependent(&'any self) -> Self::Dependent { -// self -// } -// } -// impl<'any, T> Owner<'any> for Vec { -// type Dependent = &'any [T]; +/// A type which can act as the "owner" of some data, and can produce some +/// dependent type which borrows from `Self`. Used for the [`Pair`](crate::pair) +/// struct. +/// +/// The supertrait [`HasDependent<'_>`] defines the dependent type, acting as a +/// sort of generic associated type - see its documentation for more +/// information. The [`make_dependent`](Owner::make_dependent) function defines +/// how to create a dependent from a reference to an owner. +pub trait Owner: for<'any> HasDependent<'any> { + /// Additional context provided to [`make_dependent`](Owner::make_dependent) + /// as an argument. + /// + /// If additional context is not necessary, this should be set to `()`. + // + // TODO(ichen): default this to () when associated type defaults are + // stabilized (https://github.com/rust-lang/rust/issues/29661) + type Context; -// fn make_dependent(&'any self) -> Self::Dependent { -// self -// } -// } -// impl<'any, T: std::ops::Deref> Owner<'any> for T { -// type Dependent = &'any T::Target; + /// The error type returned by [`make_dependent`](Owner::make_dependent) in + /// the event of an error. + /// + /// If `make_dependent` can't fail, this should be set to + /// [`Infallible`](std::convert::Infallible). + // + // TODO(ichen): default this to std::convert::Infallible (or preferably !) + // when associated type defaults are stabilized + // (https://github.com/rust-lang/rust/issues/29661) + type Err; -// fn make_dependent(&'any self) -> Self::Dependent { -// self -// } -// } + /// Attempts to construct a [`Dependent`](HasDependent::Dependent) from a + /// reference to an owner and some context. + fn make_dependent( + &self, + context: Self::Context, + ) -> Result<>::Dependent, Self::Err>; +} diff --git a/src/pair.rs b/src/pair.rs index 705325a..ccdce3e 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -1,18 +1,6 @@ -// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -// # # -// # !!! WARNING !!! # -// # # -// # SAFETY COMMENTS WERE WRITTEN BEFORE SUBSTANTIAL CHANGES # -// # TO THE `Owner` TRAIT WERE MADE, AND MAY BE INCORRECT! # -// # # -// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -// p.s. I hope u like my cool ASCII box <3 +use std::{convert::Infallible, fmt::Debug, marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; -// TODO: all comments are potentially out of date due to Owner trait updates. - -use std::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; - -use crate::{owner::HasDependent, Owner}; +use crate::{HasDependent, Owner}; // SAFETY: `Pair` has no special thread-related invariants or requirements, so // sending a `Pair` to another thread could only cause problems if sending @@ -39,7 +27,7 @@ where } /// A self-referential pair containing both some [`Owner`] and its -/// [`Dependent`](Owner::Dependent). +/// [`Dependent`](HasDependent::Dependent). /// /// The owner must be provided to construct a [`Pair`], and the dependent is /// immediately created (borrowing from the owner). Both are stored together in @@ -55,12 +43,12 @@ pub struct Pair { // Immutably borrowed by `self.dependent` from construction until drop owner: NonNull, - // Type-erased Box> + // Type-erased Box<>::Dependent> dependent: NonNull<()>, // Need invariance over O - if we were covariant or contravariant, two // different `O`s with two different `Owner` impls (and importantly, two - // different Dependent associated types) which have a subtype/supertype + // different associated types in HasDependent) which have a sub/supertype // relationship could be incorrectly treated as substitutable in a Pair. // That would lead to effectively an unchecked transmute of each field, // which would obviously be unsound. @@ -77,35 +65,48 @@ pub struct Pair { /// - The returned NonNull will be suitably aligned for T /// - The returned NonNull will point to a valid T /// - The returned NonNull was allocated with the [`Global`](std::alloc::Global) -/// allocator and a valid [`Layout`](std::alloc::Layout) for `T`. +/// allocator and a valid [`Layout`](std::alloc::Layout) for `T`. fn non_null_from_box(value: Box) -> NonNull { // See: https://github.com/rust-lang/rust/issues/47336#issuecomment-586578713 NonNull::from(Box::leak(value)) } impl Pair { - // TODO: expose a `try_new` and `try_new_from_box` API (will need updates to - // the Owner trait) - /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// - /// If you already have a [`Box`]ed owner, consider [`Pair::new_from_box`] - /// to avoid redundant reallocation. - pub fn new(owner: O) -> Self + /// If you already have a [`Box`]ed owner, consider + /// [`Pair::try_new_from_box_with_context`] to avoid redundant reallocation. + /// + /// If you don't need to provide any context, consider the convenience + /// constructor [`Pair::try_new`], which doesn't require a context. + /// + /// If this construction can't fail, consider the convenience constructor + /// [`Pair::new_with_context`], which returns `Self` directly. + pub fn try_new_with_context(owner: O, context: O::Context) -> Result where O: Sized, { - Self::new_from_box(Box::new(owner)) + Self::try_new_from_box_with_context(Box::new(owner), context) + .map_err(|(owner, err)| (*owner, err)) } /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// /// If you have an unboxed `O` and only box it for this function, consider - /// the convenience constructor [`Pair::new`], which boxes the owner for - /// you to reduce clutter in your code. - pub fn new_from_box(owner: Box) -> Self { + /// the convenience constructor [`Pair::try_new_with_context`], which boxes + /// the owner for you. + /// + /// If you don't need to provide any context, consider the convenience + /// constructor [`Pair::try_new_from_box`], which doesn't require a context. + /// + /// If this construction can't fail, consider the convenience constructor + /// [`Pair::new_from_box_with_context`], which returns `Self` directly. + pub fn try_new_from_box_with_context( + owner: Box, + context: O::Context, + ) -> Result, O::Err)> { // Convert owner into a NonNull, so we are no longer restricted by the // aliasing requirements of Box let owner = non_null_from_box(owner); @@ -118,8 +119,25 @@ impl Pair { // alignment and validity guarantees of Box. Additionally, the value // behind the pointer is currently not borrowed at all - this marks the // beginning of a shared borrow which will last until the returned - // `Pair` is dropped. - let dependent = unsafe { owner.as_ref() }.make_dependent(); + // `Pair` is dropped (or immediately, if `make_dependent` returns an + // error). + let maybe_dependent = unsafe { owner.as_ref() }.make_dependent(context); + + // If `make_dependent(..)` failed, early return out from this function. + let dependent = match maybe_dependent { + Ok(dependent) => dependent, + Err(err) => { + // SAFETY: `owner` was just created from a Box earlier in this + // function, and not invalidated since then. Because we haven't + // given away access to a `Self`, and the one borrow we took of + // the dependent to pass to `make_dependent` has expired, we + // know there are no outstanding borrows to owner. Therefore, + // reconstructing the original Box is okay. + let owner: Box = unsafe { Box::from_raw(owner.as_ptr()) }; + + return Err((owner, err)); + } + }; // Type-erase dependent so it's inexpressible self-referential lifetime // goes away (we know that it's borrowing self.owner immutably from @@ -128,11 +146,11 @@ impl Pair { non_null_from_box(Box::new(dependent)); let dependent: NonNull<()> = dependent.cast(); - Self { + Ok(Self { owner, dependent, prevent_covariance: PhantomData, - } + }) } /// Returns a reference to the owner. @@ -147,15 +165,37 @@ impl Pair { unsafe { self.owner.as_ref() } } - /// TODO - // TODO: The compiler will allow us to elide this generic vvvv lifetime - // what does it default to? This feels suspicious to me. vvvv - pub fn with_dependent<'a, F: for<'b> FnOnce(&'a >::Dependent) -> U, U>( - &'a self, + /// Calls the given closure, providing shared access to the dependent, and + /// returns the value computed by the closure. + /// + /// The closure must be able to work with a + /// [`Dependent`](HasDependent::Dependent) with any arbitrary lifetime that + /// lives at least as long as the borrow of `self`. This is important + /// because the dependent may be invariant over its lifetime, and the + /// correct lifetime (lasting from the construction of `self` until drop) is + /// impossible to express. For dependent types covariant over their + /// lifetime, the closure may simply return the reference to the dependent, + /// which may then be used as if this function directly returned a + /// reference. + pub fn with_dependent< + 'self_borrow, + F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T, + T, + >( + &'self_borrow self, f: F, - ) -> U { - // SAFETY: TODO - let dependent = unsafe { + ) -> T { + // SAFETY: `self.dependent` was originally converted from a valid + // Box<>::Dependent>, and type-erased to a + // NonNull<()>. As such, it inherited the alignment and validity + // guarantees of Box (for an >::Dependent) - and + // neither our code nor any of our exposed APIs could have invalidated + // those since construction. Additionally, because we have a shared + // reference to self, we know that the value behind the pointer is + // currently either not borrowed at all, or in a shared borrow state + // (no exclusive borrows, no other code assuming unique ownership). + // Here, we only either create the first shared borrow, or add another. + let dependent: &>::Dependent = unsafe { self.dependent .cast::<>::Dependent>() .as_ref() @@ -164,17 +204,33 @@ impl Pair { f(dependent) } - /// TODO + /// Calls the given closure, providing exclusive access to the dependent, + /// and returns the value computed by the closure. + /// + /// The closure must be able to work with a + /// [`Dependent`](HasDependent::Dependent) with any arbitrary lifetime that + /// lives at least as long as the borrow of `self`. This is important + /// because mutable references are invariant over their type `T`, and the + /// exact T here (a `Dependent` with a very specific lifetime lasting from + /// the construction of `self` until drop) is impossible to express. pub fn with_dependent_mut< - 'a, - F: for<'b> FnOnce(&'a mut >::Dependent) -> T, + 'self_borrow, + F: for<'any> FnOnce(&'self_borrow mut >::Dependent) -> T, T, >( - &'a mut self, + &'self_borrow mut self, f: F, ) -> T { - // SAFETY: TODO - let dependent = unsafe { + // SAFETY: `self.dependent` was originally converted from a valid + // Box<>::Dependent>, and type-erased to a + // NonNull<()>. As such, it inherited the alignment and validity + // guarantees of Box (for an >::Dependent) - and + // neither our code nor any of our exposed APIs could have invalidated + // those since construction. Additionally, because we have an exclusive + // reference to self, we know that the value behind the pointer is + // currently not borrowed at all, and can't be until the mutable borrow + // of `self` expires. + let dependent: &mut >::Dependent = unsafe { self.dependent .cast::<>::Dependent>() .as_mut() @@ -187,11 +243,11 @@ impl Pair { /// /// If you don't need the returned owner in a [`Box`], consider the /// convenience method [`Pair::into_owner`], which moves the owner out of - /// the box for you to reduce clutter in your code. + /// the box for you. pub fn into_boxed_owner(self) -> Box { // Prevent dropping `self` at the end of this scope - otherwise, the // Pair drop implementation would attempt to drop the owner and - // dependent, which would be... not good (unsound). + // dependent again, which would be... not good (unsound). // // It's important that we do this before calling the dependent's drop, // since a panic in that drop would otherwise cause at least a double @@ -202,7 +258,8 @@ impl Pair { // SAFETY: `this.dependent` was originally created from a Box, and never // invalidated since then. Because we took ownership of `self`, we know // there are no outstanding borrows to the dependent. Therefore, - // reconstructing the original Box> is okay. + // reconstructing the original Box<>::Dependent> + // is okay. drop(unsafe { Box::from_raw( this.dependent @@ -215,9 +272,7 @@ impl Pair { // invalidated since then. Because we took ownership of `self`, and we // just dropped the dependent, we know there are no outstanding borrows // to owner. Therefore, reconstructing the original Box is okay. - let boxed_owner = unsafe { Box::from_raw(this.owner.as_ptr()) }; - - boxed_owner + unsafe { Box::from_raw(this.owner.as_ptr()) } } /// Consumes the [`Pair`], dropping the dependent and returning the owner. @@ -232,16 +287,130 @@ impl Pair { } } +impl + ?Sized> Pair { + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will + /// be computed through [`Owner::make_dependent`] during this construction. + /// + /// If you already have a [`Box`]ed owner, consider [`Pair::new_from_box`] + /// to avoid redundant reallocation. + /// + /// If you need to provide some additional arguments/context to this + /// constructor, consider [`Pair::new_with_context`], which allows passing + /// in additional data. + /// + /// If this construction can fail, consider [`Pair::try_new`], which returns + /// a [`Result`]. + pub fn new(owner: O) -> Self + where + O: Sized, + { + Self::new_with_context(owner, ()) + } + + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will + /// be computed through [`Owner::make_dependent`] during this construction. + /// + /// If you have an unboxed `O` and only box it for this function, consider + /// the convenience constructor [`Pair::new`], which boxes the owner for + /// you. + /// + /// If you need to provide some additional arguments/context to this + /// constructor, consider [`Pair::new_from_box_with_context`], which allows + /// passing in additional data. + /// + /// If this construction can fail, consider [`Pair::try_new_from_box`], + /// which returns a [`Result`]. + pub fn new_from_box(owner: Box) -> Self { + Self::new_from_box_with_context(owner, ()) + } +} + +impl + ?Sized> Pair { + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will + /// be computed through [`Owner::make_dependent`] during this construction. + /// + /// If you already have a [`Box`]ed owner, consider + /// [`Pair::try_new_from_box`] to avoid redundant reallocation. + /// + /// If you need to provide some additional arguments/context to this + /// constructor, consider [`Pair::try_new_with_context`], which allows + /// passing in additional data. + /// + /// If this construction can't fail, consider the convenience constructor + /// [`Pair::new`], which returns `Self` directly. + pub fn try_new(owner: O) -> Result + where + O: Sized, + { + Self::try_new_with_context(owner, ()) + } + + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will + /// be computed through [`Owner::make_dependent`] during this construction. + /// + /// If you have an unboxed `O` and only box it for this function, consider + /// the convenience constructor [`Pair::try_new`], which boxes the owner for + /// you. + /// + /// If you need to provide some additional arguments/context to this + /// constructor, consider [`Pair::try_new_from_box_with_context`], which + /// allows passing in additional data. + /// + /// If this construction can't fail, consider the convenience constructor + /// [`Pair::new_from_box`], which returns `Self` directly. + pub fn try_new_from_box(owner: Box) -> Result, O::Err)> { + Self::try_new_from_box_with_context(owner, ()) + } +} + +impl + ?Sized> Pair { + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will + /// be computed through [`Owner::make_dependent`] during this construction. + /// + /// If you already have a [`Box`]ed owner, consider + /// [`Pair::new_from_box_with_context`] to avoid redundant reallocation. + /// + /// If you don't need to provide any context, consider the convenience + /// constructor [`Pair::new`], which doesn't require a context. + /// + /// If this construction can fail, consider [`Pair::try_new_with_context`], + /// which returns a [`Result`]. + pub fn new_with_context(owner: O, context: O::Context) -> Self + where + O: Sized, + { + let Ok(pair) = Self::try_new_with_context(owner, context); + pair + } + + /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will + /// be computed through [`Owner::make_dependent`] during this construction. + /// + /// If you have an unboxed `O` and only box it for this function, consider + /// the convenience constructor [`Pair::new_with_context`], which boxes the + /// owner for you. + /// + /// If you don't need to provide any context, consider the convenience + /// constructor [`Pair::new_from_box`], which doesn't require a context. + /// + /// If this construction can fail, consider + /// [`Pair::try_new_from_box_with_context`], which returns a [`Result`]. + pub fn new_from_box_with_context(owner: Box, context: O::Context) -> Self { + let Ok(pair) = Self::try_new_from_box_with_context(owner, context); + pair + } +} + /// The [`Drop`] implementation for [`Pair`] will drop both the dependent and /// the owner, in that order. impl Drop for Pair { fn drop(&mut self) { - // Call `Drop::drop` on the dependent `O::Dependent<'_>` + // Drop the dependent `Box<>::Dependent>` // SAFETY: `self.dependent` was originally created from a Box, and never // invalidated since then. Because we are in drop, we know there are no // outstanding borrows to the dependent. Therefore, reconstructing the - // original Box> is okay. + // original Box<>::Dependent> is okay. drop(unsafe { Box::from_raw( self.dependent @@ -250,7 +419,7 @@ impl Drop for Pair { ) }); - // Call `Drop::drop` on the owner `Box` + // Drop the owner `Box` // SAFETY: `this.owner` was originally created from a Box, and never // invalidated since then. Because we are in drop, and we just dropped @@ -259,3 +428,23 @@ impl Drop for Pair { drop(unsafe { Box::from_raw(self.owner.as_ptr()) }); } } + +impl Debug for Pair +where + for<'any> >::Dependent: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with_dependent(|dependent| { + f.debug_struct("Pair") + .field("owner", &self.get_owner()) + .field("dependent", dependent) + .finish() + }) + } +} + +impl + Default> Default for Pair { + fn default() -> Self { + Self::new(O::default()) + } +} From dc726405d804b4adcdc04bf7f26697349dfcca32 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 19 Feb 2025 17:41:17 -0500 Subject: [PATCH 017/110] updated convenience wrapper types slightly --- src/convenience/as_ref.rs | 15 ++++++--------- src/convenience/borrow.rs | 15 ++++++--------- src/convenience/deref.rs | 15 ++++++--------- src/lib.rs | 2 +- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/convenience/as_ref.rs b/src/convenience/as_ref.rs index 760d9a5..a0fc15a 100644 --- a/src/convenience/as_ref.rs +++ b/src/convenience/as_ref.rs @@ -4,23 +4,20 @@ use crate::Pair; use super::RefOwner; -pub struct AsRefPair, D: ?Sized>(Pair>); +pub struct AsRefPair, U: ?Sized>(Pair>); -impl, D: ?Sized> AsRefPair { - pub fn new(owner: O) -> Self { +impl, U: ?Sized> AsRefPair { + pub fn new(owner: T) -> Self { Self(Pair::new(RefOwner::new(owner, |owner| owner.as_ref()))) } - pub fn get_owner(&self) -> &O { + pub fn get_owner(&self) -> &T { self.0.get_owner().owner() } - pub fn with_dependent<'a, F: for<'b> FnOnce(&'b D) -> T, T>(&'a self, f: F) -> T { - self.0.with_dependent(|dependent| f(dependent)) - } - pub fn get_dependent(&self) -> &D { + pub fn get_dependent(&self) -> &U { self.0.with_dependent(|dependent| dependent) } - pub fn into_owner(self) -> O { + pub fn into_owner(self) -> T { self.0.into_owner().into_owner() } } diff --git a/src/convenience/borrow.rs b/src/convenience/borrow.rs index 267e491..f3cdba5 100644 --- a/src/convenience/borrow.rs +++ b/src/convenience/borrow.rs @@ -4,23 +4,20 @@ use crate::Pair; use super::RefOwner; -pub struct BorrowPair, D: ?Sized>(Pair>); +pub struct BorrowPair, U: ?Sized>(Pair>); -impl, D: ?Sized> BorrowPair { - pub fn new(owner: O) -> Self { +impl, U: ?Sized> BorrowPair { + pub fn new(owner: T) -> Self { Self(Pair::new(RefOwner::new(owner, |owner| owner.borrow()))) } - pub fn get_owner(&self) -> &O { + pub fn get_owner(&self) -> &T { self.0.get_owner().owner() } - pub fn with_dependent<'a, F: for<'b> FnOnce(&'b D) -> T, T>(&'a self, f: F) -> T { - self.0.with_dependent(|dependent| f(dependent)) - } - pub fn get_dependent(&self) -> &D { + pub fn get_dependent(&self) -> &U { self.0.with_dependent(|dependent| dependent) } - pub fn into_owner(self) -> O { + pub fn into_owner(self) -> T { self.0.into_owner().into_owner() } } diff --git a/src/convenience/deref.rs b/src/convenience/deref.rs index ad5c4dd..41ccebf 100644 --- a/src/convenience/deref.rs +++ b/src/convenience/deref.rs @@ -4,23 +4,20 @@ use crate::Pair; use super::RefOwner; -pub struct DerefPair(Pair>); +pub struct DerefPair(Pair>); -impl DerefPair { - pub fn new(owner: O) -> Self { +impl DerefPair { + pub fn new(owner: T) -> Self { Self(Pair::new(RefOwner::new(owner, |owner| owner))) } - pub fn get_owner(&self) -> &O { + pub fn get_owner(&self) -> &T { self.0.get_owner().owner() } - pub fn with_dependent<'a, F: for<'b> FnOnce(&'b O::Target) -> T, T>(&'a self, f: F) -> T { - self.0.with_dependent(|dependent| f(dependent)) - } - pub fn get_dependent(&self) -> &O::Target { + pub fn get_dependent(&self) -> &T::Target { self.0.with_dependent(|dependent| dependent) } - pub fn into_owner(self) -> O { + pub fn into_owner(self) -> T { self.0.into_owner().into_owner() } } diff --git a/src/lib.rs b/src/lib.rs index 5c41335..6e1157b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ mod tests { let thing = DerefPair::new("Hi".to_string()); let d1 = thing.get_dependent(); let o1 = thing.get_owner(); - let d2 = thing.with_dependent(|dep| dep.to_string()); + let d2 = thing.get_dependent().to_string(); let d3 = thing.get_dependent(); println!("{d3}{d2}{o1}{d1}"); let s: String = thing.into_owner(); From d203d4eeb7155b35fb767e12bb7f85fce5e0e82d Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 19 Feb 2025 17:43:56 -0500 Subject: [PATCH 018/110] cut convenience wrappers (useless - just use the owning type) --- src/convenience/as_ref.rs | 23 ----------------------- src/convenience/borrow.rs | 23 ----------------------- src/convenience/deref.rs | 23 ----------------------- src/convenience/mod.rs | 9 --------- src/convenience/ref_owner.rs | 36 ------------------------------------ src/lib.rs | 2 -- 6 files changed, 116 deletions(-) delete mode 100644 src/convenience/as_ref.rs delete mode 100644 src/convenience/borrow.rs delete mode 100644 src/convenience/deref.rs delete mode 100644 src/convenience/mod.rs delete mode 100644 src/convenience/ref_owner.rs diff --git a/src/convenience/as_ref.rs b/src/convenience/as_ref.rs deleted file mode 100644 index a0fc15a..0000000 --- a/src/convenience/as_ref.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::convert::AsRef; - -use crate::Pair; - -use super::RefOwner; - -pub struct AsRefPair, U: ?Sized>(Pair>); - -impl, U: ?Sized> AsRefPair { - pub fn new(owner: T) -> Self { - Self(Pair::new(RefOwner::new(owner, |owner| owner.as_ref()))) - } - - pub fn get_owner(&self) -> &T { - self.0.get_owner().owner() - } - pub fn get_dependent(&self) -> &U { - self.0.with_dependent(|dependent| dependent) - } - pub fn into_owner(self) -> T { - self.0.into_owner().into_owner() - } -} diff --git a/src/convenience/borrow.rs b/src/convenience/borrow.rs deleted file mode 100644 index f3cdba5..0000000 --- a/src/convenience/borrow.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::borrow::Borrow; - -use crate::Pair; - -use super::RefOwner; - -pub struct BorrowPair, U: ?Sized>(Pair>); - -impl, U: ?Sized> BorrowPair { - pub fn new(owner: T) -> Self { - Self(Pair::new(RefOwner::new(owner, |owner| owner.borrow()))) - } - - pub fn get_owner(&self) -> &T { - self.0.get_owner().owner() - } - pub fn get_dependent(&self) -> &U { - self.0.with_dependent(|dependent| dependent) - } - pub fn into_owner(self) -> T { - self.0.into_owner().into_owner() - } -} diff --git a/src/convenience/deref.rs b/src/convenience/deref.rs deleted file mode 100644 index 41ccebf..0000000 --- a/src/convenience/deref.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::ops::Deref; - -use crate::Pair; - -use super::RefOwner; - -pub struct DerefPair(Pair>); - -impl DerefPair { - pub fn new(owner: T) -> Self { - Self(Pair::new(RefOwner::new(owner, |owner| owner))) - } - - pub fn get_owner(&self) -> &T { - self.0.get_owner().owner() - } - pub fn get_dependent(&self) -> &T::Target { - self.0.with_dependent(|dependent| dependent) - } - pub fn into_owner(self) -> T { - self.0.into_owner().into_owner() - } -} diff --git a/src/convenience/mod.rs b/src/convenience/mod.rs deleted file mode 100644 index 5420037..0000000 --- a/src/convenience/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod as_ref; -mod borrow; -mod deref; -mod ref_owner; - -pub use as_ref::AsRefPair; -pub use borrow::BorrowPair; -pub use deref::DerefPair; -pub use ref_owner::RefOwner; diff --git a/src/convenience/ref_owner.rs b/src/convenience/ref_owner.rs deleted file mode 100644 index 44010a7..0000000 --- a/src/convenience/ref_owner.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::{HasDependent, Owner}; - -pub struct RefOwner { - owner: O, - f: fn(&O) -> &D, -} - -impl RefOwner { - pub fn new(owner: O, f: fn(&O) -> &D) -> Self { - Self { owner, f } - } - - pub fn owner(&self) -> &O { - &self.owner - } - - pub fn into_owner(self) -> O { - self.owner - } -} - -impl<'any, O, D: ?Sized> HasDependent<'any> for RefOwner { - type Dependent = &'any D; -} -impl Owner for RefOwner { - type Context = (); - - type Err = std::convert::Infallible; - - fn make_dependent( - &self, - (): Self::Context, - ) -> Result<>::Dependent, Self::Err> { - Ok((self.f)(&self.owner)) - } -} diff --git a/src/lib.rs b/src/lib.rs index 6e1157b..3b207ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,6 @@ -mod convenience; mod owner; mod pair; -pub use convenience::{AsRefPair, BorrowPair, DerefPair}; pub use owner::{HasDependent, Owner}; pub use pair::Pair; From 807534605fdb47d24cfd8ee2da66db5985103ba6 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 19 Feb 2025 17:48:48 -0500 Subject: [PATCH 019/110] cleaned up comments in lib.rs --- src/lib.rs | 103 ++++++++--------------------------------------------- 1 file changed, 14 insertions(+), 89 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3b207ac..0721fc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,92 +4,17 @@ mod pair; pub use owner::{HasDependent, Owner}; pub use pair::Pair; -// TODO: *extensive* testing, including: -// - Property-based testing -// - Fuzzing (possible? I kinda want "type fuzzing" which seems... hard) -// - Test against weird cases like contravariant types, "Oisann" types, weird -// Drop impls, impure Deref impls, etc. -// - https://docs.rs/trybuild test cases demonstrating that misuses of your API don't compile -// - All under MIRI - -// impl<'any> ForLt<'any> for String { -// type Dependent = &'any str; -// } -// impl Owner for String { -// fn make_dependent(&self) -> >::Dependent { -// self -// } -// } - -// #[cfg(test)] -// mod tests { -// use super::*; - -// #[test] -// fn my_test() { -// let m1 = ""; -// let thing = Pair::new_deref("Hi".to_string()); -// let d1 = thing.get_dependent(); -// let o1 = thing.get_owner_deref(); -// let d2 = thing.get_dependent(); -// let d3 = thing.get_dependent(); -// println!("{d3}{m1}{d2}{o1}{d1}"); -// let s: String = thing.into_owner_deref(); -// drop(s); - -// let thing = Pair::new_deref(vec![1, 2, 3, 4]); -// println!("{:?}", thing.get_dependent()); - -// // panic!(); -// } -// } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sandbox() { - let thing = DerefPair::new("Hi".to_string()); - let d1 = thing.get_dependent(); - let o1 = thing.get_owner(); - let d2 = thing.get_dependent().to_string(); - let d3 = thing.get_dependent(); - println!("{d3}{d2}{o1}{d1}"); - let s: String = thing.into_owner(); - drop(s); - - // let thing = DerefPair::new(vec![1, 2, 3, 4]); - // println!("{:?}", thing.get_dependent()); - - // struct Foo; - // struct Bar; - // impl Drop for Foo { - // fn drop(&mut self) { - // println!("Dropping Foo"); - // } - // } - // impl Drop for Bar { - // fn drop(&mut self) { - // println!("Dropping Bar"); - // panic!(); - // } - // } - - // impl Owner for Foo { - // type Dependent<'a> - // = Bar - // where - // Self: 'a; - - // fn make_dependent(&self) -> Self::Dependent<'_> { - // Bar - // } - // } - - // let pair = Pair::new(Foo); - // pair.into_owner(); - - // panic!(); - } -} +// TODO: +// - Extensive testing, including: +// - Property-based testing +// - Some kind of "type level fuzzing"?? +// - Test against known weird cases, like: +// - Types with all different kinds of variances +// - Weird drop impls (including "Oisann" types (jonhoo reference)) +// - Impure / weird Deref impls +// - Interior mutable types +// - https://docs.rs/trybuild test cases demonstrating that misuses of the +// API don't compile +// - All under MIRI +// - Soundness audits by experienced dark arts rustaceans +// - Crate/module level documentation From 10ed36a1e2fb175a271049930a845207e4e8f63b Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 20 Feb 2025 19:16:45 -0500 Subject: [PATCH 020/110] added panic handling when calling user-provided code --- Cargo.toml | 2 +- src/pair.rs | 181 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 131 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9f5efe5..452d0ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/ijchen/pair" documentation = "https://docs.rs/pair" -edition = "2021" +edition = "2024" include = ["/src/", "/Cargo.toml", "/README.md", "/LICENSE-*"] publish = false diff --git a/src/pair.rs b/src/pair.rs index ccdce3e..ccce179 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -1,31 +1,10 @@ -use std::{convert::Infallible, fmt::Debug, marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; +use std::{ + convert::Infallible, fmt::Debug, marker::PhantomData, mem::ManuallyDrop, + panic::AssertUnwindSafe, ptr::NonNull, +}; use crate::{HasDependent, Owner}; -// SAFETY: `Pair` has no special thread-related invariants or requirements, so -// sending a `Pair` to another thread could only cause problems if sending -// either the owner or the dependent to another thread could cause problems -// (since both are semantically moved with and made accessible through the -// `Pair`). -unsafe impl Send for Pair -where - O: Send, - for<'any> >::Dependent: Send, -{ -} - -// SAFETY: `Pair` has no special thread-related invariants or requirements, so -// sharing a reference to a `Pair` across multiple threads could only cause -// problems if sharing a reference to either the owner or the dependent across -// multiple threads could cause problems (since references to both are made -// accessible through references to the `Pair`). -unsafe impl Sync for Pair -where - O: Sync, - for<'any> >::Dependent: Sync, -{ -} - /// A self-referential pair containing both some [`Owner`] and its /// [`Dependent`](HasDependent::Dependent). /// @@ -115,13 +94,43 @@ impl Pair { // lasts from now until drop, where we will drop `dependent` and then // drop owner. - // SAFETY: `owner` was just converted from a valid Box, and inherits the - // alignment and validity guarantees of Box. Additionally, the value - // behind the pointer is currently not borrowed at all - this marks the - // beginning of a shared borrow which will last until the returned - // `Pair` is dropped (or immediately, if `make_dependent` returns an - // error). - let maybe_dependent = unsafe { owner.as_ref() }.make_dependent(context); + // We're about to call `make_dependent(..)` - if it panics, we want to + // be able to drop the boxed owner before unwinding the rest of the + // stack to avoid unnecessarily leaking memory (and potentially other + // resources). + let maybe_dependent = match std::panic::catch_unwind(AssertUnwindSafe(|| { + // SAFETY: `owner` was just converted from a valid Box, and inherits + // the alignment and validity guarantees of Box. Additionally, the + // value behind the pointer is currently not borrowed at all - this + // marks the beginning of a shared borrow which will last until the + // returned `Pair` is dropped (or immediately, if `make_dependent` + // panics or returns an error). + unsafe { owner.as_ref() }.make_dependent(context) + })) { + Ok(maybe_dependent) => maybe_dependent, + Err(payload) => { + // make_dependent panicked - drop the owner, then resume_unwind + + // SAFETY: `owner` was just created from a Box earlier in this + // function, and not invalidated since then. Because we haven't + // given away access to a `Self`, and the one borrow we took of + // the dependent to pass to `make_dependent` has expired, we + // know there are no outstanding borrows to owner. Therefore, + // reconstructing the original Box is okay. + let owner: Box = unsafe { Box::from_raw(owner.as_ptr()) }; + + // If the owner's drop *also* panics, we're in a super weird + // state. I think it makes more sense to resume_unwind with the + // payload of the first panic (from `make_dependent`), so if the + // owner's drop panics we just ignore it and continue on to + // resume_unwind with `make_dependent`'s payload. + let _ = std::panic::catch_unwind(AssertUnwindSafe(|| drop(owner))); + + // It's very important that we diverge here - carrying on to the + // rest of this constructor would be unsound. + std::panic::resume_unwind(payload); + } + }; // If `make_dependent(..)` failed, early return out from this function. let dependent = match maybe_dependent { @@ -177,14 +186,10 @@ impl Pair { /// lifetime, the closure may simply return the reference to the dependent, /// which may then be used as if this function directly returned a /// reference. - pub fn with_dependent< - 'self_borrow, + pub fn with_dependent<'self_borrow, F, T>(&'self_borrow self, f: F) -> T + where F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T, - T, - >( - &'self_borrow self, - f: F, - ) -> T { + { // SAFETY: `self.dependent` was originally converted from a valid // Box<>::Dependent>, and type-erased to a // NonNull<()>. As such, it inherited the alignment and validity @@ -213,14 +218,10 @@ impl Pair { /// because mutable references are invariant over their type `T`, and the /// exact T here (a `Dependent` with a very specific lifetime lasting from /// the construction of `self` until drop) is impossible to express. - pub fn with_dependent_mut< - 'self_borrow, + pub fn with_dependent_mut<'self_borrow, F, T>(&'self_borrow mut self, f: F) -> T + where F: for<'any> FnOnce(&'self_borrow mut >::Dependent) -> T, - T, - >( - &'self_borrow mut self, - f: F, - ) -> T { + { // SAFETY: `self.dependent` was originally converted from a valid // Box<>::Dependent>, and type-erased to a // NonNull<()>. As such, it inherited the alignment and validity @@ -260,13 +261,39 @@ impl Pair { // there are no outstanding borrows to the dependent. Therefore, // reconstructing the original Box<>::Dependent> // is okay. - drop(unsafe { + let dependent: Box<>::Dependent> = unsafe { Box::from_raw( this.dependent .cast::<>::Dependent>() .as_ptr(), ) - }); + }; + + // We're about to drop the dependent - if it panics, we want to be able + // to drop the boxed owner before unwinding the rest of the stack to + // avoid unnecessarily leaking memory (and potentially other resources). + if let Err(payload) = std::panic::catch_unwind(AssertUnwindSafe(|| drop(dependent))) { + // Dependent's drop panicked - drop the owner, then resume_unwind + + // SAFETY: `this.owner` was originally created from a Box, and never + // invalidated since then. Because we took ownership of `self`, and + // we just dropped the dependent (well, the drop panicked - but its + // borrow of the owner has certainly expired), we know there are no + // outstanding borrows to owner. Therefore, reconstructing the + // original Box is okay. + let owner: Box = unsafe { Box::from_raw(this.owner.as_ptr()) }; + + // If the owner's drop *also* panics, we're in a super weird state. + // I think it makes more sense to resume_unwind with the payload of + // the first panic (from dependent's drop), so if the owner's drop + // panics we just ignore it and continue on to resume_unwind with + // the dependent's payload. + let _ = std::panic::catch_unwind(AssertUnwindSafe(|| drop(owner))); + + // It's very important that we diverge here - carrying on to the + // rest of this function would be unsound. + std::panic::resume_unwind(payload); + } // SAFETY: `this.owner` was originally created from a Box, and never // invalidated since then. Because we took ownership of `self`, and we @@ -411,13 +438,39 @@ impl Drop for Pair { // invalidated since then. Because we are in drop, we know there are no // outstanding borrows to the dependent. Therefore, reconstructing the // original Box<>::Dependent> is okay. - drop(unsafe { + let dependent = unsafe { Box::from_raw( self.dependent .cast::<>::Dependent>() .as_ptr(), ) - }); + }; + + // We're about to drop the dependent - if it panics, we want to be able + // to drop the boxed owner before unwinding the rest of the stack to + // avoid unnecessarily leaking memory (and potentially other resources). + if let Err(payload) = std::panic::catch_unwind(AssertUnwindSafe(|| drop(dependent))) { + // Dependent's drop panicked - drop the owner, then resume_unwind + + // SAFETY: `this.owner` was originally created from a Box, and never + // invalidated since then. Because we are in drop, and we just + // dropped the dependent (well, the drop panicked - but its borrow + // of the owner has certainly expired), we know there are no + // outstanding borrows to owner. Therefore, reconstructing the + // original Box is okay. + let owner: Box = unsafe { Box::from_raw(self.owner.as_ptr()) }; + + // If the owner's drop *also* panics, we're in a super weird state. + // I think it makes more sense to resume_unwind with the payload of + // the first panic (from dependent's drop), so if the owner's drop + // panics we just ignore it and continue on to resume_unwind with + // the dependent's payload. + let _ = std::panic::catch_unwind(AssertUnwindSafe(|| drop(owner))); + + // It's very important that we diverge here - carrying on to the + // rest of drop would be unsound. + std::panic::resume_unwind(payload); + } // Drop the owner `Box` @@ -425,10 +478,36 @@ impl Drop for Pair { // invalidated since then. Because we are in drop, and we just dropped // the dependent, we know there are no outstanding borrows to owner. // Therefore, reconstructing the original Box is okay. - drop(unsafe { Box::from_raw(self.owner.as_ptr()) }); + let owner = unsafe { Box::from_raw(self.owner.as_ptr()) }; + + drop(owner); } } +// SAFETY: `Pair` has no special thread-related invariants or requirements, so +// sending a `Pair` to another thread could only cause problems if sending +// either the owner or the dependent to another thread could cause problems +// (since both are semantically moved with and made accessible through the +// `Pair`). +unsafe impl Send for Pair +where + O: Send, + for<'any> >::Dependent: Send, +{ +} + +// SAFETY: `Pair` has no special thread-related invariants or requirements, so +// sharing a reference to a `Pair` across multiple threads could only cause +// problems if sharing a reference to either the owner or the dependent across +// multiple threads could cause problems (since references to both are made +// accessible through references to the `Pair`). +unsafe impl Sync for Pair +where + O: Sync, + for<'any> >::Dependent: Sync, +{ +} + impl Debug for Pair where for<'any> >::Dependent: Debug, From 28b767cc7eb8368a1e6b8007d6dd36323369fadb Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 20 Feb 2025 19:27:17 -0500 Subject: [PATCH 021/110] added API overview to README.md --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e249f76..e1471de 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ Safe API for generic self-referential pairs of owner and dependent. -TODO: description and "basic API overview" (like https://crates.io/crates/takecell) +TODO: description + +The API looks generally like this (some details omitted for brevity): +```rust +pub trait HasDependent<'owner> { + type Dependent; +} + +pub trait Owner: for<'any> HasDependent<'any> { + fn make_dependent(&self) -> >::Dependent; +} + +pub struct Pair { ... } + +impl Pair { + pub fn new(owner: O) -> Self { ... } + + pub fn get_owner(&self) -> &O { ... } + + pub fn with_dependent<'self_borrow, F, T>(&'self_borrow self, f: F) -> T + where + F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T + { ... } + + pub fn with_dependent_mut<'self_borrow, F, T>(&'self_borrow mut self, f: F) -> T + where + F: for<'any> FnOnce(&'self_borrow mut >::Dependent) -> T + { ... } + + pub fn into_owner(self) -> O { ... } +} +``` # DO NOT USE THIS LIBRARY From ff8ba962b2e01556f7440000f0fa543dcead3ba4 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 22 Feb 2025 12:05:24 -0500 Subject: [PATCH 022/110] updated README.md --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e1471de..45019d1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,25 @@ Safe API for generic self-referential pairs of owner and dependent. -TODO: description +You define how to construct a dependent type from a reference to an owning type, +and `Pair` will carefully bundle them together in a safe and freely movable +self-referential struct. -The API looks generally like this (some details omitted for brevity): +# DO NOT USE THIS LIBRARY + +As of right now, I have absolutely no idea whether or not this API is sound. You +should *absolutely not* use this library for anything that matters at this point +in time. + +# API Overview + +The core API looks *roughly* like this (some details omitted for brevity): ```rust +// You specify the dependent (borrowing) type - ex, &'owner str or Foo<'owner> pub trait HasDependent<'owner> { type Dependent; } +// You specify how to make the dependent from a reference to the owner type pub trait Owner: for<'any> HasDependent<'any> { fn make_dependent(&self) -> >::Dependent; } @@ -15,29 +27,59 @@ pub trait Owner: for<'any> HasDependent<'any> { pub struct Pair { ... } impl Pair { + // A Pair can be constructed from an owner pub fn new(owner: O) -> Self { ... } + // The owner can be borrowed pub fn get_owner(&self) -> &O { ... } + // The dependent can be borrowed, although normally only through a closure + // (See the documentation of `with_dependent` for details) pub fn with_dependent<'self_borrow, F, T>(&'self_borrow self, f: F) -> T where F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T { ... } + // The dependent can be borrowed mutably, although only through a closure + // (See the documentation of `with_dependent_mut` for details) pub fn with_dependent_mut<'self_borrow, F, T>(&'self_borrow mut self, f: F) -> T where F: for<'any> FnOnce(&'self_borrow mut >::Dependent) -> T { ... } + // The owner can be recovered by consuming the Pair (dropping the dependent) pub fn into_owner(self) -> O { ... } } ``` -# DO NOT USE THIS LIBRARY - -As of right now, I have absolutely no idea whether or not this API is sound. You -should *absolutely not* use this library for anything that matters at this point -in time. +# How it Works + +Under the hood, `Pair` moves the owner onto the heap, giving it a stable memory +address. It is then borrowed and used to construct the dependent, which is also +moved onto the heap. The dependent is type-erased, so that its inexpressible +self-referential lifetime goes away. All exposed APIs are careful to ensure type +and aliasing rules are upheld, regardless of anything safe user code could do. +When the owner needs to be dropped or recovered, the dependent will first be +recovered and dropped, ending the borrow of the owner. At that point, the owner +can safely be recovered and the `Pair` deconstructed. + +# Related Projects + +| Crate | Macro free | No `alloc` | Maintained | Soundness | +| ----- | :--------: | :-------------: | :--------: | :-------: | +| `pair` | ✅ | ❌ | ✅ | No known issues | +| [`ouroboros`](https://crates.io/crates/ouroboros) | ❌ | ❌ | ✅ | ⚠️ [Unsound](https://github.com/someguynamedjosh/ouroboros/issues/122) ⚠️ | +| [`self_cell`](https://crates.io/crates/self_cell) | ❌ | ❌ | ✅ | No known issues | +| [`yoke`](https://crates.io/crates/yoke) | ❌ | ✅ | ✅ | ⚠️ [Unsound](https://github.com/unicode-org/icu4x/issues/2095) ⚠️ | +| [`selfie`](https://crates.io/crates/selfie) | ✅ | ✅ | ❌ | ⚠️ [Unsound](https://github.com/prokopyl/selfie?tab=readme-ov-file#abandoned-this-crate-is-unsound-and-no-longer-maintained_) ⚠️ | +| [`nolife`](https://crates.io/crates/nolife) | ❌ | ❌ | ✅ | No known issues | +| [`owning_ref`](https://crates.io/crates/owning_ref) | ✅ | ✅ | ❌ | ⚠️ [Unsound](https://github.com/Kimundi/owning-ref-rs/issues/77) ⚠️ | +| [`rental`](https://crates.io/crates/rental) | ❌ | ✅ | ❌ | ⚠️ [Unsound](https://github.com/Voultapher/self_cell?tab=readme-ov-file#related-projects) ⚠️ | +| [`fortify`](https://crates.io/crates/fortify) | ❌ | ❌ | ✅ | No known issues | +| [`loaned`](https://crates.io/crates/loaned) | ❌ | ✅ | ✅ | No known issues | +| [`selfref`](https://crates.io/crates/selfref) | ❌ | ✅ | ✅ | No known issues | +| [`self-reference`](https://crates.io/crates/self-reference) | ❌ | ✅ | ❌ | ⚠️ [Unsound](https://github.com/ArtBlnd/self-reference/issues/1) ⚠️ | +| [`zc`](https://crates.io/crates/zc) | ❌ | ✅ | ✅ | No known issues | # License From 9dee64382d66111694c1a2d4debec5c126a95673 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 22 Feb 2025 13:14:50 -0500 Subject: [PATCH 023/110] updated example usage section --- README.md | 99 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 45019d1..0302313 100644 --- a/README.md +++ b/README.md @@ -10,48 +10,85 @@ As of right now, I have absolutely no idea whether or not this API is sound. You should *absolutely not* use this library for anything that matters at this point in time. -# API Overview +# Example Usage -The core API looks *roughly* like this (some details omitted for brevity): +A typical use case might look something like this: ```rust -// You specify the dependent (borrowing) type - ex, &'owner str or Foo<'owner> -pub trait HasDependent<'owner> { - type Dependent; +// Let's say you have some buffer type that contains a string +#[derive(Debug)] +pub struct MyBuffer { + data: String, } -// You specify how to make the dependent from a reference to the owner type -pub trait Owner: for<'any> HasDependent<'any> { - fn make_dependent(&self) -> >::Dependent; +// And you have some borrowing "parsed" representation, containing string slices +#[derive(Debug)] +pub struct Parsed<'a> { + tokens: Vec<&'a str>, } -pub struct Pair { ... } - -impl Pair { - // A Pair can be constructed from an owner - pub fn new(owner: O) -> Self { ... } - - // The owner can be borrowed - pub fn get_owner(&self) -> &O { ... } - - // The dependent can be borrowed, although normally only through a closure - // (See the documentation of `with_dependent` for details) - pub fn with_dependent<'self_borrow, F, T>(&'self_borrow self, f: F) -> T - where - F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T - { ... } +// And you have some expensive parsing function you only want to run once +fn parse(buffer: &MyBuffer) -> Parsed<'_> { + Parsed { + tokens: buffer.data.split_whitespace().collect(), + } +} +``` - // The dependent can be borrowed mutably, although only through a closure - // (See the documentation of `with_dependent_mut` for details) - pub fn with_dependent_mut<'self_borrow, F, T>(&'self_borrow mut self, f: F) -> T - where - F: for<'any> FnOnce(&'self_borrow mut >::Dependent) -> T - { ... } +You would then implement `HasDependent` and `Owner` for `MyBuffer`: +```rust +// Defines the owner/dependent relationship between MyBuffer and Parsed<'_> +impl<'owner> HasDependent<'owner> for MyBuffer { + type Dependent = Parsed<'owner>; +} - // The owner can be recovered by consuming the Pair (dropping the dependent) - pub fn into_owner(self) -> O { ... } +// Define how to make a Parsed<'_> from a &MyBuffer +impl Owner for MyBuffer { + type Context = (); // We don't need any extra args to `make_dependent` + type Err = Infallible; // Our example parsing can't fail + + fn make_dependent( + &self, + (): Self::Context, + ) -> Result<>::Dependent, Self::Err> { + Ok(parse(self)) + } } ``` +You can now use `MyBuffer` in a `Pair`: +```rust +// A Pair can be constructed from an owner value (MyBuffer, in this example) +let mut pair = Pair::new(MyBuffer { + data: String::from("this is an example"), +}); + +// You can obtain a reference to the owner via a reference to the pair +let owner: &MyBuffer = pair.get_owner(); +assert_eq!(owner.data, "this is an example"); + +// You can access a reference to the dependent via a reference to the pair, but +// only within a provided closure. +// See the documentation of `Pair::with_dependent` for details. +let kebab = pair.with_dependent(|parsed: &Parsed<'_>| parsed.tokens.join("-")); +assert_eq!(kebab, "this-is-an-example"); + +// However, if the dependent is covariant over its lifetime (as our example +// Parsed<'_> is) you can trivially extract the dependent from the closure. +// This will not compile if the dependent is not covariant. +let parsed: &Parsed<'_> = pair.with_dependent(|parsed| parsed); +assert_eq!(parsed.tokens, ["this", "is", "an", "example"]); + +// You can obtain a mutable reference to the dependent via a mutable +// reference to the pair, but only within a provided closure. +// See the documentation of `Pair::with_dependent_mut` for details. +pair.with_dependent_mut(|parsed| parsed.tokens.pop()); +assert_eq!(pair.with_dependent(|parsed| parsed.tokens.len()), 3); + +// If you're done with the dependent, you can recover the owner. +// This will drop the dependent. +let my_buffer: MyBuffer = pair.into_owner(); +``` + # How it Works Under the hood, `Pair` moves the owner onto the heap, giving it a stable memory From ecd49000af6359f72e9305f9fadea7b1592c79f3 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 22 Feb 2025 13:22:00 -0500 Subject: [PATCH 024/110] added changelog and lints to-do comment --- CHANGELOG.md | 4 ++++ Cargo.toml | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3354c92 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Major Version 0 + +## v0.1.0 +Initial (empty) release. diff --git a/Cargo.toml b/Cargo.toml index 452d0ee..49a8068 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,9 @@ readme = "README.md" repository = "https://github.com/ijchen/pair" documentation = "https://docs.rs/pair" edition = "2024" -include = ["/src/", "/Cargo.toml", "/README.md", "/LICENSE-*"] +include = ["/src/", "/Cargo.toml", "/README.md", "CHANGELOG.md", "/LICENSE-*"] publish = false [dependencies] + +# TODO: define lints From d8259e5d93bce66ecc5b572d06e9b4e162e538ec Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 22 Feb 2025 14:34:58 -0500 Subject: [PATCH 025/110] made README doc links clickable --- README.md | 39 ++++++++++++++++++++++++++------------- src/lib.rs | 11 ++++++++++- src/owner.rs | 2 +- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0302313..9bc3934 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Safe API for generic self-referential pairs of owner and dependent. You define how to construct a dependent type from a reference to an owning type, -and `Pair` will carefully bundle them together in a safe and freely movable +and [`Pair`] will carefully bundle them together in a safe and freely movable self-referential struct. # DO NOT USE THIS LIBRARY @@ -34,7 +34,7 @@ fn parse(buffer: &MyBuffer) -> Parsed<'_> { } ``` -You would then implement `HasDependent` and `Owner` for `MyBuffer`: +You would then implement [`HasDependent`] and [`Owner`] for `MyBuffer`: ```rust // Defines the owner/dependent relationship between MyBuffer and Parsed<'_> impl<'owner> HasDependent<'owner> for MyBuffer { @@ -55,7 +55,7 @@ impl Owner for MyBuffer { } ``` -You can now use `MyBuffer` in a `Pair`: +You can now use `MyBuffer` in a [`Pair`]: ```rust // A Pair can be constructed from an owner value (MyBuffer, in this example) let mut pair = Pair::new(MyBuffer { @@ -91,14 +91,14 @@ let my_buffer: MyBuffer = pair.into_owner(); # How it Works -Under the hood, `Pair` moves the owner onto the heap, giving it a stable memory -address. It is then borrowed and used to construct the dependent, which is also -moved onto the heap. The dependent is type-erased, so that its inexpressible -self-referential lifetime goes away. All exposed APIs are careful to ensure type -and aliasing rules are upheld, regardless of anything safe user code could do. -When the owner needs to be dropped or recovered, the dependent will first be -recovered and dropped, ending the borrow of the owner. At that point, the owner -can safely be recovered and the `Pair` deconstructed. +Under the hood, [`Pair`] moves the owner onto the heap, giving it a stable +memory address. It is then borrowed and used to construct the dependent, which +is also moved onto the heap. The dependent is type-erased, so that its +inexpressible self-referential lifetime goes away. All exposed APIs are careful +to ensure type and aliasing rules are upheld, regardless of anything safe user +code could do. When the owner needs to be dropped or recovered, the dependent +will first be recovered and dropped, ending the borrow of the owner. At that +point, the owner can safely be recovered and the `Pair` deconstructed. # Related Projects @@ -123,8 +123,9 @@ can safely be recovered and the `Pair` deconstructed. Licensed under either of: - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or + ) at your option. @@ -133,3 +134,15 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + + + +[`Pair`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/struct.Pair.html +[`Owner`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/trait.Owner.html +[`HasDependent`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/trait.HasDependent.html diff --git a/src/lib.rs b/src/lib.rs index 0721fc2..6d3a0b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,13 @@ +// These markdown links are used to override the docs.rs web links present at +// the bottom of README.md. These two lists must be kept in sync, or the links +// included here will be hard-coded links to docs.rs, not relative doc links. +// TODO: for a release, the below links should be verified to match the readme, +// and this second "to-do" comment removed (the above one should stay). +//! [`Pair`]: Pair +//! [`HasDependent`]: HasDependent +//! [`Owner`]: Owner +#![doc = include_str!("../README.md")] + mod owner; mod pair; @@ -17,4 +27,3 @@ pub use pair::Pair; // API don't compile // - All under MIRI // - Soundness audits by experienced dark arts rustaceans -// - Crate/module level documentation diff --git a/src/owner.rs b/src/owner.rs index 6d33529..a8b0f26 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -32,7 +32,7 @@ pub trait HasDependent<'owner, ForImpliedBound: Sealed = Bounds<&'owner Self>> { } /// A type which can act as the "owner" of some data, and can produce some -/// dependent type which borrows from `Self`. Used for the [`Pair`](crate::pair) +/// dependent type which borrows from `Self`. Used for the [`Pair`](crate::Pair) /// struct. /// /// The supertrait [`HasDependent<'_>`] defines the dependent type, acting as a From b3fcf6adb7c1797bee80d13e998688530711573f Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 22 Feb 2025 14:47:59 -0500 Subject: [PATCH 026/110] added ON_RELEASE comment marker --- Cargo.toml | 3 +++ README.md | 5 +---- src/lib.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 49a8068..920ff5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "pair" +# ON_RELEASE: Bump version. Don't forget to do all other "ON_RELEASE" tasks! version = "0.1.0" authors = ["Isaac Chen"] description = "Safe API for generic self-referential pairs of owner and dependent." @@ -9,8 +10,10 @@ repository = "https://github.com/ijchen/pair" documentation = "https://docs.rs/pair" edition = "2024" include = ["/src/", "/Cargo.toml", "/README.md", "CHANGELOG.md", "/LICENSE-*"] +# ON_RELEASE: Remove publish = false publish = false [dependencies] +# No dependencies # TODO: define lints diff --git a/README.md b/README.md index 9bc3934..855681d 100644 --- a/README.md +++ b/README.md @@ -139,10 +139,7 @@ dual licensed as above, without any additional terms or conditions. docs.rs documentation links for rendered markdown (ex, on GitHub) These are overridden when include_str!(..)'d in lib.rs --> - + [`Pair`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/struct.Pair.html [`Owner`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/trait.Owner.html [`HasDependent`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/trait.HasDependent.html diff --git a/src/lib.rs b/src/lib.rs index 6d3a0b0..4e14510 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ // These markdown links are used to override the docs.rs web links present at // the bottom of README.md. These two lists must be kept in sync, or the links // included here will be hard-coded links to docs.rs, not relative doc links. -// TODO: for a release, the below links should be verified to match the readme, -// and this second "to-do" comment removed (the above one should stay). +// ON_RELEASE: the below links should be verified to match the readme, and this +// "on release" comment removed (the above one should stay). //! [`Pair`]: Pair //! [`HasDependent`]: HasDependent //! [`Owner`]: Owner From 3a6d0d713d8d47d3011eac38e656f1ad9642fa5d Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 22 Feb 2025 14:49:35 -0500 Subject: [PATCH 027/110] added ON_RELEASE comment to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3354c92..535f0c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ + + # Major Version 0 ## v0.1.0 From c2915f32536eb957fb88a6fee4ffc74614672be8 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 22 Feb 2025 15:14:36 -0500 Subject: [PATCH 028/110] made example usage in README a single (unit testedgit add .) code block --- README.md | 90 ++++++++++++++++++++++++++++-------------------------- src/lib.rs | 6 ++-- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 855681d..4d7ef53 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Safe API for generic self-referential pairs of owner and dependent. You define how to construct a dependent type from a reference to an owning type, -and [`Pair`] will carefully bundle them together in a safe and freely movable +and `pair` will carefully bundle them together in a safe and freely movable self-referential struct. # DO NOT USE THIS LIBRARY @@ -14,6 +14,8 @@ in time. A typical use case might look something like this: ```rust +use pair::{HasDependent, Owner, Pair}; + // Let's say you have some buffer type that contains a string #[derive(Debug)] pub struct MyBuffer { @@ -28,14 +30,14 @@ pub struct Parsed<'a> { // And you have some expensive parsing function you only want to run once fn parse(buffer: &MyBuffer) -> Parsed<'_> { - Parsed { - tokens: buffer.data.split_whitespace().collect(), - } + let tokens = buffer.data.split_whitespace().collect(); + Parsed { tokens } } -``` -You would then implement [`HasDependent`] and [`Owner`] for `MyBuffer`: -```rust + + +// You would then implement HasDependent and Owner for MyBuffer: + // Defines the owner/dependent relationship between MyBuffer and Parsed<'_> impl<'owner> HasDependent<'owner> for MyBuffer { type Dependent = Parsed<'owner>; @@ -44,7 +46,7 @@ impl<'owner> HasDependent<'owner> for MyBuffer { // Define how to make a Parsed<'_> from a &MyBuffer impl Owner for MyBuffer { type Context = (); // We don't need any extra args to `make_dependent` - type Err = Infallible; // Our example parsing can't fail + type Err = std::convert::Infallible; // Our example parsing can't fail fn make_dependent( &self, @@ -53,40 +55,42 @@ impl Owner for MyBuffer { Ok(parse(self)) } } -``` -You can now use `MyBuffer` in a [`Pair`]: -```rust -// A Pair can be constructed from an owner value (MyBuffer, in this example) -let mut pair = Pair::new(MyBuffer { - data: String::from("this is an example"), -}); - -// You can obtain a reference to the owner via a reference to the pair -let owner: &MyBuffer = pair.get_owner(); -assert_eq!(owner.data, "this is an example"); - -// You can access a reference to the dependent via a reference to the pair, but -// only within a provided closure. -// See the documentation of `Pair::with_dependent` for details. -let kebab = pair.with_dependent(|parsed: &Parsed<'_>| parsed.tokens.join("-")); -assert_eq!(kebab, "this-is-an-example"); - -// However, if the dependent is covariant over its lifetime (as our example -// Parsed<'_> is) you can trivially extract the dependent from the closure. -// This will not compile if the dependent is not covariant. -let parsed: &Parsed<'_> = pair.with_dependent(|parsed| parsed); -assert_eq!(parsed.tokens, ["this", "is", "an", "example"]); - -// You can obtain a mutable reference to the dependent via a mutable -// reference to the pair, but only within a provided closure. -// See the documentation of `Pair::with_dependent_mut` for details. -pair.with_dependent_mut(|parsed| parsed.tokens.pop()); -assert_eq!(pair.with_dependent(|parsed| parsed.tokens.len()), 3); - -// If you're done with the dependent, you can recover the owner. -// This will drop the dependent. -let my_buffer: MyBuffer = pair.into_owner(); + + +// You can now use MyBuffer in a Pair: +fn main() { + // A Pair can be constructed from an owner value (MyBuffer, in this example) + let mut pair = Pair::new(MyBuffer { + data: String::from("this is an example"), + }); + + // You can obtain a reference to the owner via a reference to the pair + let owner: &MyBuffer = pair.get_owner(); + assert_eq!(owner.data, "this is an example"); + + // You can access a reference to the dependent via a reference to the pair, + // but only within a provided closure. + // See the documentation of `Pair::with_dependent` for details. + let kebab = pair.with_dependent(|parsed: &Parsed<'_>| parsed.tokens.join("-")); + assert_eq!(kebab, "this-is-an-example"); + + // However, if the dependent is covariant over its lifetime (as our example + // Parsed<'_> is) you can trivially extract the dependent from the closure. + // This will not compile if the dependent is not covariant. + let parsed: &Parsed<'_> = pair.with_dependent(|parsed| parsed); + assert_eq!(parsed.tokens, ["this", "is", "an", "example"]); + + // You can obtain a mutable reference to the dependent via a mutable + // reference to the pair, but only within a provided closure. + // See the documentation of `Pair::with_dependent_mut` for details. + pair.with_dependent_mut(|parsed| parsed.tokens.pop()); + assert_eq!(pair.with_dependent(|parsed| parsed.tokens.len()), 3); + + // If you're done with the dependent, you can recover the owner. + // This will drop the dependent. + let my_buffer: MyBuffer = pair.into_owner(); +} ``` # How it Works @@ -139,7 +143,5 @@ dual licensed as above, without any additional terms or conditions. docs.rs documentation links for rendered markdown (ex, on GitHub) These are overridden when include_str!(..)'d in lib.rs --> - + [`Pair`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/struct.Pair.html -[`Owner`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/trait.Owner.html -[`HasDependent`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/trait.HasDependent.html diff --git a/src/lib.rs b/src/lib.rs index 4e14510..62b0577 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,9 @@ // These markdown links are used to override the docs.rs web links present at // the bottom of README.md. These two lists must be kept in sync, or the links // included here will be hard-coded links to docs.rs, not relative doc links. -// ON_RELEASE: the below links should be verified to match the readme, and this -// "on release" comment removed (the above one should stay). +// ON_RELEASE: the below link(s) should be verified to match the readme, and +// this "on release" comment removed (the above one should stay). //! [`Pair`]: Pair -//! [`HasDependent`]: HasDependent -//! [`Owner`]: Owner #![doc = include_str!("../README.md")] mod owner; From 5d1c86fd0f9caee96a62fb416b9f0fe758ded731 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 24 Feb 2025 08:35:59 -0500 Subject: [PATCH 029/110] made Context a ltGAT, started tests --- Cargo.lock | 280 ++++++++++++++++++ Cargo.toml | 3 + README.md | 4 +- ci.sh | 8 + src/owner.rs | 10 +- src/pair.rs | 14 +- tests/basic_usage.rs | 54 ++++ tests/tmp.rs | 46 +++ tests/trybuild_fails/pair_not_covariant.rs | 46 +++ .../trybuild_fails/pair_not_covariant.stderr | 15 + tests/trybuild_test.rs | 5 + tests/unsized_owner.rs | 65 ++++ 12 files changed, 536 insertions(+), 14 deletions(-) create mode 100755 ci.sh create mode 100644 tests/basic_usage.rs create mode 100644 tests/tmp.rs create mode 100644 tests/trybuild_fails/pair_not_covariant.rs create mode 100644 tests/trybuild_fails/pair_not_covariant.stderr create mode 100644 tests/trybuild_test.rs create mode 100644 tests/unsized_owner.rs diff --git a/Cargo.lock b/Cargo.lock index 82dbb46..f4f3f58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,286 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "pair" version = "0.1.0" +dependencies = [ + "trybuild", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "serde" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "trybuild" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b812699e0c4f813b872b373a4471717d9eb550da14b311058a4d9cf4173cbca6" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml", +] + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 920ff5d..75de4a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,7 @@ publish = false [dependencies] # No dependencies +[dev-dependencies] +trybuild = "1.0.103" + # TODO: define lints diff --git a/README.md b/README.md index 4d7ef53..1f67415 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,12 @@ impl<'owner> HasDependent<'owner> for MyBuffer { // Define how to make a Parsed<'_> from a &MyBuffer impl Owner for MyBuffer { - type Context = (); // We don't need any extra args to `make_dependent` + type Context<'a> = (); // We don't need any extra args to `make_dependent` type Err = std::convert::Infallible; // Our example parsing can't fail fn make_dependent( &self, - (): Self::Context, + (): Self::Context<'_>, ) -> Result<>::Dependent, Self::Err> { Ok(parse(self)) } diff --git a/ci.sh b/ci.sh new file mode 100755 index 0000000..c48c44a --- /dev/null +++ b/ci.sh @@ -0,0 +1,8 @@ +cargo clippy && \ +cargo test && \ +( + cp Cargo.toml Cargo.toml.backup && \ + trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ + sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ + cargo +nightly miri test -- --skip try_builds +) diff --git a/src/owner.rs b/src/owner.rs index a8b0f26..02fdce4 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -47,7 +47,7 @@ pub trait Owner: for<'any> HasDependent<'any> { // // TODO(ichen): default this to () when associated type defaults are // stabilized (https://github.com/rust-lang/rust/issues/29661) - type Context; + type Context<'a>; /// The error type returned by [`make_dependent`](Owner::make_dependent) in /// the event of an error. @@ -62,8 +62,8 @@ pub trait Owner: for<'any> HasDependent<'any> { /// Attempts to construct a [`Dependent`](HasDependent::Dependent) from a /// reference to an owner and some context. - fn make_dependent( - &self, - context: Self::Context, - ) -> Result<>::Dependent, Self::Err>; + fn make_dependent<'owner>( + &'owner self, + context: Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err>; } diff --git a/src/pair.rs b/src/pair.rs index ccce179..7d8f171 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -62,7 +62,7 @@ impl Pair { /// /// If this construction can't fail, consider the convenience constructor /// [`Pair::new_with_context`], which returns `Self` directly. - pub fn try_new_with_context(owner: O, context: O::Context) -> Result + pub fn try_new_with_context(owner: O, context: O::Context<'_>) -> Result where O: Sized, { @@ -84,7 +84,7 @@ impl Pair { /// [`Pair::new_from_box_with_context`], which returns `Self` directly. pub fn try_new_from_box_with_context( owner: Box, - context: O::Context, + context: O::Context<'_>, ) -> Result, O::Err)> { // Convert owner into a NonNull, so we are no longer restricted by the // aliasing requirements of Box @@ -314,7 +314,7 @@ impl Pair { } } -impl + ?Sized> Pair { +impl Owner = (), Err = Infallible> + ?Sized> Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// @@ -352,7 +352,7 @@ impl + ?Sized> Pair { } } -impl + ?Sized> Pair { +impl Owner = ()> + ?Sized> Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// @@ -402,7 +402,7 @@ impl + ?Sized> Pair { /// /// If this construction can fail, consider [`Pair::try_new_with_context`], /// which returns a [`Result`]. - pub fn new_with_context(owner: O, context: O::Context) -> Self + pub fn new_with_context(owner: O, context: O::Context<'_>) -> Self where O: Sized, { @@ -422,7 +422,7 @@ impl + ?Sized> Pair { /// /// If this construction can fail, consider /// [`Pair::try_new_from_box_with_context`], which returns a [`Result`]. - pub fn new_from_box_with_context(owner: Box, context: O::Context) -> Self { + pub fn new_from_box_with_context(owner: Box, context: O::Context<'_>) -> Self { let Ok(pair) = Self::try_new_from_box_with_context(owner, context); pair } @@ -522,7 +522,7 @@ where } } -impl + Default> Default for Pair { +impl Owner = (), Err = Infallible> + Default> Default for Pair { fn default() -> Self { Self::new(O::default()) } diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs new file mode 100644 index 0000000..3b99441 --- /dev/null +++ b/tests/basic_usage.rs @@ -0,0 +1,54 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +#[derive(Debug)] +struct Buff(String); + +impl<'owner> HasDependent<'owner> for Buff { + type Dependent = Vec<&'owner str>; +} + +impl Owner for Buff { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(self.0.split_whitespace().collect()) + } +} + +#[test] +fn basic_usage() { + let mut pair = Pair::new(Buff(String::from("This is a test of pair."))); + let owner: &Buff = pair.get_owner(); + let dep: &Vec<&str> = pair.with_dependent(|dep| dep); + + assert_eq!(owner.0, "This is a test of pair."); + assert_eq!(dep, &["This", "is", "a", "test", "of", "pair."]); + + pair.with_dependent_mut(|dep| dep.push("hi")); + pair.with_dependent_mut(|dep| dep.push("hey")); + assert_eq!( + pair.with_dependent(|d| d), + &["This", "is", "a", "test", "of", "pair.", "hi", "hey"] + ); + pair.with_dependent_mut(|dep| dep.sort()); + assert_eq!( + pair.with_dependent(|d| d), + &["This", "a", "hey", "hi", "is", "of", "pair.", "test"] + ); + + let last_word = pair.with_dependent_mut(|dep| dep.pop()); + assert_eq!(last_word, Some("test")); + assert_eq!( + pair.with_dependent(|d| d), + &["This", "a", "hey", "hi", "is", "of", "pair."] + ); + + let owner: Buff = pair.into_owner(); + assert_eq!(owner.0, "This is a test of pair."); +} diff --git a/tests/tmp.rs b/tests/tmp.rs new file mode 100644 index 0000000..187bbc8 --- /dev/null +++ b/tests/tmp.rs @@ -0,0 +1,46 @@ +// #![allow(unused)] + +// use std::{convert::Infallible, marker::PhantomData}; + +// use pair::{HasDependent, Owner, Pair}; + +// fn main() {} + +// struct Foo<'a>(PhantomData<&'a ()>); + +// struct Covariant(PhantomData); + +// impl<'owner> HasDependent<'owner> for Foo<'_> { +// type Dependent = (); +// } +// impl Owner for Foo<'_> { +// type Context<'a> = (); +// type Err = Infallible; + +// fn make_dependent( +// &self, +// (): Self::Context<'_>, +// ) -> Result<>::Dependent, Self::Err> { +// unimplemented!() +// } +// } + +// fn uh_oh<'shorter, 'longer>() +// where +// 'longer: 'shorter, +// { +// // Foo<'a> should be covariant over 'a, so this should be okay +// let mut foo_shorter: Foo<'shorter> = Foo(PhantomData); +// let foo_longer: Foo<'longer> = Foo(PhantomData); +// foo_shorter = foo_longer; + +// // Covariant should be covariant over T, so this should be okay +// let mut cov_shorter: Covariant> = Covariant(PhantomData); +// let cov_longer: Covariant> = Covariant(PhantomData); +// cov_shorter = cov_longer; + +// // Pair should *not* be covariant over O, so this should *not* be okay +// let mut pair_shorter: Pair> = Pair::new(Foo(PhantomData)); +// let pair_longer: Pair> = Pair::new(Foo(PhantomData)); +// pair_shorter = pair_longer; +// } diff --git a/tests/trybuild_fails/pair_not_covariant.rs b/tests/trybuild_fails/pair_not_covariant.rs new file mode 100644 index 0000000..ce5aaba --- /dev/null +++ b/tests/trybuild_fails/pair_not_covariant.rs @@ -0,0 +1,46 @@ +#![allow(unused)] + +use std::{convert::Infallible, marker::PhantomData}; + +use pair::{HasDependent, Owner, Pair}; + +fn main() {} + +struct Foo<'a>(PhantomData<&'a ()>); + +struct Covariant(PhantomData); + +impl<'owner> HasDependent<'owner> for Foo<'_> { + type Dependent = (); +} +impl Owner for Foo<'_> { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + unimplemented!() + } +} + +fn uh_oh<'shorter, 'longer>() +where + 'longer: 'shorter, +{ + // Foo<'a> should be covariant over 'a, so this should be okay + let mut foo_shorter: Foo<'shorter> = Foo(PhantomData); + let foo_longer: Foo<'longer> = Foo(PhantomData); + foo_shorter = foo_longer; + + // Covariant should be covariant over T, so this should be okay + let mut cov_shorter: Covariant> = Covariant(PhantomData); + let cov_longer: Covariant> = Covariant(PhantomData); + cov_shorter = cov_longer; + + // Pair should *not* be covariant over O, so this should *not* be okay + let mut pair_shorter: Pair> = Pair::new(Foo(PhantomData)); + let pair_longer: Pair> = Pair::new(Foo(PhantomData)); + pair_shorter = pair_longer; +} diff --git a/tests/trybuild_fails/pair_not_covariant.stderr b/tests/trybuild_fails/pair_not_covariant.stderr new file mode 100644 index 0000000..6436ae7 --- /dev/null +++ b/tests/trybuild_fails/pair_not_covariant.stderr @@ -0,0 +1,15 @@ +error: lifetime may not live long enough + --> tests/trybuild_fails/pair_not_covariant.rs:44:22 + | +28 | fn uh_oh<'shorter, 'longer>() + | -------- ------- lifetime `'longer` defined here + | | + | lifetime `'shorter` defined here +... +44 | let pair_longer: Pair> = Pair::new(Foo(PhantomData)); + | ^^^^^^^^^^^^^^^^^^ type annotation requires that `'shorter` must outlive `'longer` + | + = help: consider adding the following bound: `'shorter: 'longer` + = note: requirement occurs because of the type `Pair>`, which makes the generic argument `Foo<'_>` invariant + = note: the struct `Pair` is invariant over the parameter `O` + = help: see for more information about variance diff --git a/tests/trybuild_test.rs b/tests/trybuild_test.rs new file mode 100644 index 0000000..2d7d781 --- /dev/null +++ b/tests/trybuild_test.rs @@ -0,0 +1,5 @@ +#[test] +fn try_builds() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trybuild_fails/*.rs"); +} diff --git a/tests/unsized_owner.rs b/tests/unsized_owner.rs new file mode 100644 index 0000000..7ffa85c --- /dev/null +++ b/tests/unsized_owner.rs @@ -0,0 +1,65 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +#[derive(Debug)] +struct Buff(T); + +impl Buff<[u8]> { + pub fn new(data: [u8; N]) -> Box { + Box::new(Buff(data)) + } +} + +impl<'owner> HasDependent<'owner> for Buff<[u8]> { + type Dependent = (&'owner [u8], usize); +} + +impl Owner for Buff<[u8]> { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + let start = usize::min(1, self.0.len()); + let end = self.0.len().saturating_sub(1); + Ok((&self.0[start..end], self.0.len())) + } +} + +#[test] +fn basic_usage() { + let mut pair = Pair::new_from_box(Buff::new([2, 69, 42, 5, 6, 7, 8])); + let owner: &Buff<[u8]> = pair.get_owner(); + let dep: &(&[u8], usize) = pair.with_dependent(|dep| dep); + + assert_eq!(owner.0, [2, 69, 42, 5, 6, 7, 8]); + assert_eq!(*dep, ([69, 42, 5, 6, 7].as_slice(), 7)); + + pair.with_dependent_mut(|dep| dep.1 -= 2); + pair.with_dependent_mut(|dep| dep.1 *= 10); + assert_eq!( + *pair.with_dependent(|d| d), + ([69, 42, 5, 6, 7].as_slice(), 50) + ); + pair.with_dependent_mut(|dep| std::ops::AddAssign::add_assign(&mut dep.1, 5)); + assert_eq!( + *pair.with_dependent(|d| d), + ([69, 42, 5, 6, 7].as_slice(), 55) + ); + + let n = pair.with_dependent_mut(|dep| { + dep.1 = 0; + dep.1 + }); + assert_eq!(n, 0); + assert_eq!( + *pair.with_dependent(|d| d), + ([69, 42, 5, 6, 7].as_slice(), 0) + ); + + let owner: Box> = pair.into_boxed_owner(); + assert_eq!(owner.0, [2, 69, 42, 5, 6, 7, 8]); +} From a1e78d90432c90c8492c81269789a921a5e21ceb Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 24 Feb 2025 20:13:12 -0500 Subject: [PATCH 030/110] added alternative constructors test --- tests/alternative_ctors.rs | 149 +++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tests/alternative_ctors.rs diff --git a/tests/alternative_ctors.rs b/tests/alternative_ctors.rs new file mode 100644 index 0000000..73a4312 --- /dev/null +++ b/tests/alternative_ctors.rs @@ -0,0 +1,149 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +#[derive(Debug)] +struct BuffFallible(String); + +impl<'owner> HasDependent<'owner> for BuffFallible { + type Dependent = Vec<&'owner str>; +} + +impl Owner for BuffFallible { + type Context<'a> = (); + type Err = String; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + let parts: Vec<_> = self.0.split_whitespace().collect(); + + if parts.is_empty() { + Err(String::from("Conversion failed")) + } else { + Ok(parts) + } + } +} + +#[test] +fn fallible() { + let pair = Pair::try_new(BuffFallible(String::from("This is a test of pair."))).unwrap(); + assert_eq!(pair.get_owner().0, "This is a test of pair."); + + let (buff, err) = Pair::try_new(BuffFallible(String::from(" "))).unwrap_err(); + assert_eq!(buff.0, " "); + assert_eq!(err, "Conversion failed"); + + let pair = Pair::try_new_from_box(Box::new(BuffFallible(String::from( + "This is a test of pair.", + )))) + .unwrap(); + assert_eq!(pair.get_owner().0, "This is a test of pair."); + + let (buff, err) = + Pair::try_new_from_box(Box::new(BuffFallible(String::from(" ")))).unwrap_err(); + assert_eq!(buff.0, " "); + assert_eq!(err, "Conversion failed"); +} + +#[derive(Debug)] +struct BuffWithContext(String); + +impl<'owner> HasDependent<'owner> for BuffWithContext { + type Dependent = Vec<&'owner str>; +} + +impl Owner for BuffWithContext { + type Context<'a> = &'a str; + type Err = Infallible; + + fn make_dependent( + &self, + context: Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(self.0.split(context).collect()) + } +} + +#[test] +fn with_context() { + let pair = Pair::new_with_context(BuffWithContext(String::from("foo, bar, bat, baz")), ", "); + assert_eq!(pair.get_owner().0, "foo, bar, bat, baz"); + + let pair = Pair::new_from_box_with_context( + Box::new(BuffWithContext(String::from("foo, bar, bat, baz"))), + ", ", + ); + assert_eq!(pair.get_owner().0, "foo, bar, bat, baz"); +} + +#[derive(Debug)] +struct BuffFallibleWithContext(String); + +impl<'owner> HasDependent<'owner> for BuffFallibleWithContext { + type Dependent = Vec<&'owner str>; +} + +impl Owner for BuffFallibleWithContext { + type Context<'a> = &'a str; + type Err = String; + + fn make_dependent( + &self, + context: Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + let parts: Vec<_> = self.0.split(context).collect(); + + if parts.len() > 1 { + Ok(parts) + } else { + Err(format!( + "Conversion of string '{}' with context '{context}' failed.", + self.0 + )) + } + } +} + +#[test] +fn fallible_with_context() { + let pair = Pair::try_new_with_context( + BuffFallibleWithContext(String::from("foo, bar, bat, baz")), + ", ", + ) + .unwrap(); + assert_eq!(pair.get_owner().0, "foo, bar, bat, baz"); + + let (buff, err) = Pair::try_new_with_context( + BuffFallibleWithContext(String::from("This is a test of pair.")), + ", ", + ) + .unwrap_err(); + assert_eq!(buff.0, "This is a test of pair."); + assert_eq!( + err, + "Conversion of string 'This is a test of pair.' with context ', ' failed." + ); + + let pair = Pair::try_new_from_box_with_context( + Box::new(BuffFallibleWithContext(String::from("foo, bar, bat, baz"))), + ", ", + ) + .unwrap(); + assert_eq!(pair.get_owner().0, "foo, bar, bat, baz"); + + let (buff, err) = Pair::try_new_from_box_with_context( + Box::new(BuffFallibleWithContext(String::from( + "This is a test of pair.", + ))), + ", ", + ) + .unwrap_err(); + assert_eq!(buff.0, "This is a test of pair."); + assert_eq!( + err, + "Conversion of string 'This is a test of pair.' with context ', ' failed." + ); +} From 0b6fee718dc04eaf18f540e6006b8396402fc736 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 24 Feb 2025 23:50:16 -0500 Subject: [PATCH 031/110] added panic safety tests --- src/pair.rs | 5 +- tests/basic_usage.rs | 26 ++++ tests/panic_safety.rs | 272 +++++++++++++++++++++++++++++++++++++++++ tests/unsized_owner.rs | 2 +- 4 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 tests/panic_safety.rs diff --git a/src/pair.rs b/src/pair.rs index 7d8f171..3756c0b 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -251,9 +251,8 @@ impl Pair { // dependent again, which would be... not good (unsound). // // It's important that we do this before calling the dependent's drop, - // since a panic in that drop would otherwise cause at least a double - // panic, and potentially even unsoundness (although that part I'm less - // sure of) + // since a panic in that drop would otherwise cause a double free when + // we attempt to drop the dependent again when dropping `self`. let this = ManuallyDrop::new(self); // SAFETY: `this.dependent` was originally created from a Box, and never diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs index 3b99441..7299dd5 100644 --- a/tests/basic_usage.rs +++ b/tests/basic_usage.rs @@ -52,3 +52,29 @@ fn basic_usage() { let owner: Buff = pair.into_owner(); assert_eq!(owner.0, "This is a test of pair."); } + +#[test] +fn get_owner_stress_test() { + // Let's just do a bunch of `get_owner(..)`s interlaced with a bunch of + // other random stuff and see what MIRI thinks + let mut pair = Pair::new(Buff(String::from("This is a test of pair."))); + let owner1 = pair.get_owner(); + let owner2 = pair.get_owner(); + let owner3 = pair.get_owner(); + let dep1 = pair.with_dependent(|dep| dep); + let owner4 = pair.get_owner(); + let dep2 = pair.with_dependent(|dep| dep); + println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{dep1:?}{dep2:?}"); + pair.with_dependent_mut(|dep| dep.push("hi")); + let owner1 = pair.get_owner(); + let owner2 = pair.get_owner(); + println!("{owner1:?}{owner2:?}"); + let pair2 = pair; + let owner1 = pair2.get_owner(); + let owner2 = pair2.get_owner(); + let owner3 = pair2.get_owner(); + let dep1 = pair2.with_dependent(|dep| dep); + let owner4 = pair2.get_owner(); + let dep2 = pair2.with_dependent(|dep| dep); + println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{dep1:?}{dep2:?}"); +} diff --git a/tests/panic_safety.rs b/tests/panic_safety.rs new file mode 100644 index 0000000..154e921 --- /dev/null +++ b/tests/panic_safety.rs @@ -0,0 +1,272 @@ +use std::{ + cell::RefCell, + convert::Infallible, + panic::{AssertUnwindSafe, catch_unwind, panic_any}, + rc::Rc, +}; + +use pair::{HasDependent, Owner, Pair}; + +#[derive(Debug, PartialEq, Eq)] +struct MyPayload(u8); + +// make_dependent panics +struct PanicOnMakeDependent(Rc>); +impl Drop for PanicOnMakeDependent { + fn drop(&mut self) { + *self.0.borrow_mut() = true; + } +} + +impl<'owner> HasDependent<'owner> for PanicOnMakeDependent { + type Dependent = (); +} + +impl Owner for PanicOnMakeDependent { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + panic_any(MyPayload(7)); + } +} + +#[test] +fn make_dependent_panic_handled() { + let owner_drop_called = Rc::new(RefCell::new(false)); + let owner_drop_called2 = Rc::clone(&owner_drop_called); + let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| { + Pair::new(PanicOnMakeDependent(owner_drop_called2)); + })) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(payload, MyPayload(7)); + assert!(*owner_drop_called.borrow()); +} + +// make_dependent panics and owner drop panics +struct PanicOnMakeDependentAndOwnerDrop(Rc>); +impl Drop for PanicOnMakeDependentAndOwnerDrop { + fn drop(&mut self) { + *self.0.borrow_mut() = true; + panic!("lol"); + } +} + +impl<'owner> HasDependent<'owner> for PanicOnMakeDependentAndOwnerDrop { + type Dependent = (); +} + +impl Owner for PanicOnMakeDependentAndOwnerDrop { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + panic_any(MyPayload(42)); + } +} + +#[test] +fn make_dependent_and_owner_drop_panic_handled() { + let owner_drop_called = Rc::new(RefCell::new(false)); + let owner_drop_called2 = Rc::clone(&owner_drop_called); + let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| { + Pair::new(PanicOnMakeDependentAndOwnerDrop(owner_drop_called2)); + })) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(payload, MyPayload(42)); + assert!(*owner_drop_called.borrow()); +} + +// dependent drop panics in into_owner +#[derive(Debug)] +struct PanicOnDepDropIntoOwner(Rc>); +impl Drop for PanicOnDepDropIntoOwner { + fn drop(&mut self) { + *self.0.borrow_mut() = true; + } +} + +struct PanicOnDepDropIntoOwnerDep; +impl Drop for PanicOnDepDropIntoOwnerDep { + fn drop(&mut self) { + panic_any(MyPayload(11)); + } +} +impl<'owner> HasDependent<'owner> for PanicOnDepDropIntoOwner { + type Dependent = PanicOnDepDropIntoOwnerDep; +} + +impl Owner for PanicOnDepDropIntoOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(PanicOnDepDropIntoOwnerDep) + } +} + +#[test] +fn dependent_drop_panic_handled_in_into_owner() { + let owner_drop_called = Rc::new(RefCell::new(false)); + let pair = Pair::new(PanicOnDepDropIntoOwner(Rc::clone(&owner_drop_called))); + let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| pair.into_owner())) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(payload, MyPayload(11)); + assert!(*owner_drop_called.borrow()); +} + +// dependent drop panics and owner drop panics in into_owner +#[derive(Debug)] +struct PanicOnDepAndOwnerDropIntoOwner(Rc>); +impl Drop for PanicOnDepAndOwnerDropIntoOwner { + fn drop(&mut self) { + *self.0.borrow_mut() = true; + panic!("ruh roh, raggy"); + } +} + +struct PanicOnDepAndOwnerDropIntoOwnerDep; +impl Drop for PanicOnDepAndOwnerDropIntoOwnerDep { + fn drop(&mut self) { + panic_any(MyPayload(1)); + } +} +impl<'owner> HasDependent<'owner> for PanicOnDepAndOwnerDropIntoOwner { + type Dependent = PanicOnDepAndOwnerDropIntoOwnerDep; +} + +impl Owner for PanicOnDepAndOwnerDropIntoOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(PanicOnDepAndOwnerDropIntoOwnerDep) + } +} + +#[test] +fn dependent_and_owner_drop_panic_handled_in_into_owner() { + let owner_drop_called = Rc::new(RefCell::new(false)); + let pair = Pair::new(PanicOnDepAndOwnerDropIntoOwner(Rc::clone( + &owner_drop_called, + ))); + let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| pair.into_owner())) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(payload, MyPayload(1)); + assert!(*owner_drop_called.borrow()); +} + +// dependent drop panics in pair drop +#[derive(Debug)] +struct PanicOnDepDropPairDrop(Rc>); +impl Drop for PanicOnDepDropPairDrop { + fn drop(&mut self) { + *self.0.borrow_mut() = true; + } +} + +struct PanicOnDepDropPairDropDep; +impl Drop for PanicOnDepDropPairDropDep { + fn drop(&mut self) { + panic_any(MyPayload(3)); + } +} +impl<'owner> HasDependent<'owner> for PanicOnDepDropPairDrop { + type Dependent = PanicOnDepDropPairDropDep; +} + +impl Owner for PanicOnDepDropPairDrop { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(PanicOnDepDropPairDropDep) + } +} + +#[test] +fn dependent_drop_panic_handled_in_pair_drop() { + let owner_drop_called = Rc::new(RefCell::new(false)); + let pair = Pair::new(PanicOnDepDropPairDrop(Rc::clone(&owner_drop_called))); + let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| drop(pair))) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(payload, MyPayload(3)); + assert!(*owner_drop_called.borrow()); +} + +// dependent drop panics and owner drop panics in into_owner +#[derive(Debug)] +struct PanicOnDepAndOwnerDropPairDrop(Rc>); +impl Drop for PanicOnDepAndOwnerDropPairDrop { + fn drop(&mut self) { + *self.0.borrow_mut() = true; + panic!("ruh roh, raggy"); + } +} + +struct PanicOnDepAndOwnerDropPairDropDep; +impl Drop for PanicOnDepAndOwnerDropPairDropDep { + fn drop(&mut self) { + panic_any(MyPayload(5)); + } +} +impl<'owner> HasDependent<'owner> for PanicOnDepAndOwnerDropPairDrop { + type Dependent = PanicOnDepAndOwnerDropPairDropDep; +} + +impl Owner for PanicOnDepAndOwnerDropPairDrop { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(PanicOnDepAndOwnerDropPairDropDep) + } +} + +#[test] +fn dependent_and_owner_drop_panic_handled_in_pair_drop() { + let owner_drop_called = Rc::new(RefCell::new(false)); + let pair = Pair::new(PanicOnDepAndOwnerDropPairDrop(Rc::clone( + &owner_drop_called, + ))); + let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| drop(pair))) + .unwrap_err() + .downcast() + .unwrap(); + + assert_eq!(payload, MyPayload(5)); + assert!(*owner_drop_called.borrow()); +} diff --git a/tests/unsized_owner.rs b/tests/unsized_owner.rs index 7ffa85c..34eaec5 100644 --- a/tests/unsized_owner.rs +++ b/tests/unsized_owner.rs @@ -30,7 +30,7 @@ impl Owner for Buff<[u8]> { } #[test] -fn basic_usage() { +fn unsized_owner() { let mut pair = Pair::new_from_box(Buff::new([2, 69, 42, 5, 6, 7, 8])); let owner: &Buff<[u8]> = pair.get_owner(); let dep: &(&[u8], usize) = pair.with_dependent(|dep| dep); From a196368d2e70b1318771c0510efd719cb008dfec Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 26 Feb 2025 22:25:02 -0500 Subject: [PATCH 032/110] added drop tests --- src/pair.rs | 18 +++++++++++ tests/basic_usage.rs | 29 ++++++++++------- tests/drop.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++ tests/tmp.rs | 2 ++ 4 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 tests/drop.rs diff --git a/src/pair.rs b/src/pair.rs index 3756c0b..ca62244 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -429,6 +429,24 @@ impl + ?Sized> Pair { /// The [`Drop`] implementation for [`Pair`] will drop both the dependent and /// the owner, in that order. +// +// NOTE(ichen): There are definitely some weird dropck things going on, but I do +// not believe they can lead to any unsoundness. Because of the signature of +// Pair, dropck think we access an O and do nothing with O::Dependent. It's +// right about O - we don't access it directly, but the dependent (which we do +// drop) might access an O in its drop. Unfortunately, the compiler is wrong +// about O::Dependent. It doesn't see any indication of O::Dependent in the +// signature for Pair (because we've type erased it), so dropck has no idea that +// we will drop an O::Dependent in our drop. +// +// This sounds like a problem, but I believe it is not. The signature of Owner +// and HasDependent enforce that the dependent only borrows the owner, or things +// which the owner also borrows. Additionally, the compiler will ensure that +// anything the owner borrows are valid until the pair's drop. Therefore, the +// dependent cannot contain any references which will be invalidated before the +// drop of the Pair. As far as I know, this is the only concern surrounding +// dropck not understanding the semantics of Pair, and cannot cause unsoundness +// for the reasons described above. impl Drop for Pair { fn drop(&mut self) { // Drop the dependent `Box<>::Dependent>` diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs index 7299dd5..951d3ac 100644 --- a/tests/basic_usage.rs +++ b/tests/basic_usage.rs @@ -54,9 +54,9 @@ fn basic_usage() { } #[test] -fn get_owner_stress_test() { - // Let's just do a bunch of `get_owner(..)`s interlaced with a bunch of - // other random stuff and see what MIRI thinks +fn basic_api_stress_test() { + // Let's just do a bunch of the basic API functions interlaced together and + // see what MIRI thinks let mut pair = Pair::new(Buff(String::from("This is a test of pair."))); let owner1 = pair.get_owner(); let owner2 = pair.get_owner(); @@ -65,16 +65,23 @@ fn get_owner_stress_test() { let owner4 = pair.get_owner(); let dep2 = pair.with_dependent(|dep| dep); println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{dep1:?}{dep2:?}"); - pair.with_dependent_mut(|dep| dep.push("hi")); + pair.with_dependent_mut(|dep| dep.push("hey")); + let owner1 = pair.get_owner(); + let dep1 = pair.with_dependent(|dep| dep); + let owner2 = pair.get_owner(); + let dep2 = pair.with_dependent(|dep| dep); + println!("{owner1:?}{owner2:?}{dep1:?}{dep2:?}"); + pair.with_dependent_mut(|dep| dep.push("what's up")); + pair.with_dependent_mut(|dep| dep.push("hello")); let owner1 = pair.get_owner(); let owner2 = pair.get_owner(); println!("{owner1:?}{owner2:?}"); - let pair2 = pair; - let owner1 = pair2.get_owner(); - let owner2 = pair2.get_owner(); - let owner3 = pair2.get_owner(); - let dep1 = pair2.with_dependent(|dep| dep); - let owner4 = pair2.get_owner(); - let dep2 = pair2.with_dependent(|dep| dep); + let new_pair = (|x| x)(std::convert::identity(pair)); + let owner1 = new_pair.get_owner(); + let owner2 = new_pair.get_owner(); + let owner3 = new_pair.get_owner(); + let dep1 = new_pair.with_dependent(|dep| dep); + let owner4 = new_pair.get_owner(); + let dep2 = new_pair.with_dependent(|dep| dep); println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{dep1:?}{dep2:?}"); } diff --git a/tests/drop.rs b/tests/drop.rs new file mode 100644 index 0000000..3a8e85f --- /dev/null +++ b/tests/drop.rs @@ -0,0 +1,74 @@ +use std::{cell::RefCell, convert::Infallible, rc::Rc}; + +use pair::{HasDependent, Owner, Pair}; + +#[derive(Debug)] +struct OnDrop(Rc>); +impl Drop for OnDrop { + fn drop(&mut self) { + *self.0.borrow_mut() = true; + } +} + +struct OnDropDep(Rc>); +impl Drop for OnDropDep { + fn drop(&mut self) { + *self.0.borrow_mut() = true; + } +} +impl<'owner> HasDependent<'owner> for OnDrop { + type Dependent = OnDropDep; +} + +impl Owner for OnDrop { + type Context<'a> = Rc>; + type Err = Infallible; + + fn make_dependent( + &self, + context: Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(OnDropDep(context)) + } +} + +#[test] +fn both_drops_called() { + let owner_drop_called = Rc::new(RefCell::new(false)); + let dep_drop_called = Rc::new(RefCell::new(false)); + let pair = Pair::new_with_context( + OnDrop(Rc::clone(&owner_drop_called)), + Rc::clone(&dep_drop_called), + ); + + assert!(!*owner_drop_called.borrow()); + assert!(!*dep_drop_called.borrow()); + + drop(pair); + + assert!(*owner_drop_called.borrow()); + assert!(*dep_drop_called.borrow()); +} + +#[test] +fn dep_drop_called_on_into_owner() { + let owner_drop_called = Rc::new(RefCell::new(false)); + let dep_drop_called = Rc::new(RefCell::new(false)); + let pair = Pair::new_with_context( + OnDrop(Rc::clone(&owner_drop_called)), + Rc::clone(&dep_drop_called), + ); + + assert!(!*owner_drop_called.borrow()); + assert!(!*dep_drop_called.borrow()); + + let owner = pair.into_owner(); + + assert!(!*owner_drop_called.borrow()); + assert!(*dep_drop_called.borrow()); + + drop(owner); + + assert!(*owner_drop_called.borrow()); + assert!(*dep_drop_called.borrow()); +} diff --git a/tests/tmp.rs b/tests/tmp.rs index 187bbc8..3ed89cf 100644 --- a/tests/tmp.rs +++ b/tests/tmp.rs @@ -1,3 +1,5 @@ +// TODO: dropck stuff + // #![allow(unused)] // use std::{convert::Infallible, marker::PhantomData}; From 4a3af8bef8474913129985aee7f1d99f75421653 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 28 Feb 2025 13:42:44 -0500 Subject: [PATCH 033/110] added test for Debug impl --- ci.sh | 2 +- tests/debug.rs | 172 +++++++++++++++++++++++++++++++++++++++++ tests/trybuild_test.rs | 2 +- 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 tests/debug.rs diff --git a/ci.sh b/ci.sh index c48c44a..3ef4a58 100755 --- a/ci.sh +++ b/ci.sh @@ -4,5 +4,5 @@ cargo test && \ cp Cargo.toml Cargo.toml.backup && \ trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ - cargo +nightly miri test -- --skip try_builds + MIRIFLAGS="-Zmiri-retag-fields" cargo +nightly miri test -- --skip nomiri ) diff --git a/tests/debug.rs b/tests/debug.rs new file mode 100644 index 0000000..783c548 --- /dev/null +++ b/tests/debug.rs @@ -0,0 +1,172 @@ +use std::{borrow::Cow, convert::Infallible, fmt::Debug}; + +use pair::{HasDependent, Owner}; + +mod real { + pub use pair::Pair; +} + +mod fake { + use std::fmt::Debug; + + use pair::{HasDependent, Owner}; + + #[derive(Debug)] + pub struct Pair<'a, O: Owner> + where + >::Dependent: Debug, + { + #[expect(dead_code, reason = "we actually care about the derived Debug")] + pub owner: O, + #[expect(dead_code, reason = "we actually care about the derived Debug")] + pub dependent: >::Dependent, + } +} + +fn debugs_match Owner = (), Err = Infallible> + Clone + Debug>(owner: O) +where + for<'any> >::Dependent: Debug, +{ + let Ok(dependent) = owner.make_dependent(()); + let pair_real = real::Pair::new(owner.clone()); + let pair_fake = fake::Pair { + owner: owner.clone(), + dependent, + }; + + // Normal debug (and pretty-print) + assert_eq!(format!("{pair_real:?}"), format!("{pair_fake:?}")); + assert_eq!(format!("{pair_real:#?}"), format!("{pair_fake:#?}")); + + // Hex integers (lowercase and uppercase) + // See: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits + assert_eq!(format!("{pair_real:x?}"), format!("{pair_fake:x?}")); + assert_eq!(format!("{pair_real:X?}"), format!("{pair_fake:X?}")); + assert_eq!(format!("{pair_real:#x?}"), format!("{pair_fake:#x?}")); + assert_eq!(format!("{pair_real:#X?}"), format!("{pair_fake:#X?}")); + + // Getting crazy with it (I'm not gonna test every combination, but I'm down + // to just throw a bunch of random stuff at it and make sure that works out) + // + // The "🦀^+#12.5?" means: ferris fill, center aligned, with sign, pretty + // printed, no "0" option integer formatting (would override fill/align), + // width 12, 5 digits of precision, debug formatted. + // + // See: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters + assert_eq!( + format!("{pair_real:🦀^+#12.5?}"), + format!("{pair_fake:🦀^+#12.5?}") + ); + assert_eq!( + format!("{pair_real:🦀^+#12.5?}"), + format!("{pair_fake:🦀^+#12.5?}") + ); +} + +macro_rules! debug_tests { + ( + $struct_or_enum:ident $name:ident $decl_body:tt $(; $semicolon_exists:vis)? + & $lt:lifetime $self_kw:ident => $dep_ty:ty : $dep_expr:expr ; + + $($owner:expr),+$(,)? + ) => { + #[derive(Debug, Clone)] + $struct_or_enum $name $decl_body $(; $semicolon_exists)? + + impl<$lt> HasDependent<$lt> for $name { + type Dependent = $dep_ty; + } + impl Owner for $name { + type Context<'a> = (); + type Err = Infallible; + fn make_dependent<'owner>( + &'owner $self_kw, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok($dep_expr) + } + } + $( + debugs_match($owner); + )+ + }; +} + +#[test] +fn debug_impls_match_derive_nomiri() { + debug_tests! { + struct O1(String); + &'owner self => &'owner str: &self.0; + + O1(String::new()), + O1(String::from("Hello, world!")), + } + + debug_tests! { + struct O2(i32); + &'owner self => &'owner i32: &self.0; + + O2(0), + O2(i32::MAX), + } + + debug_tests! { + struct O3(char); + &'owner self => Option>: + Some(std::iter::repeat(()).take(self.0 as usize % 20).collect()); + + O3('🦀'), O3(' '), O3('!'), O3('a'), O3('A'), O3('*'), O3('-'), O3('~'), + O3('\\'), O3('"'), O3('\x00'), O3('\n'), O3('\t'), O3('\''), O3('&'), + } + + debug_tests! { + struct O4(f64); + &'owner self => u8: + self.0 + .to_be_bytes() + .iter() + .copied() + .reduce(|acc, e| u8::wrapping_add(acc, e)) + .unwrap(); + + O4(0.0), O4(-0.0), O4(1.0), O4(3.14), O4(f64::NAN), O4(f64::INFINITY), + O4(f64::NEG_INFINITY), O4(f64::EPSILON), + } + + debug_tests! { + struct O5([isize; 42]); + &'owner self => &'owner [isize]: &self.0[5..24]; + + O5([0; 42]), O5([69; 42]), + } + + debug_tests! { + struct O6(Vec, [char; 6], Option<()>); + &'owner self => (&'owner char, Cow<'owner, str>, Option<&'owner ()>): + (&self.1[1], String::from_utf8_lossy(&self.0), self.2.as_ref()); + + O6(b"Foo bar bat baz".to_vec(), ['f', 'o', 'o', 'b', 'a', 'r'], Some(())), + O6(b"My friend, I wish that I could say that I agree".to_vec(), ['g', 't', 'w', 'w', 'o', 'a'], None), + } + + debug_tests! { + enum O7 { + Foo, + Bar(u8), + Bat(String), + Baz { + name: String, + age: u8, + }, + } + &'owner self => Option>: match self { + O7::Foo => None, + O7::Bar(n) => Some(Cow::Owned(format!("{n}"))), + O7::Bat(s) => Some(Cow::Borrowed(s)), + O7::Baz { name, age } => Some(Cow::Owned(format!("{name} is {age}"))), + }; + + O7::Foo, O7::Bar(0), O7::Bar(1), O7::Bar(42), O7::Bar(69), O7::Bar(u8::MAX), + O7::Bat(String::from("testing")), O7::Baz { name: String::from("Hermes"), age: u8::MAX }, + } +} diff --git a/tests/trybuild_test.rs b/tests/trybuild_test.rs index 2d7d781..65b4bbd 100644 --- a/tests/trybuild_test.rs +++ b/tests/trybuild_test.rs @@ -1,5 +1,5 @@ #[test] -fn try_builds() { +fn try_builds_nomiri() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trybuild_fails/*.rs"); } From 698526b218c96408ff94d67aefa35fa769cacb73 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 28 Feb 2025 13:47:03 -0500 Subject: [PATCH 034/110] moved sealed stuff to bottom of owner.rs --- src/owner.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/owner.rs b/src/owner.rs index 02fdce4..d05aa19 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -1,18 +1,3 @@ -/// Used to prevent implementors of [`HasDependent`] from overriding the -/// `ForImpliedBounds` generic type from its default. -mod sealed { - /// The `ForImpliedBounds` generic type for - /// [`HasDependent`](super::HasDependent) should not be overridden from its - /// default. - pub trait Sealed {} - /// The `ForImpliedBounds` generic type for - /// [`HasDependent`](super::HasDependent) should not be overridden from its - /// default. - pub struct Bounds(std::marker::PhantomData); - impl Sealed for Bounds {} -} -use sealed::{Bounds, Sealed}; - /// Defines the dependent type for the [`Owner`] trait. /// /// Semantically, you can think of this like a lifetime Generic Associated Type @@ -67,3 +52,18 @@ pub trait Owner: for<'any> HasDependent<'any> { context: Self::Context<'_>, ) -> Result<>::Dependent, Self::Err>; } + +/// Used to prevent implementors of [`HasDependent`] from overriding the +/// `ForImpliedBounds` generic type from its default. +mod sealed { + /// The `ForImpliedBounds` generic type for + /// [`HasDependent`](super::HasDependent) should not be overridden from its + /// default. + pub trait Sealed {} + /// The `ForImpliedBounds` generic type for + /// [`HasDependent`](super::HasDependent) should not be overridden from its + /// default. + pub struct Bounds(std::marker::PhantomData); + impl Sealed for Bounds {} +} +use sealed::{Bounds, Sealed}; From bdc8fbe8b992bb52381843c016c8e0e42b88cc39 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 28 Feb 2025 16:50:09 -0500 Subject: [PATCH 035/110] updated drop tests to check drop order --- tests/drop.rs | 55 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/tests/drop.rs b/tests/drop.rs index 3a8e85f..3fdee45 100644 --- a/tests/drop.rs +++ b/tests/drop.rs @@ -3,51 +3,61 @@ use std::{cell::RefCell, convert::Infallible, rc::Rc}; use pair::{HasDependent, Owner, Pair}; #[derive(Debug)] -struct OnDrop(Rc>); -impl Drop for OnDrop { +struct OnDrop { + value: Rc>, + f: fn(&mut T), +} +impl Drop for OnDrop { fn drop(&mut self) { - *self.0.borrow_mut() = true; + (self.f)(&mut self.value.borrow_mut()) } } -struct OnDropDep(Rc>); -impl Drop for OnDropDep { +struct OnDropDep { + value: Rc>, + f: fn(&mut T), +} +impl Drop for OnDropDep { fn drop(&mut self) { - *self.0.borrow_mut() = true; + (self.f)(&mut self.value.borrow_mut()) } } -impl<'owner> HasDependent<'owner> for OnDrop { - type Dependent = OnDropDep; +impl<'owner, T> HasDependent<'owner> for OnDrop { + type Dependent = OnDropDep; } -impl Owner for OnDrop { - type Context<'a> = Rc>; +impl Owner for OnDrop { + type Context<'a> = (Rc>, fn(&mut T)); type Err = Infallible; fn make_dependent( &self, context: Self::Context<'_>, ) -> Result<>::Dependent, Self::Err> { - Ok(OnDropDep(context)) + Ok(OnDropDep { + value: context.0, + f: context.1, + }) } } #[test] fn both_drops_called() { - let owner_drop_called = Rc::new(RefCell::new(false)); - let dep_drop_called = Rc::new(RefCell::new(false)); + // Using a common Vec instead of two bools, since we care about drop order + let drops_called = Rc::new(RefCell::new(Vec::new())); let pair = Pair::new_with_context( - OnDrop(Rc::clone(&owner_drop_called)), - Rc::clone(&dep_drop_called), + OnDrop { + value: Rc::clone(&drops_called), + f: |v| v.push("owner"), + }, + (Rc::clone(&drops_called), |v| v.push("dep")), ); - assert!(!*owner_drop_called.borrow()); - assert!(!*dep_drop_called.borrow()); + assert_eq!(*drops_called.borrow(), [] as [&str; 0]); drop(pair); - assert!(*owner_drop_called.borrow()); - assert!(*dep_drop_called.borrow()); + assert_eq!(*drops_called.borrow(), ["dep", "owner"]); } #[test] @@ -55,8 +65,11 @@ fn dep_drop_called_on_into_owner() { let owner_drop_called = Rc::new(RefCell::new(false)); let dep_drop_called = Rc::new(RefCell::new(false)); let pair = Pair::new_with_context( - OnDrop(Rc::clone(&owner_drop_called)), - Rc::clone(&dep_drop_called), + OnDrop { + value: Rc::clone(&owner_drop_called), + f: |d| *d = true, + }, + (Rc::clone(&dep_drop_called), |d| *d = true), ); assert!(!*owner_drop_called.borrow()); From cbde27b8dd5af147b3a912b62c2c3a0b9c28957e Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 28 Feb 2025 18:13:13 -0500 Subject: [PATCH 036/110] added to-do list for tests --- tests/to-do.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/to-do.md diff --git a/tests/to-do.md b/tests/to-do.md new file mode 100644 index 0000000..cc1a955 --- /dev/null +++ b/tests/to-do.md @@ -0,0 +1,37 @@ +# Test to-do list +- ZSTs + - Test with zero-sized owner + - Test with zero-sized dependent + - Test with both zero-sized owner and dependent +- Test `Default` implementation creates expected values +- Test dependent borrowing from multiple parts of owner +- Weird nesting + - Test `Pair` that owns another `Pair` + - Test with circular references +- Variance stuff + - Test with covariant lifetime-parameterized dependent (like string slices) + - Test with invariant lifetime-parameterized dependent (like `&mut T`) + - Test with contravariant lifetime-parameterized dependent + - Verify that compiler prevents extracting dependent with invariant types +- Interior mutability + - Test with interior-mutable owner + - Test with interior-mutable dependent + - Test with interior-mutable for both owner and dependent +- Test with trait object owner (requires dyn Trait + Sized) +- Trybuild tests + - Attempts to extract the dependent outside `with_dependent`'s closure + - Verify dependent cannot outlive the pair. This means writing code that + attempts to extract and store a reference to the dependent that would + outlive the Pair itself + - Test that the compiler prevents use of borrowed data after into_owner() +- Concurrency + - Sending ownership of a Pair between threads through channels + - Sharing a Pair via &Pair<_> across multiple threads + - Sending and sharing a Pair via Arc> across multiple threads + - Wrapping a Pair in Arc and concurrently racily accessing from + multiple threads + - Verify pair is !Send when owner is !Send + - Verify pair is !Send when dependent is !Send + - Verify pair is !Sync when owner is !Sync + - Verify pair is !Sync when dependent is !Sync +- Run with asan, threadsan, loom, and some kind of memory leak detector From bd80a3d27a5afe00047ee702778bd9b5ea086380 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 28 Feb 2025 21:24:37 -0500 Subject: [PATCH 037/110] updated test to-do documentation --- src/lib.rs | 14 -------------- tests/to-do.md | 6 ++++-- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 62b0577..77890a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,17 +11,3 @@ mod pair; pub use owner::{HasDependent, Owner}; pub use pair::Pair; - -// TODO: -// - Extensive testing, including: -// - Property-based testing -// - Some kind of "type level fuzzing"?? -// - Test against known weird cases, like: -// - Types with all different kinds of variances -// - Weird drop impls (including "Oisann" types (jonhoo reference)) -// - Impure / weird Deref impls -// - Interior mutable types -// - https://docs.rs/trybuild test cases demonstrating that misuses of the -// API don't compile -// - All under MIRI -// - Soundness audits by experienced dark arts rustaceans diff --git a/tests/to-do.md b/tests/to-do.md index cc1a955..86b0747 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -17,9 +17,11 @@ - Test with interior-mutable owner - Test with interior-mutable dependent - Test with interior-mutable for both owner and dependent -- Test with trait object owner (requires dyn Trait + Sized) +- Test with trait object owner - Trybuild tests - - Attempts to extract the dependent outside `with_dependent`'s closure + - Attempts to extract the dependent outside `with_dependent`'s closure for + invariant and contravariant dependent + - Attempts to extract the dependent outside `with_dependent_mut` - Verify dependent cannot outlive the pair. This means writing code that attempts to extract and store a reference to the dependent that would outlive the Pair itself From 24336a3631e7a32e5417e2a65d887548a7b17656 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 28 Feb 2025 21:32:02 -0500 Subject: [PATCH 038/110] added ZST tests --- tests/to-do.md | 4 --- tests/zst.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 tests/zst.rs diff --git a/tests/to-do.md b/tests/to-do.md index 86b0747..d0c6b4f 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,8 +1,4 @@ # Test to-do list -- ZSTs - - Test with zero-sized owner - - Test with zero-sized dependent - - Test with both zero-sized owner and dependent - Test `Default` implementation creates expected values - Test dependent borrowing from multiple parts of owner - Weird nesting diff --git a/tests/zst.rs b/tests/zst.rs new file mode 100644 index 0000000..d79165c --- /dev/null +++ b/tests/zst.rs @@ -0,0 +1,87 @@ +use pair::{HasDependent, Owner, Pair}; +use std::convert::Infallible; + +// ZST owner with non-ZST dependent +#[derive(Debug)] +struct ZstOwner; + +#[derive(Debug, PartialEq)] +struct NonZstDependent(i32); + +impl<'owner> HasDependent<'owner> for ZstOwner { + type Dependent = NonZstDependent; +} + +impl Owner for ZstOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent(&self, (): Self::Context<'_>) -> Result { + Ok(NonZstDependent(42)) + } +} + +#[test] +fn test_zst_owner() { + let pair = Pair::new(ZstOwner); + + assert_eq!(size_of_val(pair.get_owner()), 0); + pair.with_dependent(|dep| { + assert_eq!(*dep, NonZstDependent(42)); + }); +} + +// Non-ZST owner with ZST dependent +#[derive(Debug, PartialEq)] +struct NonZstOwner(i32); + +#[derive(Debug)] +struct ZstDependent; + +impl<'owner> HasDependent<'owner> for NonZstOwner { + type Dependent = ZstDependent; +} + +impl Owner for NonZstOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent(&self, (): Self::Context<'_>) -> Result { + Ok(ZstDependent) + } +} + +#[test] +fn test_zst_dependent() { + let pair = Pair::new(NonZstOwner(123)); + + assert_eq!(*pair.get_owner(), NonZstOwner(123)); + pair.with_dependent(|dep| { + assert_eq!(size_of_val(dep), 0); + }); +} + +// Both owner and dependent are ZSTs +struct BothZstOwner; + +impl<'owner> HasDependent<'owner> for BothZstOwner { + type Dependent = ZstDependent; +} + +impl Owner for BothZstOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent(&self, _: Self::Context<'_>) -> Result { + Ok(ZstDependent) + } +} + +#[test] +fn test_both_zst() { + let pair = Pair::new(BothZstOwner); + assert_eq!(size_of_val(pair.get_owner()), 0); + pair.with_dependent(|dep| { + assert_eq!(size_of_val(dep), 0); + }); +} From 0e5dcdeabc75e7a4d1c7f7e1c7705623fdaff934 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 28 Feb 2025 21:45:12 -0500 Subject: [PATCH 039/110] implemented default tests --- tests/default.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/to-do.md | 1 - 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/default.rs diff --git a/tests/default.rs b/tests/default.rs new file mode 100644 index 0000000..0e0240c --- /dev/null +++ b/tests/default.rs @@ -0,0 +1,56 @@ +use pair::{HasDependent, Owner, Pair}; +use std::{convert::Infallible, fmt::Debug}; + +#[derive(Default, Debug, PartialEq)] +struct DefaultOwner(T); + +impl<'owner, T> HasDependent<'owner> for DefaultOwner { + type Dependent = &'owner T; +} + +impl Owner for DefaultOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent<'owner>(&'owner self, (): Self::Context<'_>) -> Result<&'owner T, Self::Err> { + Ok(&self.0) + } +} + +fn test_default() { + let pair: Pair> = Pair::default(); + + assert_eq!(pair.get_owner().0, T::default()); + pair.with_dependent(|dep| { + assert_eq!(dep, &&T::default()); + }); + + assert_eq!(pair.into_owner().0, T::default()); +} + +#[test] +fn test_default_implementation() { + test_default::<()>(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::(); + test_default::<&str>(); + test_default::<[u8; 10]>(); + test_default::>(); + test_default::>(); + test_default::(); + test_default::>(); +} diff --git a/tests/to-do.md b/tests/to-do.md index d0c6b4f..f79bbbe 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,5 +1,4 @@ # Test to-do list -- Test `Default` implementation creates expected values - Test dependent borrowing from multiple parts of owner - Weird nesting - Test `Pair` that owns another `Pair` From c11c1bc7f38d399bd4dba175a3902ae727a13b53 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 1 Mar 2025 15:54:46 -0500 Subject: [PATCH 040/110] added multiborrow owner test --- tests/multiborrow.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++ tests/to-do.md | 1 - 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/multiborrow.rs diff --git a/tests/multiborrow.rs b/tests/multiborrow.rs new file mode 100644 index 0000000..0ad77f4 --- /dev/null +++ b/tests/multiborrow.rs @@ -0,0 +1,59 @@ +use pair::{HasDependent, Owner, Pair}; +use std::convert::Infallible; + +#[derive(PartialEq)] +struct MultiPartOwner { + field1: String, + field2: Vec, + field3: bool, + field4: Box, +} + +struct MultiPartDependent<'a> { + string_ref: &'a str, + int_ref: &'a i32, + bool_ref: &'a bool, +} + +impl<'owner> HasDependent<'owner> for MultiPartOwner { + type Dependent = MultiPartDependent<'owner>; +} + +impl Owner for MultiPartOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent<'owner>( + &'owner self, + _: Self::Context<'_>, + ) -> Result, Self::Err> { + Ok(MultiPartDependent { + string_ref: &self.field1, + int_ref: &self.field2[0], + bool_ref: &self.field3, + }) + } +} + +#[test] +fn test_multiple_borrows() { + let owner = MultiPartOwner { + field1: "Hello, world!".to_string(), + field2: vec![3, 1, 4, 1, 5], + field3: true, + field4: Box::default(), + }; + + let pair = Pair::new(owner); + + pair.with_dependent(|dep| { + assert_eq!(dep.string_ref, "Hello, world!"); + assert_eq!(*dep.int_ref, 3); + assert_eq!(*dep.bool_ref, true); + }); + + let owner = pair.into_owner(); + assert_eq!(owner.field1, "Hello, world!"); + assert_eq!(owner.field2, [3, 1, 4, 1, 5]); + assert_eq!(owner.field3, true); +} diff --git a/tests/to-do.md b/tests/to-do.md index f79bbbe..3e2bb12 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,5 +1,4 @@ # Test to-do list -- Test dependent borrowing from multiple parts of owner - Weird nesting - Test `Pair` that owns another `Pair` - Test with circular references From b858995710e6451b02bbc51dd5f3261240e49800 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 1 Mar 2025 16:01:55 -0500 Subject: [PATCH 041/110] simplified signatures --- tests/debug.rs | 6 +++--- tests/default.rs | 5 ++++- tests/multiborrow.rs | 8 ++++---- tests/zst.rs | 5 ++++- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/debug.rs b/tests/debug.rs index 783c548..4d3f0f8 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -79,10 +79,10 @@ macro_rules! debug_tests { impl Owner for $name { type Context<'a> = (); type Err = Infallible; - fn make_dependent<'owner>( - &'owner $self_kw, + fn make_dependent( + &$self_kw, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Err> { Ok($dep_expr) } } diff --git a/tests/default.rs b/tests/default.rs index 0e0240c..21c4162 100644 --- a/tests/default.rs +++ b/tests/default.rs @@ -12,7 +12,10 @@ impl Owner for DefaultOwner { type Context<'a> = (); type Err = Infallible; - fn make_dependent<'owner>(&'owner self, (): Self::Context<'_>) -> Result<&'owner T, Self::Err> { + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { Ok(&self.0) } } diff --git a/tests/multiborrow.rs b/tests/multiborrow.rs index 0ad77f4..371679e 100644 --- a/tests/multiborrow.rs +++ b/tests/multiborrow.rs @@ -23,10 +23,10 @@ impl Owner for MultiPartOwner { type Context<'a> = (); type Err = Infallible; - fn make_dependent<'owner>( - &'owner self, - _: Self::Context<'_>, - ) -> Result, Self::Err> { + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { Ok(MultiPartDependent { string_ref: &self.field1, int_ref: &self.field2[0], diff --git a/tests/zst.rs b/tests/zst.rs index d79165c..9c27b40 100644 --- a/tests/zst.rs +++ b/tests/zst.rs @@ -72,7 +72,10 @@ impl Owner for BothZstOwner { type Context<'a> = (); type Err = Infallible; - fn make_dependent(&self, _: Self::Context<'_>) -> Result { + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { Ok(ZstDependent) } } From 65a72b7c5daa7430adbcbad30c1b54a40c2b3a75 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 1 Mar 2025 17:13:47 -0500 Subject: [PATCH 042/110] added nested pair test --- tests/nested_pair.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/nested_pair.rs diff --git a/tests/nested_pair.rs b/tests/nested_pair.rs new file mode 100644 index 0000000..492568c --- /dev/null +++ b/tests/nested_pair.rs @@ -0,0 +1,78 @@ +use pair::{HasDependent, Owner, Pair}; +use std::convert::Infallible; + +#[derive(Debug)] +struct SimpleOwner(i32); + +impl<'owner> HasDependent<'owner> for SimpleOwner { + type Dependent = &'owner i32; +} + +impl Owner for SimpleOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(&self.0) + } +} + +// Pair that owns another Pair +#[derive(Debug)] +struct PairOwner { + value: i32, + inner_pair: Pair, +} + +#[derive(Debug)] +struct PairOwnerDependent<'owner> { + value_ref: &'owner i32, + inner_pair_ref: &'owner Pair, +} + +impl<'owner> HasDependent<'owner> for PairOwner { + type Dependent = PairOwnerDependent<'owner>; +} + +impl Owner for PairOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(PairOwnerDependent { + value_ref: &self.value, + inner_pair_ref: &self.inner_pair, + }) + } +} + +#[test] +fn test_pair_owning_pair() { + let inner_pair = Pair::new(SimpleOwner(42)); + + let pair_owner = PairOwner { + value: 100, + inner_pair, + }; + + let outer_pair = Pair::new(pair_owner); + + outer_pair.with_dependent(|outer_dep| { + assert_eq!(*outer_dep.value_ref, 100); + + // Access the inner pair's owner and dependent + assert_eq!(outer_dep.inner_pair_ref.get_owner().0, 42); + assert_eq!(outer_dep.inner_pair_ref.with_dependent(|dep| dep), &&42); + }); + + let PairOwner { value, inner_pair } = outer_pair.into_owner(); + assert_eq!(value, 100); + assert_eq!(inner_pair.get_owner().0, 42); + assert_eq!(inner_pair.with_dependent(|dep| dep), &&42); +} From 886472669b75cf0418f661333db43fe08240a68a Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 1 Mar 2025 17:14:24 -0500 Subject: [PATCH 043/110] updated to-do list --- tests/to-do.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/to-do.md b/tests/to-do.md index 3e2bb12..8378a53 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,7 +1,4 @@ # Test to-do list -- Weird nesting - - Test `Pair` that owns another `Pair` - - Test with circular references - Variance stuff - Test with covariant lifetime-parameterized dependent (like string slices) - Test with invariant lifetime-parameterized dependent (like `&mut T`) From a4c27cf42353c26ce34f96d45b101578d883d41c Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 17:34:07 -0500 Subject: [PATCH 044/110] added variance related tests --- tests/contravariance.rs | 42 +++++++++++++++++++ tests/to-do.md | 5 --- .../contravariance_violation.rs | 37 ++++++++++++++++ .../contravariance_violation.stderr | 13 ++++++ .../invariant_dep_extraction.rs | 28 +++++++++++++ .../invariant_dep_extraction.stderr | 12 ++++++ 6 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 tests/contravariance.rs create mode 100644 tests/trybuild_fails/contravariance_violation.rs create mode 100644 tests/trybuild_fails/contravariance_violation.stderr create mode 100644 tests/trybuild_fails/invariant_dep_extraction.rs create mode 100644 tests/trybuild_fails/invariant_dep_extraction.stderr diff --git a/tests/contravariance.rs b/tests/contravariance.rs new file mode 100644 index 0000000..57ddbc4 --- /dev/null +++ b/tests/contravariance.rs @@ -0,0 +1,42 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +struct ContraOwner; + +impl<'owner> HasDependent<'owner> for ContraOwner { + type Dependent = fn(&'owner u32); +} +impl Owner for ContraOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(|_| {}) + } +} + +fn contravariant_example<'self_borrow>(pair: &'self_borrow Pair) { + // The with_dependent function requires that the provided closure works for + // *any* dependent lifetime. Because of that, our closure cannot assume that + // the function pointer it's given can be called with any specific short + // lifetime. In fact, the *only* lifetime we can assume the function pointer + // requires is 'static - this is okay since we can give a 'static reference + // to a function pointer that expects any shorter lifetime (since &'a u32 is + // covariant in 'a). But any lifetime potentially less than 'static might + // not live as long as the body of the function pointer was allowed to + // assume - therefore, this cannot compile: + // let f: &'self_borrow for<'a> fn(&'a u32) = pair.with_dependent(|f| f); + // But this can: + let f: &'self_borrow fn(&'static u32) = pair.with_dependent(|f| f); + + f(&42); +} + +#[test] +fn examples() { + contravariant_example(&Pair::new(ContraOwner)); +} diff --git a/tests/to-do.md b/tests/to-do.md index 8378a53..21fbff4 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,9 +1,4 @@ # Test to-do list -- Variance stuff - - Test with covariant lifetime-parameterized dependent (like string slices) - - Test with invariant lifetime-parameterized dependent (like `&mut T`) - - Test with contravariant lifetime-parameterized dependent - - Verify that compiler prevents extracting dependent with invariant types - Interior mutability - Test with interior-mutable owner - Test with interior-mutable dependent diff --git a/tests/trybuild_fails/contravariance_violation.rs b/tests/trybuild_fails/contravariance_violation.rs new file mode 100644 index 0000000..096a639 --- /dev/null +++ b/tests/trybuild_fails/contravariance_violation.rs @@ -0,0 +1,37 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +struct ContraOwner; + +impl<'owner> HasDependent<'owner> for ContraOwner { + type Dependent = fn(&'owner u32); +} +impl Owner for ContraOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(|_| {}) + } +} + +fn contravariant_example<'self_borrow>(pair: &'self_borrow Pair) { + // The with_dependent function requires that the provided closure works for + // *any* dependent lifetime. Because of that, our closure cannot assume that + // the function pointer it's given can be called with any specific short + // lifetime. In fact, the *only* lifetime we can assume the function pointer + // requires is 'static - this is okay since we can give a 'static reference + // to a function pointer that expects any shorter lifetime (since &'a u32 is + // covariant in 'a). But any lifetime potentially less than 'static might + // not live as long as the body of the function pointer was allowed to + // assume - therefore, this cannot compile: + let _: &'self_borrow for<'a> fn(&'a u32) = pair.with_dependent(|f| f); +} + +fn main() { + contravariant_example(&Pair::new(ContraOwner)); +} diff --git a/tests/trybuild_fails/contravariance_violation.stderr b/tests/trybuild_fails/contravariance_violation.stderr new file mode 100644 index 0000000..574d846 --- /dev/null +++ b/tests/trybuild_fails/contravariance_violation.stderr @@ -0,0 +1,13 @@ +error[E0308]: mismatched types + --> tests/trybuild_fails/contravariance_violation.rs:32:48 + | +32 | let _: &'self_borrow for<'a> fn(&'a u32) = pair.with_dependent(|f| f); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other + | + = note: expected reference `&for<'a> fn(&'a u32)` + found reference `&fn(&u32)` +note: the lifetime requirement is introduced here + --> src/pair.rs + | + | F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T, + | ^ diff --git a/tests/trybuild_fails/invariant_dep_extraction.rs b/tests/trybuild_fails/invariant_dep_extraction.rs new file mode 100644 index 0000000..d96aaa3 --- /dev/null +++ b/tests/trybuild_fails/invariant_dep_extraction.rs @@ -0,0 +1,28 @@ +use std::{cell::Cell, convert::Infallible, marker::PhantomData}; + +use pair::{HasDependent, Owner, Pair}; + +struct InvarOwner; + +impl<'owner> HasDependent<'owner> for InvarOwner { + type Dependent = PhantomData>; +} + +impl Owner for InvarOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(PhantomData) + } +} + +fn main() { + let pair: Pair = Pair::new(InvarOwner); + + // This should fail to compile + pair.with_dependent(|dep| dep); +} diff --git a/tests/trybuild_fails/invariant_dep_extraction.stderr b/tests/trybuild_fails/invariant_dep_extraction.stderr new file mode 100644 index 0000000..a62bcc5 --- /dev/null +++ b/tests/trybuild_fails/invariant_dep_extraction.stderr @@ -0,0 +1,12 @@ +error[E0597]: `pair` does not live long enough + --> tests/trybuild_fails/invariant_dep_extraction.rs:27:5 + | +24 | let pair: Pair = Pair::new(InvarOwner); + | ---- binding `pair` declared here +... +27 | pair.with_dependent(|dep| dep); + | ^^^^ --- returning this value requires that `pair` is borrowed for `'static` + | | + | borrowed value does not live long enough +28 | } + | - `pair` dropped here while still borrowed From 9ed874c5b68661e13b0d0735ea5fba458b20a4dd Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 17:37:30 -0500 Subject: [PATCH 045/110] updated test to-do list --- tests/to-do.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/to-do.md b/tests/to-do.md index 21fbff4..5d0eedc 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -5,8 +5,6 @@ - Test with interior-mutable for both owner and dependent - Test with trait object owner - Trybuild tests - - Attempts to extract the dependent outside `with_dependent`'s closure for - invariant and contravariant dependent - Attempts to extract the dependent outside `with_dependent_mut` - Verify dependent cannot outlive the pair. This means writing code that attempts to extract and store a reference to the dependent that would From a9b3a39d6fc3ef53bace24ab55f58e9f440ed30d Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 17:53:44 -0500 Subject: [PATCH 046/110] added dynamic trait owner test --- tests/dyn_trait_owner.rs | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/dyn_trait_owner.rs diff --git a/tests/dyn_trait_owner.rs b/tests/dyn_trait_owner.rs new file mode 100644 index 0000000..7a71793 --- /dev/null +++ b/tests/dyn_trait_owner.rs @@ -0,0 +1,43 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +trait MyTrait { + fn get(&self) -> &i32; +} + +struct MyConcrete(i32); + +impl MyTrait for MyConcrete { + fn get(&self) -> &i32 { + &self.0 + } +} + +impl<'owner> HasDependent<'owner> for dyn MyTrait { + type Dependent = &'owner i32; +} +impl Owner for dyn MyTrait { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(self.get()) + } +} + +#[test] +fn dyn_trait_owner() { + let pair = Pair::new_from_box(Box::new(MyConcrete(69)) as Box); + let owner: &dyn MyTrait = pair.get_owner(); + let dep: &i32 = pair.with_dependent(|dep| dep); + + assert_eq!(owner.get(), &69); + assert_eq!(dep, &69); + + let owner: Box = pair.into_boxed_owner(); + assert_eq!(owner.get(), &69); +} From 0aecf20dc7008b50f3816dfcb3de572f76e4d3af Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 17:54:16 -0500 Subject: [PATCH 047/110] updated test to-do list --- tests/to-do.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/to-do.md b/tests/to-do.md index 5d0eedc..9aefd5e 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -3,7 +3,6 @@ - Test with interior-mutable owner - Test with interior-mutable dependent - Test with interior-mutable for both owner and dependent -- Test with trait object owner - Trybuild tests - Attempts to extract the dependent outside `with_dependent_mut` - Verify dependent cannot outlive the pair. This means writing code that From b44e981bfb6cde7794065e8c494824699dc8b287 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 18:03:31 -0500 Subject: [PATCH 048/110] cleaned up syntax --- tests/contravariance.rs | 6 +++--- tests/dyn_trait_owner.rs | 6 +++--- tests/trybuild_fails/contravariance_violation.rs | 6 +++--- tests/trybuild_fails/invariant_dep_extraction.rs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/contravariance.rs b/tests/contravariance.rs index 57ddbc4..923a2f3 100644 --- a/tests/contravariance.rs +++ b/tests/contravariance.rs @@ -11,10 +11,10 @@ impl Owner for ContraOwner { type Context<'a> = (); type Err = Infallible; - fn make_dependent<'owner>( - &'owner self, + fn make_dependent( + &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Err> { Ok(|_| {}) } } diff --git a/tests/dyn_trait_owner.rs b/tests/dyn_trait_owner.rs index 7a71793..5e2b19e 100644 --- a/tests/dyn_trait_owner.rs +++ b/tests/dyn_trait_owner.rs @@ -21,10 +21,10 @@ impl Owner for dyn MyTrait { type Context<'a> = (); type Err = Infallible; - fn make_dependent<'owner>( - &'owner self, + fn make_dependent( + &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Err> { Ok(self.get()) } } diff --git a/tests/trybuild_fails/contravariance_violation.rs b/tests/trybuild_fails/contravariance_violation.rs index 096a639..09c08c9 100644 --- a/tests/trybuild_fails/contravariance_violation.rs +++ b/tests/trybuild_fails/contravariance_violation.rs @@ -11,10 +11,10 @@ impl Owner for ContraOwner { type Context<'a> = (); type Err = Infallible; - fn make_dependent<'owner>( - &'owner self, + fn make_dependent( + &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Err> { Ok(|_| {}) } } diff --git a/tests/trybuild_fails/invariant_dep_extraction.rs b/tests/trybuild_fails/invariant_dep_extraction.rs index d96aaa3..3df3c2a 100644 --- a/tests/trybuild_fails/invariant_dep_extraction.rs +++ b/tests/trybuild_fails/invariant_dep_extraction.rs @@ -12,10 +12,10 @@ impl Owner for InvarOwner { type Context<'a> = (); type Err = Infallible; - fn make_dependent<'owner>( - &'owner self, + fn make_dependent( + &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Err> { Ok(PhantomData) } } From dee71bb2312460dad50bc7b5eb9e17135938b8a9 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 18:09:30 -0500 Subject: [PATCH 049/110] added interior mutability tests --- tests/interior_mutability.rs | 128 +++++++++++++++++++++++++++++++++++ tests/to-do.md | 4 -- 2 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 tests/interior_mutability.rs diff --git a/tests/interior_mutability.rs b/tests/interior_mutability.rs new file mode 100644 index 0000000..b3f4fce --- /dev/null +++ b/tests/interior_mutability.rs @@ -0,0 +1,128 @@ +use pair::{HasDependent, Owner, Pair}; +use std::cell::{Cell, RefCell}; +use std::convert::Infallible; + +// Test with interior-mutable owner +struct InteriorMutableOwner { + value: RefCell, +} + +impl<'owner> HasDependent<'owner> for InteriorMutableOwner { + type Dependent = &'owner RefCell; +} + +impl Owner for InteriorMutableOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(&self.value) + } +} + +#[test] +fn test_interior_mutable_owner() { + let pair = Pair::new(InteriorMutableOwner { + value: RefCell::new(42), + }); + + // Mutate the owner's value through the RefCell + *pair.get_owner().value.borrow_mut() = 100; + + // Verify the change is visible to the dependent + assert_eq!(*pair.with_dependent(|dep| dep).borrow(), 100); + + // Mutate the owner's value through the dependent + *pair.with_dependent(|dep| dep).borrow_mut() = 210; + + // Verify the change is visible to the owner + assert_eq!(*pair.get_owner().value.borrow(), 210); +} + +// Test with interior-mutable dependent +struct RegularOwner { + value: i32, +} + +struct InteriorMutableDependent<'a> { + value_cell: Cell, + original_ref: &'a i32, +} + +impl<'owner> HasDependent<'owner> for RegularOwner { + type Dependent = InteriorMutableDependent<'owner>; +} + +impl Owner for RegularOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(InteriorMutableDependent { + value_cell: Cell::new(self.value), + original_ref: &self.value, + }) + } +} + +#[test] +fn test_interior_mutable_dependent() { + let pair = Pair::new(RegularOwner { value: 42 }); + + // Mutate the dependent's Cell value + pair.with_dependent(|dep| dep.value_cell.set(100)); + assert_eq!(pair.with_dependent(|dep| dep).value_cell.get(), 100); + // The RegularOwner's original value should be unchanged + assert_eq!(pair.with_dependent(|dep| dep).original_ref, &42); +} + +// Test with interior-mutable owner and dependent +struct BothInteriorMutableOwner { + value: RefCell, +} + +struct BothInteriorMutableDependent<'a> { + owner_ref: &'a RefCell, + local_value: Cell, +} + +impl<'owner> HasDependent<'owner> for BothInteriorMutableOwner { + type Dependent = BothInteriorMutableDependent<'owner>; +} + +impl Owner for BothInteriorMutableOwner { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(BothInteriorMutableDependent { + owner_ref: &self.value, + local_value: Cell::new(*self.value.borrow()), + }) + } +} + +#[test] +fn test_both_interior_mutable() { + let pair = Pair::new(BothInteriorMutableOwner { + value: RefCell::new(42), + }); + + // Mutate the owner + *pair.get_owner().value.borrow_mut() = 100; + assert_eq!(*pair.with_dependent(|dep| dep).owner_ref.borrow(), 100); + assert_eq!(pair.with_dependent(|dep| dep).local_value.get(), 42); + + // Mutate the dependent + pair.with_dependent(|dep| dep).local_value.set(200); + assert_eq!(pair.with_dependent(|dep| dep).local_value.get(), 200); +} diff --git a/tests/to-do.md b/tests/to-do.md index 9aefd5e..cae3a45 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,8 +1,4 @@ # Test to-do list -- Interior mutability - - Test with interior-mutable owner - - Test with interior-mutable dependent - - Test with interior-mutable for both owner and dependent - Trybuild tests - Attempts to extract the dependent outside `with_dependent_mut` - Verify dependent cannot outlive the pair. This means writing code that From 43f3177fd4ef1de56eff625e83a190a3000708dd Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 18:12:32 -0500 Subject: [PATCH 050/110] added tests attempting to extract dep from with_dependent_mut --- tests/to-do.md | 1 - tests/trybuild_fails/extract_with_dep_mut.rs | 27 +++++++++++++++++++ .../extract_with_dep_mut.stderr | 11 ++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/trybuild_fails/extract_with_dep_mut.rs create mode 100644 tests/trybuild_fails/extract_with_dep_mut.stderr diff --git a/tests/to-do.md b/tests/to-do.md index cae3a45..653acf5 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,6 +1,5 @@ # Test to-do list - Trybuild tests - - Attempts to extract the dependent outside `with_dependent_mut` - Verify dependent cannot outlive the pair. This means writing code that attempts to extract and store a reference to the dependent that would outlive the Pair itself diff --git a/tests/trybuild_fails/extract_with_dep_mut.rs b/tests/trybuild_fails/extract_with_dep_mut.rs new file mode 100644 index 0000000..d86de5b --- /dev/null +++ b/tests/trybuild_fails/extract_with_dep_mut.rs @@ -0,0 +1,27 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +#[derive(Debug)] +struct Buff(String); + +impl<'owner> HasDependent<'owner> for Buff { + type Dependent = Vec<&'owner str>; +} + +impl Owner for Buff { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(self.0.split_whitespace().collect()) + } +} + +fn main() { + let mut pair = Pair::new(Buff(String::from("This is a test of pair."))); + let _: &mut Vec<&str> = pair.with_dependent_mut(|dep| dep); +} diff --git a/tests/trybuild_fails/extract_with_dep_mut.stderr b/tests/trybuild_fails/extract_with_dep_mut.stderr new file mode 100644 index 0000000..482dbe1 --- /dev/null +++ b/tests/trybuild_fails/extract_with_dep_mut.stderr @@ -0,0 +1,11 @@ +error[E0597]: `pair` does not live long enough + --> tests/trybuild_fails/extract_with_dep_mut.rs:26:29 + | +25 | let mut pair = Pair::new(Buff(String::from("This is a test of pair."))); + | -------- binding `pair` declared here +26 | let _: &mut Vec<&str> = pair.with_dependent_mut(|dep| dep); + | ^^^^ --- returning this value requires that `pair` is borrowed for `'static` + | | + | borrowed value does not live long enough +27 | } + | - `pair` dropped here while still borrowed From 3f84cbcd1121738ad31a8c6cff81977a8c55d48c Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 18:16:24 -0500 Subject: [PATCH 051/110] added test that attempts to keep dep alive after pair is dropped --- tests/to-do.md | 3 -- .../keep_dep_after_pair_drop.rs | 31 +++++++++++++++++++ .../keep_dep_after_pair_drop.stderr | 13 ++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 tests/trybuild_fails/keep_dep_after_pair_drop.rs create mode 100644 tests/trybuild_fails/keep_dep_after_pair_drop.stderr diff --git a/tests/to-do.md b/tests/to-do.md index 653acf5..f8687cd 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,8 +1,5 @@ # Test to-do list - Trybuild tests - - Verify dependent cannot outlive the pair. This means writing code that - attempts to extract and store a reference to the dependent that would - outlive the Pair itself - Test that the compiler prevents use of borrowed data after into_owner() - Concurrency - Sending ownership of a Pair between threads through channels diff --git a/tests/trybuild_fails/keep_dep_after_pair_drop.rs b/tests/trybuild_fails/keep_dep_after_pair_drop.rs new file mode 100644 index 0000000..369cd9d --- /dev/null +++ b/tests/trybuild_fails/keep_dep_after_pair_drop.rs @@ -0,0 +1,31 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +#[derive(Debug)] +struct Buff(String); + +impl<'owner> HasDependent<'owner> for Buff { + type Dependent = Vec<&'owner str>; +} + +impl Owner for Buff { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(self.0.split_whitespace().collect()) + } +} + +fn main() { + let pair = Pair::new(Buff(String::from("This is a test of pair."))); + let dep: &Vec<&str> = pair.with_dependent(|dep| dep); + + drop(pair); + + let _ = dep; +} diff --git a/tests/trybuild_fails/keep_dep_after_pair_drop.stderr b/tests/trybuild_fails/keep_dep_after_pair_drop.stderr new file mode 100644 index 0000000..358d1af --- /dev/null +++ b/tests/trybuild_fails/keep_dep_after_pair_drop.stderr @@ -0,0 +1,13 @@ +error[E0505]: cannot move out of `pair` because it is borrowed + --> tests/trybuild_fails/keep_dep_after_pair_drop.rs:28:10 + | +25 | let pair = Pair::new(Buff(String::from("This is a test of pair."))); + | ---- binding `pair` declared here +26 | let dep: &Vec<&str> = pair.with_dependent(|dep| dep); + | ---- borrow of `pair` occurs here +27 | +28 | drop(pair); + | ^^^^ move out of `pair` occurs here +29 | +30 | let _ = dep; + | --- borrow later used here From e9fa8dbd5adee3731c27727dd78a58a7356810a7 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 20:34:51 -0500 Subject: [PATCH 052/110] added test that attempts to use dependent after into_owner(..) --- tests/to-do.md | 2 -- .../keep_dep_after_into_owner.rs | 33 +++++++++++++++++++ .../keep_dep_after_into_owner.stderr | 13 ++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 tests/trybuild_fails/keep_dep_after_into_owner.rs create mode 100644 tests/trybuild_fails/keep_dep_after_into_owner.stderr diff --git a/tests/to-do.md b/tests/to-do.md index f8687cd..ca62e47 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -1,6 +1,4 @@ # Test to-do list -- Trybuild tests - - Test that the compiler prevents use of borrowed data after into_owner() - Concurrency - Sending ownership of a Pair between threads through channels - Sharing a Pair via &Pair<_> across multiple threads diff --git a/tests/trybuild_fails/keep_dep_after_into_owner.rs b/tests/trybuild_fails/keep_dep_after_into_owner.rs new file mode 100644 index 0000000..8155922 --- /dev/null +++ b/tests/trybuild_fails/keep_dep_after_into_owner.rs @@ -0,0 +1,33 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +#[derive(Debug)] +struct Buff(String); + +impl<'owner> HasDependent<'owner> for Buff { + type Dependent = Vec<&'owner str>; +} + +impl Owner for Buff { + type Context<'a> = (); + type Err = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Err> { + Ok(self.0.split_whitespace().collect()) + } +} + +fn main() { + let pair = Pair::new(Buff(String::from("This is a test of pair."))); + let dep: &Vec<&str> = pair.with_dependent(|dep| dep); + + let owner = pair.into_owner(); + + let _ = dep; + + drop(owner); +} diff --git a/tests/trybuild_fails/keep_dep_after_into_owner.stderr b/tests/trybuild_fails/keep_dep_after_into_owner.stderr new file mode 100644 index 0000000..fad5609 --- /dev/null +++ b/tests/trybuild_fails/keep_dep_after_into_owner.stderr @@ -0,0 +1,13 @@ +error[E0505]: cannot move out of `pair` because it is borrowed + --> tests/trybuild_fails/keep_dep_after_into_owner.rs:28:17 + | +25 | let pair = Pair::new(Buff(String::from("This is a test of pair."))); + | ---- binding `pair` declared here +26 | let dep: &Vec<&str> = pair.with_dependent(|dep| dep); + | ---- borrow of `pair` occurs here +27 | +28 | let owner = pair.into_owner(); + | ^^^^ move out of `pair` occurs here +29 | +30 | let _ = dep; + | --- borrow later used here From ab2677f0e2f0c5fb03c932c3067ca1e9221d9d9f Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 21:09:36 -0500 Subject: [PATCH 053/110] fixed path in Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 75de4a2..2b4ea7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ readme = "README.md" repository = "https://github.com/ijchen/pair" documentation = "https://docs.rs/pair" edition = "2024" -include = ["/src/", "/Cargo.toml", "/README.md", "CHANGELOG.md", "/LICENSE-*"] +include = ["/src/", "/Cargo.toml", "/README.md", "/CHANGELOG.md", "/LICENSE-*"] # ON_RELEASE: Remove publish = false publish = false From 8c7bcdcf2a92abbe2653e491de4081122d7a7b2c Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 22:32:23 -0500 Subject: [PATCH 054/110] minor comment wording tweak --- src/pair.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index ca62244..3b0cb57 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -182,10 +182,9 @@ impl Pair { /// lives at least as long as the borrow of `self`. This is important /// because the dependent may be invariant over its lifetime, and the /// correct lifetime (lasting from the construction of `self` until drop) is - /// impossible to express. For dependent types covariant over their - /// lifetime, the closure may simply return the reference to the dependent, - /// which may then be used as if this function directly returned a - /// reference. + /// inexpressible. For dependent types covariant over their lifetime, the + /// closure may simply return the reference to the dependent, which may then + /// be used as if this function directly returned a reference. pub fn with_dependent<'self_borrow, F, T>(&'self_borrow self, f: F) -> T where F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T, @@ -217,7 +216,7 @@ impl Pair { /// lives at least as long as the borrow of `self`. This is important /// because mutable references are invariant over their type `T`, and the /// exact T here (a `Dependent` with a very specific lifetime lasting from - /// the construction of `self` until drop) is impossible to express. + /// the construction of `self` until drop) is inexpressible. pub fn with_dependent_mut<'self_borrow, F, T>(&'self_borrow mut self, f: F) -> T where F: for<'any> FnOnce(&'self_borrow mut >::Dependent) -> T, From 87c7b96c24ba66640891a92012e8f179a01ef489 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sun, 2 Mar 2025 22:33:58 -0500 Subject: [PATCH 055/110] another minor wording tweak in a comment --- src/pair.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index 3b0cb57..91057fa 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -228,8 +228,8 @@ impl Pair { // neither our code nor any of our exposed APIs could have invalidated // those since construction. Additionally, because we have an exclusive // reference to self, we know that the value behind the pointer is - // currently not borrowed at all, and can't be until the mutable borrow - // of `self` expires. + // currently not borrowed at all, and can't be until our exclusive + // borrow of `self` expires. let dependent: &mut >::Dependent = unsafe { self.dependent .cast::<>::Dependent>() From ac7433ab99950bc11aff4909b3f09b156c938f44 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 3 Mar 2025 17:14:20 -0500 Subject: [PATCH 056/110] minor wording tweak --- src/pair.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pair.rs b/src/pair.rs index 91057fa..b2e8723 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -431,7 +431,7 @@ impl + ?Sized> Pair { // // NOTE(ichen): There are definitely some weird dropck things going on, but I do // not believe they can lead to any unsoundness. Because of the signature of -// Pair, dropck think we access an O and do nothing with O::Dependent. It's +// Pair, dropck thinks we access an O and do nothing with O::Dependent. It's // right about O - we don't access it directly, but the dependent (which we do // drop) might access an O in its drop. Unfortunately, the compiler is wrong // about O::Dependent. It doesn't see any indication of O::Dependent in the From 2c12c08e10b7f497383831bfaf0c77c29e4ed634 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 3 Mar 2025 17:22:15 -0500 Subject: [PATCH 057/110] renamed Owner's Err associated type to Error --- README.md | 4 ++-- src/owner.rs | 4 ++-- src/pair.rs | 14 +++++------ tests/alternative_ctors.rs | 12 +++++----- tests/basic_usage.rs | 4 ++-- tests/contravariance.rs | 4 ++-- tests/debug.rs | 9 +++---- tests/default.rs | 4 ++-- tests/drop.rs | 4 ++-- tests/dyn_trait_owner.rs | 4 ++-- tests/interior_mutability.rs | 12 +++++----- tests/multiborrow.rs | 4 ++-- tests/nested_pair.rs | 8 +++---- tests/panic_safety.rs | 24 +++++++++---------- tests/tmp.rs | 4 ++-- .../contravariance_violation.rs | 4 ++-- tests/trybuild_fails/extract_with_dep_mut.rs | 4 ++-- .../invariant_dep_extraction.rs | 4 ++-- .../keep_dep_after_into_owner.rs | 4 ++-- .../keep_dep_after_pair_drop.rs | 4 ++-- tests/trybuild_fails/pair_not_covariant.rs | 4 ++-- tests/unsized_owner.rs | 4 ++-- tests/zst.rs | 18 +++++++++----- 23 files changed, 84 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 1f67415..c5a57e3 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,12 @@ impl<'owner> HasDependent<'owner> for MyBuffer { // Define how to make a Parsed<'_> from a &MyBuffer impl Owner for MyBuffer { type Context<'a> = (); // We don't need any extra args to `make_dependent` - type Err = std::convert::Infallible; // Our example parsing can't fail + type Error = std::convert::Infallible; // Our example parsing can't fail fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(parse(self)) } } diff --git a/src/owner.rs b/src/owner.rs index d05aa19..21593d7 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -43,14 +43,14 @@ pub trait Owner: for<'any> HasDependent<'any> { // TODO(ichen): default this to std::convert::Infallible (or preferably !) // when associated type defaults are stabilized // (https://github.com/rust-lang/rust/issues/29661) - type Err; + type Error; /// Attempts to construct a [`Dependent`](HasDependent::Dependent) from a /// reference to an owner and some context. fn make_dependent<'owner>( &'owner self, context: Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err>; + ) -> Result<>::Dependent, Self::Error>; } /// Used to prevent implementors of [`HasDependent`] from overriding the diff --git a/src/pair.rs b/src/pair.rs index b2e8723..2f739f2 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -62,7 +62,7 @@ impl Pair { /// /// If this construction can't fail, consider the convenience constructor /// [`Pair::new_with_context`], which returns `Self` directly. - pub fn try_new_with_context(owner: O, context: O::Context<'_>) -> Result + pub fn try_new_with_context(owner: O, context: O::Context<'_>) -> Result where O: Sized, { @@ -85,7 +85,7 @@ impl Pair { pub fn try_new_from_box_with_context( owner: Box, context: O::Context<'_>, - ) -> Result, O::Err)> { + ) -> Result, O::Error)> { // Convert owner into a NonNull, so we are no longer restricted by the // aliasing requirements of Box let owner = non_null_from_box(owner); @@ -312,7 +312,7 @@ impl Pair { } } -impl Owner = (), Err = Infallible> + ?Sized> Pair { +impl Owner = (), Error = Infallible> + ?Sized> Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// @@ -363,7 +363,7 @@ impl Owner = ()> + ?Sized> Pair { /// /// If this construction can't fail, consider the convenience constructor /// [`Pair::new`], which returns `Self` directly. - pub fn try_new(owner: O) -> Result + pub fn try_new(owner: O) -> Result where O: Sized, { @@ -383,12 +383,12 @@ impl Owner = ()> + ?Sized> Pair { /// /// If this construction can't fail, consider the convenience constructor /// [`Pair::new_from_box`], which returns `Self` directly. - pub fn try_new_from_box(owner: Box) -> Result, O::Err)> { + pub fn try_new_from_box(owner: Box) -> Result, O::Error)> { Self::try_new_from_box_with_context(owner, ()) } } -impl + ?Sized> Pair { +impl + ?Sized> Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// @@ -538,7 +538,7 @@ where } } -impl Owner = (), Err = Infallible> + Default> Default for Pair { +impl Owner = (), Error = Infallible> + Default> Default for Pair { fn default() -> Self { Self::new(O::default()) } diff --git a/tests/alternative_ctors.rs b/tests/alternative_ctors.rs index 73a4312..59118f8 100644 --- a/tests/alternative_ctors.rs +++ b/tests/alternative_ctors.rs @@ -11,12 +11,12 @@ impl<'owner> HasDependent<'owner> for BuffFallible { impl Owner for BuffFallible { type Context<'a> = (); - type Err = String; + type Error = String; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { let parts: Vec<_> = self.0.split_whitespace().collect(); if parts.is_empty() { @@ -57,12 +57,12 @@ impl<'owner> HasDependent<'owner> for BuffWithContext { impl Owner for BuffWithContext { type Context<'a> = &'a str; - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, context: Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(self.0.split(context).collect()) } } @@ -88,12 +88,12 @@ impl<'owner> HasDependent<'owner> for BuffFallibleWithContext { impl Owner for BuffFallibleWithContext { type Context<'a> = &'a str; - type Err = String; + type Error = String; fn make_dependent( &self, context: Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { let parts: Vec<_> = self.0.split(context).collect(); if parts.len() > 1 { diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs index 951d3ac..14a06fd 100644 --- a/tests/basic_usage.rs +++ b/tests/basic_usage.rs @@ -11,12 +11,12 @@ impl<'owner> HasDependent<'owner> for Buff { impl Owner for Buff { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(self.0.split_whitespace().collect()) } } diff --git a/tests/contravariance.rs b/tests/contravariance.rs index 923a2f3..c4b1d54 100644 --- a/tests/contravariance.rs +++ b/tests/contravariance.rs @@ -9,12 +9,12 @@ impl<'owner> HasDependent<'owner> for ContraOwner { } impl Owner for ContraOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(|_| {}) } } diff --git a/tests/debug.rs b/tests/debug.rs index 4d3f0f8..60444ee 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -23,8 +23,9 @@ mod fake { } } -fn debugs_match Owner = (), Err = Infallible> + Clone + Debug>(owner: O) -where +fn debugs_match Owner = (), Error = Infallible> + Clone + Debug>( + owner: O, +) where for<'any> >::Dependent: Debug, { let Ok(dependent) = owner.make_dependent(()); @@ -78,11 +79,11 @@ macro_rules! debug_tests { } impl Owner for $name { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &$self_kw, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok($dep_expr) } } diff --git a/tests/default.rs b/tests/default.rs index 21c4162..d6bffdc 100644 --- a/tests/default.rs +++ b/tests/default.rs @@ -10,12 +10,12 @@ impl<'owner, T> HasDependent<'owner> for DefaultOwner { impl Owner for DefaultOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(&self.0) } } diff --git a/tests/drop.rs b/tests/drop.rs index 3fdee45..529d251 100644 --- a/tests/drop.rs +++ b/tests/drop.rs @@ -28,12 +28,12 @@ impl<'owner, T> HasDependent<'owner> for OnDrop { impl Owner for OnDrop { type Context<'a> = (Rc>, fn(&mut T)); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, context: Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(OnDropDep { value: context.0, f: context.1, diff --git a/tests/dyn_trait_owner.rs b/tests/dyn_trait_owner.rs index 5e2b19e..093d7fc 100644 --- a/tests/dyn_trait_owner.rs +++ b/tests/dyn_trait_owner.rs @@ -19,12 +19,12 @@ impl<'owner> HasDependent<'owner> for dyn MyTrait { } impl Owner for dyn MyTrait { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(self.get()) } } diff --git a/tests/interior_mutability.rs b/tests/interior_mutability.rs index b3f4fce..9dcd90d 100644 --- a/tests/interior_mutability.rs +++ b/tests/interior_mutability.rs @@ -13,12 +13,12 @@ impl<'owner> HasDependent<'owner> for InteriorMutableOwner { impl Owner for InteriorMutableOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(&self.value) } } @@ -58,12 +58,12 @@ impl<'owner> HasDependent<'owner> for RegularOwner { impl Owner for RegularOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(InteriorMutableDependent { value_cell: Cell::new(self.value), original_ref: &self.value, @@ -98,12 +98,12 @@ impl<'owner> HasDependent<'owner> for BothInteriorMutableOwner { impl Owner for BothInteriorMutableOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(BothInteriorMutableDependent { owner_ref: &self.value, local_value: Cell::new(*self.value.borrow()), diff --git a/tests/multiborrow.rs b/tests/multiborrow.rs index 371679e..ae56b98 100644 --- a/tests/multiborrow.rs +++ b/tests/multiborrow.rs @@ -21,12 +21,12 @@ impl<'owner> HasDependent<'owner> for MultiPartOwner { impl Owner for MultiPartOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(MultiPartDependent { string_ref: &self.field1, int_ref: &self.field2[0], diff --git a/tests/nested_pair.rs b/tests/nested_pair.rs index 492568c..f74ec2b 100644 --- a/tests/nested_pair.rs +++ b/tests/nested_pair.rs @@ -10,12 +10,12 @@ impl<'owner> HasDependent<'owner> for SimpleOwner { impl Owner for SimpleOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(&self.0) } } @@ -39,12 +39,12 @@ impl<'owner> HasDependent<'owner> for PairOwner { impl Owner for PairOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(PairOwnerDependent { value_ref: &self.value, inner_pair_ref: &self.inner_pair, diff --git a/tests/panic_safety.rs b/tests/panic_safety.rs index 154e921..f359296 100644 --- a/tests/panic_safety.rs +++ b/tests/panic_safety.rs @@ -24,12 +24,12 @@ impl<'owner> HasDependent<'owner> for PanicOnMakeDependent { impl Owner for PanicOnMakeDependent { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { panic_any(MyPayload(7)); } } @@ -64,12 +64,12 @@ impl<'owner> HasDependent<'owner> for PanicOnMakeDependentAndOwnerDrop { impl Owner for PanicOnMakeDependentAndOwnerDrop { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { panic_any(MyPayload(42)); } } @@ -110,12 +110,12 @@ impl<'owner> HasDependent<'owner> for PanicOnDepDropIntoOwner { impl Owner for PanicOnDepDropIntoOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(PanicOnDepDropIntoOwnerDep) } } @@ -155,12 +155,12 @@ impl<'owner> HasDependent<'owner> for PanicOnDepAndOwnerDropIntoOwner { impl Owner for PanicOnDepAndOwnerDropIntoOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(PanicOnDepAndOwnerDropIntoOwnerDep) } } @@ -201,12 +201,12 @@ impl<'owner> HasDependent<'owner> for PanicOnDepDropPairDrop { impl Owner for PanicOnDepDropPairDrop { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(PanicOnDepDropPairDropDep) } } @@ -246,12 +246,12 @@ impl<'owner> HasDependent<'owner> for PanicOnDepAndOwnerDropPairDrop { impl Owner for PanicOnDepAndOwnerDropPairDrop { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(PanicOnDepAndOwnerDropPairDropDep) } } diff --git a/tests/tmp.rs b/tests/tmp.rs index 3ed89cf..c560ce5 100644 --- a/tests/tmp.rs +++ b/tests/tmp.rs @@ -17,12 +17,12 @@ // } // impl Owner for Foo<'_> { // type Context<'a> = (); -// type Err = Infallible; +// type Error = Infallible; // fn make_dependent( // &self, // (): Self::Context<'_>, -// ) -> Result<>::Dependent, Self::Err> { +// ) -> Result<>::Dependent, Self::Error> { // unimplemented!() // } // } diff --git a/tests/trybuild_fails/contravariance_violation.rs b/tests/trybuild_fails/contravariance_violation.rs index 09c08c9..71c0a7c 100644 --- a/tests/trybuild_fails/contravariance_violation.rs +++ b/tests/trybuild_fails/contravariance_violation.rs @@ -9,12 +9,12 @@ impl<'owner> HasDependent<'owner> for ContraOwner { } impl Owner for ContraOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(|_| {}) } } diff --git a/tests/trybuild_fails/extract_with_dep_mut.rs b/tests/trybuild_fails/extract_with_dep_mut.rs index d86de5b..c0b80ea 100644 --- a/tests/trybuild_fails/extract_with_dep_mut.rs +++ b/tests/trybuild_fails/extract_with_dep_mut.rs @@ -11,12 +11,12 @@ impl<'owner> HasDependent<'owner> for Buff { impl Owner for Buff { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(self.0.split_whitespace().collect()) } } diff --git a/tests/trybuild_fails/invariant_dep_extraction.rs b/tests/trybuild_fails/invariant_dep_extraction.rs index 3df3c2a..a15c1f5 100644 --- a/tests/trybuild_fails/invariant_dep_extraction.rs +++ b/tests/trybuild_fails/invariant_dep_extraction.rs @@ -10,12 +10,12 @@ impl<'owner> HasDependent<'owner> for InvarOwner { impl Owner for InvarOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(PhantomData) } } diff --git a/tests/trybuild_fails/keep_dep_after_into_owner.rs b/tests/trybuild_fails/keep_dep_after_into_owner.rs index 8155922..a5d7781 100644 --- a/tests/trybuild_fails/keep_dep_after_into_owner.rs +++ b/tests/trybuild_fails/keep_dep_after_into_owner.rs @@ -11,12 +11,12 @@ impl<'owner> HasDependent<'owner> for Buff { impl Owner for Buff { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(self.0.split_whitespace().collect()) } } diff --git a/tests/trybuild_fails/keep_dep_after_pair_drop.rs b/tests/trybuild_fails/keep_dep_after_pair_drop.rs index 369cd9d..994684c 100644 --- a/tests/trybuild_fails/keep_dep_after_pair_drop.rs +++ b/tests/trybuild_fails/keep_dep_after_pair_drop.rs @@ -11,12 +11,12 @@ impl<'owner> HasDependent<'owner> for Buff { impl Owner for Buff { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(self.0.split_whitespace().collect()) } } diff --git a/tests/trybuild_fails/pair_not_covariant.rs b/tests/trybuild_fails/pair_not_covariant.rs index ce5aaba..1d4c6e9 100644 --- a/tests/trybuild_fails/pair_not_covariant.rs +++ b/tests/trybuild_fails/pair_not_covariant.rs @@ -15,12 +15,12 @@ impl<'owner> HasDependent<'owner> for Foo<'_> { } impl Owner for Foo<'_> { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { unimplemented!() } } diff --git a/tests/unsized_owner.rs b/tests/unsized_owner.rs index 34eaec5..252a721 100644 --- a/tests/unsized_owner.rs +++ b/tests/unsized_owner.rs @@ -17,12 +17,12 @@ impl<'owner> HasDependent<'owner> for Buff<[u8]> { impl Owner for Buff<[u8]> { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { let start = usize::min(1, self.0.len()); let end = self.0.len().saturating_sub(1); Ok((&self.0[start..end], self.0.len())) diff --git a/tests/zst.rs b/tests/zst.rs index 9c27b40..ea5e6de 100644 --- a/tests/zst.rs +++ b/tests/zst.rs @@ -14,9 +14,12 @@ impl<'owner> HasDependent<'owner> for ZstOwner { impl Owner for ZstOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; - fn make_dependent(&self, (): Self::Context<'_>) -> Result { + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { Ok(NonZstDependent(42)) } } @@ -44,9 +47,12 @@ impl<'owner> HasDependent<'owner> for NonZstOwner { impl Owner for NonZstOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; - fn make_dependent(&self, (): Self::Context<'_>) -> Result { + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { Ok(ZstDependent) } } @@ -70,12 +76,12 @@ impl<'owner> HasDependent<'owner> for BothZstOwner { impl Owner for BothZstOwner { type Context<'a> = (); - type Err = Infallible; + type Error = Infallible; fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Err> { + ) -> Result<>::Dependent, Self::Error> { Ok(ZstDependent) } } From cbd0f3cc67f55800b3157d589106513e45bf16d5 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 3 Mar 2025 22:29:46 -0500 Subject: [PATCH 058/110] added Send/Sync trybuild tests --- tests/tmp.rs | 48 ------------------- tests/to-do.md | 4 -- tests/trybuild_fails/not_send_dep.rs | 33 +++++++++++++ tests/trybuild_fails/not_send_dep.stderr | 18 +++++++ tests/trybuild_fails/not_send_owner.rs | 32 +++++++++++++ tests/trybuild_fails/not_send_owner.stderr | 18 +++++++ tests/trybuild_fails/not_sync_dep.rs | 33 +++++++++++++ tests/trybuild_fails/not_sync_dep.stderr | 18 +++++++ tests/trybuild_fails/not_sync_owner.rs | 32 +++++++++++++ tests/trybuild_fails/not_sync_owner.stderr | 18 +++++++ .../send_sync_miserable_failure.rs | 29 +++++++++++ .../send_sync_miserable_failure.stderr | 37 ++++++++++++++ 12 files changed, 268 insertions(+), 52 deletions(-) delete mode 100644 tests/tmp.rs create mode 100644 tests/trybuild_fails/not_send_dep.rs create mode 100644 tests/trybuild_fails/not_send_dep.stderr create mode 100644 tests/trybuild_fails/not_send_owner.rs create mode 100644 tests/trybuild_fails/not_send_owner.stderr create mode 100644 tests/trybuild_fails/not_sync_dep.rs create mode 100644 tests/trybuild_fails/not_sync_dep.stderr create mode 100644 tests/trybuild_fails/not_sync_owner.rs create mode 100644 tests/trybuild_fails/not_sync_owner.stderr create mode 100644 tests/trybuild_fails/send_sync_miserable_failure.rs create mode 100644 tests/trybuild_fails/send_sync_miserable_failure.stderr diff --git a/tests/tmp.rs b/tests/tmp.rs deleted file mode 100644 index c560ce5..0000000 --- a/tests/tmp.rs +++ /dev/null @@ -1,48 +0,0 @@ -// TODO: dropck stuff - -// #![allow(unused)] - -// use std::{convert::Infallible, marker::PhantomData}; - -// use pair::{HasDependent, Owner, Pair}; - -// fn main() {} - -// struct Foo<'a>(PhantomData<&'a ()>); - -// struct Covariant(PhantomData); - -// impl<'owner> HasDependent<'owner> for Foo<'_> { -// type Dependent = (); -// } -// impl Owner for Foo<'_> { -// type Context<'a> = (); -// type Error = Infallible; - -// fn make_dependent( -// &self, -// (): Self::Context<'_>, -// ) -> Result<>::Dependent, Self::Error> { -// unimplemented!() -// } -// } - -// fn uh_oh<'shorter, 'longer>() -// where -// 'longer: 'shorter, -// { -// // Foo<'a> should be covariant over 'a, so this should be okay -// let mut foo_shorter: Foo<'shorter> = Foo(PhantomData); -// let foo_longer: Foo<'longer> = Foo(PhantomData); -// foo_shorter = foo_longer; - -// // Covariant should be covariant over T, so this should be okay -// let mut cov_shorter: Covariant> = Covariant(PhantomData); -// let cov_longer: Covariant> = Covariant(PhantomData); -// cov_shorter = cov_longer; - -// // Pair should *not* be covariant over O, so this should *not* be okay -// let mut pair_shorter: Pair> = Pair::new(Foo(PhantomData)); -// let pair_longer: Pair> = Pair::new(Foo(PhantomData)); -// pair_shorter = pair_longer; -// } diff --git a/tests/to-do.md b/tests/to-do.md index ca62e47..bb7ab58 100644 --- a/tests/to-do.md +++ b/tests/to-do.md @@ -5,8 +5,4 @@ - Sending and sharing a Pair via Arc> across multiple threads - Wrapping a Pair in Arc and concurrently racily accessing from multiple threads - - Verify pair is !Send when owner is !Send - - Verify pair is !Send when dependent is !Send - - Verify pair is !Sync when owner is !Sync - - Verify pair is !Sync when dependent is !Sync - Run with asan, threadsan, loom, and some kind of memory leak detector diff --git a/tests/trybuild_fails/not_send_dep.rs b/tests/trybuild_fails/not_send_dep.rs new file mode 100644 index 0000000..72de8d1 --- /dev/null +++ b/tests/trybuild_fails/not_send_dep.rs @@ -0,0 +1,33 @@ +use std::{convert::Infallible, sync::MutexGuard}; + +use pair::{HasDependent, Owner, Pair}; + +struct MyOwner; +struct NotSend(MutexGuard<'static, ()>); + +impl<'owner> HasDependent<'owner> for MyOwner { + type Dependent = NotSend; +} + +impl Owner for MyOwner { + type Context<'a> = (); + type Error = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + unimplemented!() + } +} + +fn check_send() {} +fn check_sync() {} + +fn main() { + check_send::(); + check_sync::<(MyOwner, ::Dependent)>(); + + check_send::>(); + check_sync::>(); +} diff --git a/tests/trybuild_fails/not_send_dep.stderr b/tests/trybuild_fails/not_send_dep.stderr new file mode 100644 index 0000000..ae65b7b --- /dev/null +++ b/tests/trybuild_fails/not_send_dep.stderr @@ -0,0 +1,18 @@ +error[E0277]: `MutexGuard<'static, ()>` cannot be sent between threads safely + --> tests/trybuild_fails/not_send_dep.rs:31:18 + | +31 | check_send::>(); + | ^^^^^^^^^^^^^ `MutexGuard<'static, ()>` cannot be sent between threads safely + | + = help: within `NotSend`, the trait `Send` is not implemented for `MutexGuard<'static, ()>` +note: required because it appears within the type `NotSend` + --> tests/trybuild_fails/not_send_dep.rs:6:8 + | +6 | struct NotSend(MutexGuard<'static, ()>); + | ^^^^^^^ + = note: required for `Pair` to implement `Send` +note: required by a bound in `check_send` + --> tests/trybuild_fails/not_send_dep.rs:24:18 + | +24 | fn check_send() {} + | ^^^^ required by this bound in `check_send` diff --git a/tests/trybuild_fails/not_send_owner.rs b/tests/trybuild_fails/not_send_owner.rs new file mode 100644 index 0000000..003946a --- /dev/null +++ b/tests/trybuild_fails/not_send_owner.rs @@ -0,0 +1,32 @@ +use std::{convert::Infallible, sync::MutexGuard}; + +use pair::{HasDependent, Owner, Pair}; + +struct NotSend(MutexGuard<'static, ()>); + +impl<'owner> HasDependent<'owner> for NotSend { + type Dependent = (); +} + +impl Owner for NotSend { + type Context<'a> = (); + type Error = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + unimplemented!() + } +} + +fn check_send() {} +fn check_sync() {} + +fn main() { + check_send::<::Dependent>(); + check_sync::<(NotSend, ::Dependent)>(); + + check_send::>(); + check_sync::>(); +} diff --git a/tests/trybuild_fails/not_send_owner.stderr b/tests/trybuild_fails/not_send_owner.stderr new file mode 100644 index 0000000..d605a3f --- /dev/null +++ b/tests/trybuild_fails/not_send_owner.stderr @@ -0,0 +1,18 @@ +error[E0277]: `MutexGuard<'static, ()>` cannot be sent between threads safely + --> tests/trybuild_fails/not_send_owner.rs:30:18 + | +30 | check_send::>(); + | ^^^^^^^^^^^^^ `MutexGuard<'static, ()>` cannot be sent between threads safely + | + = help: within `NotSend`, the trait `Send` is not implemented for `MutexGuard<'static, ()>` +note: required because it appears within the type `NotSend` + --> tests/trybuild_fails/not_send_owner.rs:5:8 + | +5 | struct NotSend(MutexGuard<'static, ()>); + | ^^^^^^^ + = note: required for `Pair` to implement `Send` +note: required by a bound in `check_send` + --> tests/trybuild_fails/not_send_owner.rs:23:18 + | +23 | fn check_send() {} + | ^^^^ required by this bound in `check_send` diff --git a/tests/trybuild_fails/not_sync_dep.rs b/tests/trybuild_fails/not_sync_dep.rs new file mode 100644 index 0000000..b394bb5 --- /dev/null +++ b/tests/trybuild_fails/not_sync_dep.rs @@ -0,0 +1,33 @@ +use std::{cell::UnsafeCell, convert::Infallible}; + +use pair::{HasDependent, Owner, Pair}; + +struct MyOwner; +struct NotSync(UnsafeCell<()>); + +impl<'owner> HasDependent<'owner> for MyOwner { + type Dependent = NotSync; +} + +impl Owner for MyOwner { + type Context<'a> = (); + type Error = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + unimplemented!() + } +} + +fn check_send() {} +fn check_sync() {} + +fn main() { + check_send::<(MyOwner, ::Dependent)>(); + check_sync::(); + + check_send::>(); + check_sync::>(); +} diff --git a/tests/trybuild_fails/not_sync_dep.stderr b/tests/trybuild_fails/not_sync_dep.stderr new file mode 100644 index 0000000..b63bc53 --- /dev/null +++ b/tests/trybuild_fails/not_sync_dep.stderr @@ -0,0 +1,18 @@ +error[E0277]: `UnsafeCell<()>` cannot be shared between threads safely + --> tests/trybuild_fails/not_sync_dep.rs:32:18 + | +32 | check_sync::>(); + | ^^^^^^^^^^^^^ `UnsafeCell<()>` cannot be shared between threads safely + | + = help: within `NotSync`, the trait `Sync` is not implemented for `UnsafeCell<()>` +note: required because it appears within the type `NotSync` + --> tests/trybuild_fails/not_sync_dep.rs:6:8 + | +6 | struct NotSync(UnsafeCell<()>); + | ^^^^^^^ + = note: required for `Pair` to implement `Sync` +note: required by a bound in `check_sync` + --> tests/trybuild_fails/not_sync_dep.rs:25:18 + | +25 | fn check_sync() {} + | ^^^^ required by this bound in `check_sync` diff --git a/tests/trybuild_fails/not_sync_owner.rs b/tests/trybuild_fails/not_sync_owner.rs new file mode 100644 index 0000000..840a7be --- /dev/null +++ b/tests/trybuild_fails/not_sync_owner.rs @@ -0,0 +1,32 @@ +use std::{cell::UnsafeCell, convert::Infallible}; + +use pair::{HasDependent, Owner, Pair}; + +struct NotSync(UnsafeCell<()>); + +impl<'owner> HasDependent<'owner> for NotSync { + type Dependent = (); +} + +impl Owner for NotSync { + type Context<'a> = (); + type Error = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + unimplemented!() + } +} + +fn check_send() {} +fn check_sync() {} + +fn main() { + check_send::<(NotSync, ::Dependent)>(); + check_sync::<::Dependent>(); + + check_send::>(); + check_sync::>(); +} diff --git a/tests/trybuild_fails/not_sync_owner.stderr b/tests/trybuild_fails/not_sync_owner.stderr new file mode 100644 index 0000000..67ece17 --- /dev/null +++ b/tests/trybuild_fails/not_sync_owner.stderr @@ -0,0 +1,18 @@ +error[E0277]: `UnsafeCell<()>` cannot be shared between threads safely + --> tests/trybuild_fails/not_sync_owner.rs:31:18 + | +31 | check_sync::>(); + | ^^^^^^^^^^^^^ `UnsafeCell<()>` cannot be shared between threads safely + | + = help: within `NotSync`, the trait `Sync` is not implemented for `UnsafeCell<()>` +note: required because it appears within the type `NotSync` + --> tests/trybuild_fails/not_sync_owner.rs:5:8 + | +5 | struct NotSync(UnsafeCell<()>); + | ^^^^^^^ + = note: required for `Pair` to implement `Sync` +note: required by a bound in `check_sync` + --> tests/trybuild_fails/not_sync_owner.rs:24:18 + | +24 | fn check_sync() {} + | ^^^^ required by this bound in `check_sync` diff --git a/tests/trybuild_fails/send_sync_miserable_failure.rs b/tests/trybuild_fails/send_sync_miserable_failure.rs new file mode 100644 index 0000000..a877f68 --- /dev/null +++ b/tests/trybuild_fails/send_sync_miserable_failure.rs @@ -0,0 +1,29 @@ +use std::convert::Infallible; + +use pair::{HasDependent, Owner, Pair}; + +struct NotSendOrSync(*mut ()); + +impl<'owner> HasDependent<'owner> for NotSendOrSync { + type Dependent = NotSendOrSync; +} + +impl Owner for NotSendOrSync { + type Context<'a> = (); + type Error = Infallible; + + fn make_dependent<'owner>( + &'owner self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + unimplemented!() + } +} + +fn check_send() {} +fn check_sync() {} + +fn main() { + check_send::>(); + check_sync::>(); +} diff --git a/tests/trybuild_fails/send_sync_miserable_failure.stderr b/tests/trybuild_fails/send_sync_miserable_failure.stderr new file mode 100644 index 0000000..d6406c9 --- /dev/null +++ b/tests/trybuild_fails/send_sync_miserable_failure.stderr @@ -0,0 +1,37 @@ +error[E0277]: `*mut ()` cannot be sent between threads safely + --> tests/trybuild_fails/send_sync_miserable_failure.rs:27:18 + | +27 | check_send::>(); + | ^^^^^^^^^^^^^^^^^^^ `*mut ()` cannot be sent between threads safely + | + = help: within `NotSendOrSync`, the trait `Send` is not implemented for `*mut ()` +note: required because it appears within the type `NotSendOrSync` + --> tests/trybuild_fails/send_sync_miserable_failure.rs:5:8 + | +5 | struct NotSendOrSync(*mut ()); + | ^^^^^^^^^^^^^ + = note: required for `Pair` to implement `Send` +note: required by a bound in `check_send` + --> tests/trybuild_fails/send_sync_miserable_failure.rs:23:18 + | +23 | fn check_send() {} + | ^^^^ required by this bound in `check_send` + +error[E0277]: `*mut ()` cannot be shared between threads safely + --> tests/trybuild_fails/send_sync_miserable_failure.rs:28:18 + | +28 | check_sync::>(); + | ^^^^^^^^^^^^^^^^^^^ `*mut ()` cannot be shared between threads safely + | + = help: within `NotSendOrSync`, the trait `Sync` is not implemented for `*mut ()` +note: required because it appears within the type `NotSendOrSync` + --> tests/trybuild_fails/send_sync_miserable_failure.rs:5:8 + | +5 | struct NotSendOrSync(*mut ()); + | ^^^^^^^^^^^^^ + = note: required for `Pair` to implement `Sync` +note: required by a bound in `check_sync` + --> tests/trybuild_fails/send_sync_miserable_failure.rs:24:18 + | +24 | fn check_sync() {} + | ^^^^ required by this bound in `check_sync` From 7d66e771ab0e4d85ab215bafff19fd0599da7615 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Mon, 3 Mar 2025 22:39:48 -0500 Subject: [PATCH 059/110] added leak sanitizer and nightly test to ci.sh --- ci.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci.sh b/ci.sh index 3ef4a58..4f1f8d1 100755 --- a/ci.sh +++ b/ci.sh @@ -1,5 +1,8 @@ cargo clippy && \ cargo test && \ +# TODO: trybuild tests fail on nightly (and beta) - error messages changed +cargo +nightly test -- --skip try_builds && \ +RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test -- --skip try_builds && \ ( cp Cargo.toml Cargo.toml.backup && \ trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ From 00ac7d7075d0c081c55da4e79fdfeedb21f373d7 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 4 Mar 2025 22:02:57 -0500 Subject: [PATCH 060/110] added concurrency tests --- Cargo.lock | 319 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + ci.sh | 4 +- tests/concurrent_loom.rs | 203 +++++++++++++++++++++++++ tests/concurrent_std.rs | 195 ++++++++++++++++++++++++ tests/to-do.md | 8 - 6 files changed, 720 insertions(+), 10 deletions(-) create mode 100644 tests/concurrent_loom.rs create mode 100644 tests/concurrent_std.rs delete mode 100644 tests/to-do.md diff --git a/Cargo.lock b/Cargo.lock index f4f3f58..ee2da40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,40 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "generator" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "glob" version = "0.3.2" @@ -36,19 +64,88 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pair" version = "0.1.0" dependencies = [ + "loom", "trybuild", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "proc-macro2" version = "1.0.93" @@ -67,12 +164,68 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "serde" version = "1.0.218" @@ -114,6 +267,21 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + [[package]] name = "syn" version = "2.0.98" @@ -140,6 +308,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "toml" version = "0.8.20" @@ -174,6 +352,55 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "trybuild" version = "1.0.103" @@ -195,6 +422,28 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -204,6 +453,76 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 2b4ea7f..eb9d6ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ publish = false # No dependencies [dev-dependencies] +loom = "0.7.2" trybuild = "1.0.103" # TODO: define lints diff --git a/ci.sh b/ci.sh index 4f1f8d1..52deb52 100755 --- a/ci.sh +++ b/ci.sh @@ -2,10 +2,10 @@ cargo clippy && \ cargo test && \ # TODO: trybuild tests fail on nightly (and beta) - error messages changed cargo +nightly test -- --skip try_builds && \ -RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test -- --skip try_builds && \ +RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test -- --skip try_builds --skip loom && \ ( cp Cargo.toml Cargo.toml.backup && \ trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ - MIRIFLAGS="-Zmiri-retag-fields" cargo +nightly miri test -- --skip nomiri + MIRIFLAGS="-Zmiri-retag-fields" cargo +nightly miri test -- --skip nomiri --skip loom ) diff --git a/tests/concurrent_loom.rs b/tests/concurrent_loom.rs new file mode 100644 index 0000000..8396ee8 --- /dev/null +++ b/tests/concurrent_loom.rs @@ -0,0 +1,203 @@ +use std::sync::{Arc, Mutex, RwLock, mpsc}; + +use pair::{HasDependent, Owner, Pair}; + +// A simple owner type for testing thread safety +#[derive(Debug)] +struct Buff(String); + +// A dependent type that borrows from Buff +#[derive(Debug)] +struct Parsed<'a> { + tokens: Vec<&'a str>, +} + +fn parse(buffer: &Buff) -> Parsed<'_> { + let tokens = buffer.0.split_whitespace().collect(); + Parsed { tokens } +} + +impl<'owner> HasDependent<'owner> for Buff { + type Dependent = Parsed<'owner>; +} + +impl Owner for Buff { + type Context<'a> = (); + type Error = std::convert::Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + Ok(parse(self)) + } +} + +// Test sending ownership of a Pair between threads through channels +#[test] +fn pair_ownership_transfer_loom() { + loom::model(|| { + let (tx, rx) = mpsc::channel(); + + let pair = Pair::new(Buff(String::from("this is a test"))); + + let t1 = loom::thread::spawn(move || { + assert_eq!(pair.get_owner().0, "this is a test"); + assert_eq!( + pair.with_dependent(|parsed| parsed).tokens, + ["this", "is", "a", "test"] + ); + tx.send(pair).unwrap(); + }); + + let t2 = loom::thread::spawn(move || { + let received_pair = rx.recv().unwrap(); + + assert_eq!(received_pair.get_owner().0, "this is a test"); + assert_eq!( + received_pair.with_dependent(|parsed| parsed).tokens, + ["this", "is", "a", "test"] + ); + + received_pair + }); + + t1.join().unwrap(); + let received_pair = t2.join().unwrap(); + + assert_eq!(received_pair.get_owner().0, "this is a test"); + assert_eq!( + received_pair.with_dependent(|parsed| parsed).tokens, + ["this", "is", "a", "test"] + ); + }); +} + +// Test sending and sharing a Pair via Arc> across multiple threads +#[test] +fn pair_arc_sharing_loom() { + loom::model(|| { + let pair = Arc::new(Pair::new(Buff(String::from("arc sharing test")))); + + let pair1 = Arc::clone(&pair); + let t1 = loom::thread::spawn(move || { + assert_eq!(pair1.get_owner().0, "arc sharing test"); + assert_eq!( + pair1.with_dependent(|parsed| parsed).tokens, + ["arc", "sharing", "test"] + ); + }); + + let pair2 = Arc::clone(&pair); + let t2 = loom::thread::spawn(move || { + assert_eq!(pair2.get_owner().0, "arc sharing test"); + assert_eq!( + pair2.with_dependent(|parsed| parsed).tokens, + ["arc", "sharing", "test"] + ); + }); + + t1.join().unwrap(); + t2.join().unwrap(); + }); +} + +// Test concurrently accessing an Arc> from multiple threads +#[test] +fn pair_mutex_concurrent_access_loom() { + loom::model(|| { + let pair = Arc::new(Mutex::new(Pair::new(Buff(String::from( + "mutex concurrent test", + ))))); + + let join_handles: Vec<_> = (0..4) + .map(|_| { + let pair_clone = Arc::clone(&pair); + loom::thread::spawn(move || { + let mut pair = pair_clone.lock().unwrap(); + assert_eq!( + pair.with_dependent(|parsed| parsed).tokens[..3], + ["mutex", "concurrent", "test"] + ); + pair.with_dependent_mut(|parsed| parsed.tokens.push("modified")); + }) + }) + .collect(); + + for join_handle in join_handles { + join_handle.join().unwrap(); + } + + let pair = Arc::into_inner(pair).unwrap().into_inner().unwrap(); + pair.with_dependent(|parsed| { + assert_eq!( + parsed.tokens, + [ + "mutex", + "concurrent", + "test", + "modified", + "modified", + "modified", + "modified", + ] + ); + }); + }); +} + +// Test concurrently accessing an Arc> from multiple threads +#[test] +fn pair_rwlock_concurrent_access_loom() { + loom::model(|| { + let pair = Arc::new(RwLock::new(Pair::new(Buff(String::from( + "rwlock concurrent test", + ))))); + + let mut join_handles: Vec<_> = Vec::new(); + + // Reader threads + for _ in 0..2 { + let pair_clone = Arc::clone(&pair); + let join_handle = loom::thread::spawn(move || { + let pair = pair_clone.read().unwrap(); + assert_eq!( + pair.with_dependent(|parsed| parsed).tokens[..3], + ["rwlock", "concurrent", "test"] + ); + }); + + join_handles.push(join_handle); + } + + // Writer threads + for _ in 0..2 { + let pair_clone = Arc::clone(&pair); + let join_handle = loom::thread::spawn(move || { + let pair = pair_clone.read().unwrap(); + assert_eq!( + pair.with_dependent(|parsed| parsed).tokens[..3], + ["rwlock", "concurrent", "test"] + ); + drop(pair); + + let mut pair = pair_clone.write().unwrap(); + pair.with_dependent_mut(|parsed| parsed.tokens.push("modified")); + }); + + join_handles.push(join_handle); + } + + for join_handle in join_handles { + join_handle.join().unwrap(); + } + + let pair = Arc::into_inner(pair).unwrap().into_inner().unwrap(); + pair.with_dependent(|parsed| { + assert_eq!( + parsed.tokens, + ["rwlock", "concurrent", "test", "modified", "modified"] + ); + }); + }); +} diff --git a/tests/concurrent_std.rs b/tests/concurrent_std.rs new file mode 100644 index 0000000..c9fc87e --- /dev/null +++ b/tests/concurrent_std.rs @@ -0,0 +1,195 @@ +use std::sync::{Arc, Mutex, RwLock, mpsc}; + +use pair::{HasDependent, Owner, Pair}; + +// A simple owner type for testing thread safety +#[derive(Debug)] +struct Buff(String); + +// A dependent type that borrows from Buff +#[derive(Debug)] +struct Parsed<'a> { + tokens: Vec<&'a str>, +} + +fn parse(buffer: &Buff) -> Parsed<'_> { + let tokens = buffer.0.split_whitespace().collect(); + Parsed { tokens } +} + +impl<'owner> HasDependent<'owner> for Buff { + type Dependent = Parsed<'owner>; +} + +impl Owner for Buff { + type Context<'a> = (); + type Error = std::convert::Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + Ok(parse(self)) + } +} + +// Test sending ownership of a Pair between threads through channels +#[test] +fn pair_ownership_transfer() { + let (tx, rx) = mpsc::channel(); + + let pair = Pair::new(Buff(String::from("this is a test"))); + + let t1 = std::thread::spawn(move || { + assert_eq!(pair.get_owner().0, "this is a test"); + assert_eq!( + pair.with_dependent(|parsed| parsed).tokens, + ["this", "is", "a", "test"] + ); + tx.send(pair).unwrap(); + }); + + let t2 = std::thread::spawn(move || { + let received_pair = rx.recv().unwrap(); + + assert_eq!(received_pair.get_owner().0, "this is a test"); + assert_eq!( + received_pair.with_dependent(|parsed| parsed).tokens, + ["this", "is", "a", "test"] + ); + + received_pair + }); + + t1.join().unwrap(); + let received_pair = t2.join().unwrap(); + + assert_eq!(received_pair.get_owner().0, "this is a test"); + assert_eq!( + received_pair.with_dependent(|parsed| parsed).tokens, + ["this", "is", "a", "test"] + ); +} + +// Test sending and sharing a Pair via Arc> across multiple threads +#[test] +fn pair_arc_sharing() { + let pair = Arc::new(Pair::new(Buff(String::from("arc sharing test")))); + + let pair1 = Arc::clone(&pair); + let t1 = std::thread::spawn(move || { + assert_eq!(pair1.get_owner().0, "arc sharing test"); + assert_eq!( + pair1.with_dependent(|parsed| parsed).tokens, + ["arc", "sharing", "test"] + ); + }); + + let pair2 = Arc::clone(&pair); + let t2 = std::thread::spawn(move || { + assert_eq!(pair2.get_owner().0, "arc sharing test"); + assert_eq!( + pair2.with_dependent(|parsed| parsed).tokens, + ["arc", "sharing", "test"] + ); + }); + + t1.join().unwrap(); + t2.join().unwrap(); +} + +// Test concurrently accessing an Arc> from multiple threads +#[test] +fn pair_mutex_concurrent_access() { + let pair = Arc::new(Mutex::new(Pair::new(Buff(String::from( + "mutex concurrent test", + ))))); + + let join_handles: Vec<_> = (0..4) + .map(|_| { + let pair_clone = Arc::clone(&pair); + std::thread::spawn(move || { + let mut pair = pair_clone.lock().unwrap(); + assert_eq!( + pair.with_dependent(|parsed| parsed).tokens[..3], + ["mutex", "concurrent", "test"] + ); + pair.with_dependent_mut(|parsed| parsed.tokens.push("modified")); + }) + }) + .collect(); + + for join_handle in join_handles { + join_handle.join().unwrap(); + } + + let pair = Arc::into_inner(pair).unwrap().into_inner().unwrap(); + pair.with_dependent(|parsed| { + assert_eq!( + parsed.tokens, + [ + "mutex", + "concurrent", + "test", + "modified", + "modified", + "modified", + "modified", + ] + ); + }); +} + +// Test concurrently accessing an Arc> from multiple threads +#[test] +fn pair_rwlock_concurrent_access() { + let pair = Arc::new(RwLock::new(Pair::new(Buff(String::from( + "rwlock concurrent test", + ))))); + + let mut join_handles: Vec<_> = Vec::new(); + + // Reader threads + for _ in 0..2 { + let pair_clone = Arc::clone(&pair); + let join_handle = std::thread::spawn(move || { + let pair = pair_clone.read().unwrap(); + assert_eq!( + pair.with_dependent(|parsed| parsed).tokens[..3], + ["rwlock", "concurrent", "test"] + ); + }); + + join_handles.push(join_handle); + } + + // Writer threads + for _ in 0..2 { + let pair_clone = Arc::clone(&pair); + let join_handle = std::thread::spawn(move || { + let pair = pair_clone.read().unwrap(); + assert_eq!( + pair.with_dependent(|parsed| parsed).tokens[..3], + ["rwlock", "concurrent", "test"] + ); + drop(pair); + + let mut pair = pair_clone.write().unwrap(); + pair.with_dependent_mut(|parsed| parsed.tokens.push("modified")); + }); + + join_handles.push(join_handle); + } + + for join_handle in join_handles { + join_handle.join().unwrap(); + } + + let pair = Arc::into_inner(pair).unwrap().into_inner().unwrap(); + pair.with_dependent(|parsed| { + assert_eq!( + parsed.tokens, + ["rwlock", "concurrent", "test", "modified", "modified"] + ); + }); +} diff --git a/tests/to-do.md b/tests/to-do.md deleted file mode 100644 index bb7ab58..0000000 --- a/tests/to-do.md +++ /dev/null @@ -1,8 +0,0 @@ -# Test to-do list -- Concurrency - - Sending ownership of a Pair between threads through channels - - Sharing a Pair via &Pair<_> across multiple threads - - Sending and sharing a Pair via Arc> across multiple threads - - Wrapping a Pair in Arc and concurrently racily accessing from - multiple threads -- Run with asan, threadsan, loom, and some kind of memory leak detector From 782fe94cf996fd62728d0bc83c9f717fe7f47479 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 4 Mar 2025 22:17:23 -0500 Subject: [PATCH 061/110] updated ON_RELEASE comments --- CHANGELOG.md | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 535f0c1..af5dec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ - + # Major Version 0 diff --git a/README.md b/README.md index c5a57e3..5b24ce9 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ You define how to construct a dependent type from a reference to an owning type, and `pair` will carefully bundle them together in a safe and freely movable self-referential struct. + # DO NOT USE THIS LIBRARY As of right now, I have absolutely no idea whether or not this API is sound. You From 99c775937af3107dc902cfac07139f4b89cfa9e1 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 4 Mar 2025 22:22:05 -0500 Subject: [PATCH 062/110] added cargo fmt check to ci --- ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci.sh b/ci.sh index 52deb52..fb02dcd 100755 --- a/ci.sh +++ b/ci.sh @@ -1,3 +1,4 @@ +cargo fmt --check && \ cargo clippy && \ cargo test && \ # TODO: trybuild tests fail on nightly (and beta) - error messages changed From fe273106c50bf1a626f0f6262035ea6ce54b78e2 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 4 Mar 2025 22:27:09 -0500 Subject: [PATCH 063/110] added keywords and categories to Cargo.toml --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index eb9d6ba..387bd3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ version = "0.1.0" authors = ["Isaac Chen"] description = "Safe API for generic self-referential pairs of owner and dependent." license = "MIT OR Apache-2.0" +keywords = ["self-referential", "data-structures", "memory", "ownership", "lifetime"] +categories = ["data-structures", "memory-management", "rust-patterns"] # TODO: "no-std" readme = "README.md" repository = "https://github.com/ijchen/pair" documentation = "https://docs.rs/pair" From b9a55a61bd6ae933793d92667ed93bd949162f14 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 4 Mar 2025 22:30:32 -0500 Subject: [PATCH 064/110] added -Dwarnings to CI --- ci.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci.sh b/ci.sh index fb02dcd..d421152 100755 --- a/ci.sh +++ b/ci.sh @@ -1,8 +1,8 @@ cargo fmt --check && \ -cargo clippy && \ -cargo test && \ +cargo clippy -- -Dwarnings && \ +RUSTFLAGS="-Dwarnings" cargo test && \ # TODO: trybuild tests fail on nightly (and beta) - error messages changed -cargo +nightly test -- --skip try_builds && \ +RUSTFLAGS="-Dwarnings" cargo +nightly test -- --skip try_builds && \ RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test -- --skip try_builds --skip loom && \ ( cp Cargo.toml Cargo.toml.backup && \ From c7a3f4951958b89014053512402d04c5222eda5d Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Tue, 4 Mar 2025 22:36:37 -0500 Subject: [PATCH 065/110] updated lib.rs README include to be cfg gated --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 77890a6..1d26374 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ // ON_RELEASE: the below link(s) should be verified to match the readme, and // this "on release" comment removed (the above one should stay). //! [`Pair`]: Pair -#![doc = include_str!("../README.md")] +#![cfg_attr(any(doc, test), doc = include_str!("../README.md"))] mod owner; mod pair; From 8345c74304a85fab39c5e932aef2a366db42bb8d Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 5 Mar 2025 01:01:42 -0500 Subject: [PATCH 066/110] added no_std support --- Cargo.toml | 12 +++++++-- ci.sh | 5 ++++ src/lib.rs | 12 +++++++++ src/owner.rs | 7 +++-- src/pair.rs | 39 ++++++++++++++++----------- src/panicking.rs | 62 +++++++++++++++++++++++++++++++++++++++++++ tests/panic_safety.rs | 12 ++++----- 7 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 src/panicking.rs diff --git a/Cargo.toml b/Cargo.toml index 387bd3c..ea60097 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ version = "0.1.0" authors = ["Isaac Chen"] description = "Safe API for generic self-referential pairs of owner and dependent." license = "MIT OR Apache-2.0" -keywords = ["self-referential", "data-structures", "memory", "ownership", "lifetime"] -categories = ["data-structures", "memory-management", "rust-patterns"] # TODO: "no-std" +keywords = ["self-referential", "data-structures", "memory", "ownership", "no_std"] +categories = ["memory-management", "rust-patterns", "no-std", "data-structures"] readme = "README.md" repository = "https://github.com/ijchen/pair" documentation = "https://docs.rs/pair" @@ -22,4 +22,12 @@ publish = false loom = "0.7.2" trybuild = "1.0.103" +[features] +default = ["std"] + +# The `std` feature (enabled by default) enables usage of the standard library +# within pair. Without the standard library, pair cannot handle panics as +# gracefully. +std = [] + # TODO: define lints diff --git a/ci.sh b/ci.sh index d421152..ce75dbe 100755 --- a/ci.sh +++ b/ci.sh @@ -1,12 +1,17 @@ cargo fmt --check && \ cargo clippy -- -Dwarnings && \ +cargo clippy --no-default-features -- -Dwarnings && \ RUSTFLAGS="-Dwarnings" cargo test && \ +RUSTFLAGS="-Dwarnings" cargo test --no-default-features -- --skip std_only && \ # TODO: trybuild tests fail on nightly (and beta) - error messages changed RUSTFLAGS="-Dwarnings" cargo +nightly test -- --skip try_builds && \ +RUSTFLAGS="-Dwarnings" cargo +nightly test --no-default-features -- --skip try_builds --skip std_only && \ RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test -- --skip try_builds --skip loom && \ +RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test --no-default-features -- --skip try_builds --skip loom --skip std_only && \ ( cp Cargo.toml Cargo.toml.backup && \ trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ MIRIFLAGS="-Zmiri-retag-fields" cargo +nightly miri test -- --skip nomiri --skip loom + MIRIFLAGS="-Zmiri-retag-fields" cargo +nightly miri test --no-default-features -- --skip nomiri --skip loom --skip std_only ) diff --git a/src/lib.rs b/src/lib.rs index 1d26374..da3cabd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,21 @@ // this "on release" comment removed (the above one should stay). //! [`Pair`]: Pair #![cfg_attr(any(doc, test), doc = include_str!("../README.md"))] +#![no_std] + +#[cfg(not(feature = "std"))] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +// TODO: move this to-do list to a to-do.md or smth +// - re-organize Cargo.toml to be more readable (esp. package section order) +// - document feature flags in README +// - clean up ci.sh mod owner; mod pair; +mod panicking; pub use owner::{HasDependent, Owner}; pub use pair::Pair; diff --git a/src/owner.rs b/src/owner.rs index 21593d7..1dfaddf 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -1,3 +1,6 @@ +//! Defines the [`Owner`] and [`HasDependent`] traits, the common interface for +//! types stored in a [`Pair`](crate::Pair). + /// Defines the dependent type for the [`Owner`] trait. /// /// Semantically, you can think of this like a lifetime Generic Associated Type @@ -40,7 +43,7 @@ pub trait Owner: for<'any> HasDependent<'any> { /// If `make_dependent` can't fail, this should be set to /// [`Infallible`](std::convert::Infallible). // - // TODO(ichen): default this to std::convert::Infallible (or preferably !) + // TODO(ichen): default this to core::convert::Infallible (or preferably !) // when associated type defaults are stabilized // (https://github.com/rust-lang/rust/issues/29661) type Error; @@ -63,7 +66,7 @@ mod sealed { /// The `ForImpliedBounds` generic type for /// [`HasDependent`](super::HasDependent) should not be overridden from its /// default. - pub struct Bounds(std::marker::PhantomData); + pub struct Bounds(core::marker::PhantomData); impl Sealed for Bounds {} } use sealed::{Bounds, Sealed}; diff --git a/src/pair.rs b/src/pair.rs index 2f739f2..de14d9f 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -1,9 +1,16 @@ -use std::{ - convert::Infallible, fmt::Debug, marker::PhantomData, mem::ManuallyDrop, - panic::AssertUnwindSafe, ptr::NonNull, -}; +//! Defines [`Pair`], the primary abstraction provided by this crate. + +use core::{convert::Infallible, fmt::Debug, marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; -use crate::{HasDependent, Owner}; +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; +#[cfg(feature = "std")] +use std::boxed::Box; + +use crate::{ + HasDependent, Owner, + panicking::{catch_unwind, resume_unwind}, +}; /// A self-referential pair containing both some [`Owner`] and its /// [`Dependent`](HasDependent::Dependent). @@ -98,7 +105,7 @@ impl Pair { // be able to drop the boxed owner before unwinding the rest of the // stack to avoid unnecessarily leaking memory (and potentially other // resources). - let maybe_dependent = match std::panic::catch_unwind(AssertUnwindSafe(|| { + let maybe_dependent = match catch_unwind(|| { // SAFETY: `owner` was just converted from a valid Box, and inherits // the alignment and validity guarantees of Box. Additionally, the // value behind the pointer is currently not borrowed at all - this @@ -106,7 +113,7 @@ impl Pair { // returned `Pair` is dropped (or immediately, if `make_dependent` // panics or returns an error). unsafe { owner.as_ref() }.make_dependent(context) - })) { + }) { Ok(maybe_dependent) => maybe_dependent, Err(payload) => { // make_dependent panicked - drop the owner, then resume_unwind @@ -124,11 +131,11 @@ impl Pair { // payload of the first panic (from `make_dependent`), so if the // owner's drop panics we just ignore it and continue on to // resume_unwind with `make_dependent`'s payload. - let _ = std::panic::catch_unwind(AssertUnwindSafe(|| drop(owner))); + let _ = catch_unwind(|| drop(owner)); // It's very important that we diverge here - carrying on to the // rest of this constructor would be unsound. - std::panic::resume_unwind(payload); + resume_unwind(payload); } }; @@ -270,7 +277,7 @@ impl Pair { // We're about to drop the dependent - if it panics, we want to be able // to drop the boxed owner before unwinding the rest of the stack to // avoid unnecessarily leaking memory (and potentially other resources). - if let Err(payload) = std::panic::catch_unwind(AssertUnwindSafe(|| drop(dependent))) { + if let Err(payload) = catch_unwind(|| drop(dependent)) { // Dependent's drop panicked - drop the owner, then resume_unwind // SAFETY: `this.owner` was originally created from a Box, and never @@ -286,11 +293,11 @@ impl Pair { // the first panic (from dependent's drop), so if the owner's drop // panics we just ignore it and continue on to resume_unwind with // the dependent's payload. - let _ = std::panic::catch_unwind(AssertUnwindSafe(|| drop(owner))); + let _ = catch_unwind(|| drop(owner)); // It's very important that we diverge here - carrying on to the // rest of this function would be unsound. - std::panic::resume_unwind(payload); + resume_unwind(payload); } // SAFETY: `this.owner` was originally created from a Box, and never @@ -465,7 +472,7 @@ impl Drop for Pair { // We're about to drop the dependent - if it panics, we want to be able // to drop the boxed owner before unwinding the rest of the stack to // avoid unnecessarily leaking memory (and potentially other resources). - if let Err(payload) = std::panic::catch_unwind(AssertUnwindSafe(|| drop(dependent))) { + if let Err(payload) = catch_unwind(|| drop(dependent)) { // Dependent's drop panicked - drop the owner, then resume_unwind // SAFETY: `this.owner` was originally created from a Box, and never @@ -481,11 +488,11 @@ impl Drop for Pair { // the first panic (from dependent's drop), so if the owner's drop // panics we just ignore it and continue on to resume_unwind with // the dependent's payload. - let _ = std::panic::catch_unwind(AssertUnwindSafe(|| drop(owner))); + let _ = catch_unwind(|| drop(owner)); // It's very important that we diverge here - carrying on to the // rest of drop would be unsound. - std::panic::resume_unwind(payload); + resume_unwind(payload); } // Drop the owner `Box` @@ -528,7 +535,7 @@ impl Debug for Pair where for<'any> >::Dependent: Debug, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.with_dependent(|dependent| { f.debug_struct("Pair") .field("owner", &self.get_owner()) diff --git a/src/panicking.rs b/src/panicking.rs new file mode 100644 index 0000000..7014ad4 --- /dev/null +++ b/src/panicking.rs @@ -0,0 +1,62 @@ +//! Panic handling abstracted to work with and without `#[cfg(feature = "std")]` + +#[cfg(feature = "std")] +use std::boxed::Box; + +#[cfg(feature = "std")] +type PanicPayloadInner = Box; +#[cfg(not(feature = "std"))] +type PanicPayloadInner = core::convert::Infallible; + +/// A panic payload, abstracted to work with and without +/// `#[cfg(feature = "std")]`. +/// +/// With `std`, this will contain a normal panic payload +/// (`Box`). +/// +/// Without `std`, this will never be constructed, and contains +/// `std::convert::Infallible`. +pub struct PanicPayload(PanicPayloadInner); + +/// [`std::panic::catch_unwind`], abstracted to work with and without `std`. +/// +/// With `std`, this function just delegates to [`std::panic::catch_unwind`]. +/// +/// Without `std`, this function will call the provided closure without +/// attempting to catch panics at all - it will therefore always either return +/// [`Ok`] or diverge. +/// +/// Note that this function additionally does not require the closure is +/// [`UnwindSafe`](core::panic::UnwindSafe) - our usage within this crate would +/// be wrapping all calls in [`AssertUnwindSafe`](core::panic::AssertUnwindSafe) +/// anyway. It would be difficult for an API consumer to observe violated +/// invariants through unwind unsafety, and the API burden on normal use cases +/// would be too heavy if we didn't assert unwind safety on their behalf. +pub fn catch_unwind R, R>(f: F) -> Result { + // If we have `std`, delegate to `catch_unwind` + #[cfg(feature = "std")] + let output = std::panic::catch_unwind(core::panic::AssertUnwindSafe(f)).map_err(PanicPayload); + + // If we don't have `std`, just call the function and let panics go uncaught + #[cfg(not(feature = "std"))] + let output = Ok(f()); + + output +} + +/// [`std::panic::resume_unwind`], abstracted to work with and without `std`. +/// +/// With `std`, this function just delegates to [`std::panic::resume_unwind`]. +/// +/// Without `std`, this function is impossible to call - a [`PanicPayload`] is +/// never produced by [`catch_unwind`] without `std`. +pub fn resume_unwind(payload: PanicPayload) -> ! { + // If we have `std`, delegate to `resume_unwind` + #[cfg(feature = "std")] + std::panic::resume_unwind(payload.0); + + // If we don't have `std`, a PanicPayload can never be produced, so this + // function can't be called in the first place + #[cfg(not(feature = "std"))] + match payload {} +} diff --git a/tests/panic_safety.rs b/tests/panic_safety.rs index f359296..e7a2d42 100644 --- a/tests/panic_safety.rs +++ b/tests/panic_safety.rs @@ -35,7 +35,7 @@ impl Owner for PanicOnMakeDependent { } #[test] -fn make_dependent_panic_handled() { +fn make_dependent_panic_handled_std_only() { let owner_drop_called = Rc::new(RefCell::new(false)); let owner_drop_called2 = Rc::clone(&owner_drop_called); let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| { @@ -75,7 +75,7 @@ impl Owner for PanicOnMakeDependentAndOwnerDrop { } #[test] -fn make_dependent_and_owner_drop_panic_handled() { +fn make_dependent_and_owner_drop_panic_handled_std_only() { let owner_drop_called = Rc::new(RefCell::new(false)); let owner_drop_called2 = Rc::clone(&owner_drop_called); let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| { @@ -121,7 +121,7 @@ impl Owner for PanicOnDepDropIntoOwner { } #[test] -fn dependent_drop_panic_handled_in_into_owner() { +fn dependent_drop_panic_handled_in_into_owner_std_only() { let owner_drop_called = Rc::new(RefCell::new(false)); let pair = Pair::new(PanicOnDepDropIntoOwner(Rc::clone(&owner_drop_called))); let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| pair.into_owner())) @@ -166,7 +166,7 @@ impl Owner for PanicOnDepAndOwnerDropIntoOwner { } #[test] -fn dependent_and_owner_drop_panic_handled_in_into_owner() { +fn dependent_and_owner_drop_panic_handled_in_into_owner_std_only() { let owner_drop_called = Rc::new(RefCell::new(false)); let pair = Pair::new(PanicOnDepAndOwnerDropIntoOwner(Rc::clone( &owner_drop_called, @@ -212,7 +212,7 @@ impl Owner for PanicOnDepDropPairDrop { } #[test] -fn dependent_drop_panic_handled_in_pair_drop() { +fn dependent_drop_panic_handled_in_pair_drop_std_only() { let owner_drop_called = Rc::new(RefCell::new(false)); let pair = Pair::new(PanicOnDepDropPairDrop(Rc::clone(&owner_drop_called))); let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| drop(pair))) @@ -257,7 +257,7 @@ impl Owner for PanicOnDepAndOwnerDropPairDrop { } #[test] -fn dependent_and_owner_drop_panic_handled_in_pair_drop() { +fn dependent_and_owner_drop_panic_handled_in_pair_drop_std_only() { let owner_drop_called = Rc::new(RefCell::new(false)); let pair = Pair::new(PanicOnDepAndOwnerDropPairDrop(Rc::clone( &owner_drop_called, From 9462c67ec2166f77d9d586c1acaeb8305c29e601 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 5 Mar 2025 01:18:11 -0500 Subject: [PATCH 067/110] clippy has arrived --- Cargo.toml | 44 +++++++++++++++++++++++++++++++++++- src/owner.rs | 6 +++++ src/pair.rs | 27 ++++++++++++++++++---- src/panicking.rs | 7 ++++++ tests/alternative_ctors.rs | 2 ++ tests/basic_usage.rs | 2 ++ tests/concurrent_loom.rs | 2 ++ tests/concurrent_std.rs | 2 ++ tests/contravariance.rs | 2 ++ tests/debug.rs | 3 +++ tests/default.rs | 2 ++ tests/drop.rs | 4 +++- tests/dyn_trait_owner.rs | 2 ++ tests/interior_mutability.rs | 2 ++ tests/multiborrow.rs | 2 ++ tests/nested_pair.rs | 2 ++ tests/panic_safety.rs | 14 +++++++----- tests/trybuild_test.rs | 2 ++ tests/unsized_owner.rs | 2 ++ tests/zst.rs | 8 ++++--- 20 files changed, 121 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea60097..9a71876 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ include = ["/src/", "/Cargo.toml", "/README.md", "/CHANGELOG.md", "/LICENSE-*"] # ON_RELEASE: Remove publish = false publish = false + + [dependencies] # No dependencies @@ -22,6 +24,8 @@ publish = false loom = "0.7.2" trybuild = "1.0.103" + + [features] default = ["std"] @@ -30,4 +34,42 @@ default = ["std"] # gracefully. std = [] -# TODO: define lints + + +[lints.rust] +# "deprecated_safe" lint group +deprecated_safe = { level = "warn", priority = -1 } +# "future_incompatible" lint group +future_incompatible = { level = "warn", priority = -1 } +# "keyword_idents" lint group +keyword_idents = { level = "warn", priority = -1 } +# Cherry-picked lint overrides +unstable_features = "forbid" # do not permit nightly-only features +elided_lifetimes_in_paths = "warn" # elided lifetimes are unclear +missing_debug_implementations = "warn" # all public types should impl Debug +missing_docs = "warn" # all public items should be documented +non_ascii_idents = "warn" # non-ASCII identifiers could be confusing +redundant_imports = "warn" # redundant imports are unnecessary +redundant_lifetimes = "warn" # duplicate identical lifetimes are unnecessary +single_use_lifetimes = "warn" # single-use lifetimes are unnecessary +unnameable_types = "warn" # unnameable types should be considered case-by-case +unused_qualifications = "warn" # overly-qualified paths decrease readability + +[lints.clippy] +# "pedantic" lint group +pedantic = { level = "warn", priority = -1 } +must_use_candidate = "allow" # too many false positives +# "cargo" lint group +cargo = { level = "warn", priority = -1 } +# Cherry-picked lint overrides +multiple_unsafe_ops_per_block = "forbid" # enforce all unsafe ops have a SAFETY comment +undocumented_unsafe_blocks = "forbid" # enforce all unsafe ops have a SAFETY comment +unnecessary_safety_comment = "warn" # unnecessary SAFETY comments would be confusing +unnecessary_safety_doc = "warn" # unnecessary "# Safety" sections would be confusing +allow_attributes = "warn" # prefer #[expect(..)] +allow_attributes_without_reason = "warn" # should always include a reason +dbg_macro = "warn" # this macro should only be used during development +todo = "warn" # this macro should only be used during development +cognitive_complexity = "warn" # can be #[expect(..)]ed, but useful as a warning +too_long_first_doc_paragraph = "warn" # first doc paragraph should be brief +use_self = "warn" # `Self` is more clear when applicable diff --git a/src/owner.rs b/src/owner.rs index 1dfaddf..f63b905 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -19,6 +19,10 @@ pub trait HasDependent<'owner, ForImpliedBound: Sealed = Bounds<&'owner Self>> { type Dependent; } +#[expect( + clippy::missing_errors_doc, + reason = "failure modes are specific to the trait's implementation" +)] /// A type which can act as the "owner" of some data, and can produce some /// dependent type which borrows from `Self`. Used for the [`Pair`](crate::Pair) /// struct. @@ -59,10 +63,12 @@ pub trait Owner: for<'any> HasDependent<'any> { /// Used to prevent implementors of [`HasDependent`] from overriding the /// `ForImpliedBounds` generic type from its default. mod sealed { + #![expect(unnameable_types, reason = "...kinda the point")] /// The `ForImpliedBounds` generic type for /// [`HasDependent`](super::HasDependent) should not be overridden from its /// default. pub trait Sealed {} + #[derive(Debug)] /// The `ForImpliedBounds` generic type for /// [`HasDependent`](super::HasDependent) should not be overridden from its /// default. diff --git a/src/pair.rs b/src/pair.rs index de14d9f..9d1fc67 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -45,13 +45,14 @@ pub struct Pair { prevent_covariance: PhantomData<*mut O>, } -/// Creates a [`NonNull`] from [`Box`]. The returned NonNull is the same +/// Creates a [`NonNull`] from [`Box`]. The returned `NonNull` is the same /// pointer as the Box, and therefore comes with all of Box's representation /// guarantees: -/// - The returned NonNull will be suitably aligned for T -/// - The returned NonNull will point to a valid T -/// - The returned NonNull was allocated with the [`Global`](std::alloc::Global) -/// allocator and a valid [`Layout`](std::alloc::Layout) for `T`. +/// - The returned `NonNull` will be suitably aligned for T +/// - The returned `NonNull` will point to a valid T +/// - The returned `NonNull` was allocated with the +/// [`Global`](std::alloc::Global) allocator and a valid +/// [`Layout`](std::alloc::Layout) for `T`. fn non_null_from_box(value: Box) -> NonNull { // See: https://github.com/rust-lang/rust/issues/47336#issuecomment-586578713 NonNull::from(Box::leak(value)) @@ -69,6 +70,10 @@ impl Pair { /// /// If this construction can't fail, consider the convenience constructor /// [`Pair::new_with_context`], which returns `Self` directly. + /// + /// # Errors + /// If [`::make_dependent`](Owner::make_dependent) returns an + /// error. pub fn try_new_with_context(owner: O, context: O::Context<'_>) -> Result where O: Sized, @@ -89,6 +94,10 @@ impl Pair { /// /// If this construction can't fail, consider the convenience constructor /// [`Pair::new_from_box_with_context`], which returns `Self` directly. + /// + /// # Errors + /// If [`::make_dependent`](Owner::make_dependent) returns an + /// error. pub fn try_new_from_box_with_context( owner: Box, context: O::Context<'_>, @@ -370,6 +379,10 @@ impl Owner = ()> + ?Sized> Pair { /// /// If this construction can't fail, consider the convenience constructor /// [`Pair::new`], which returns `Self` directly. + /// + /// # Errors + /// If [`::make_dependent`](Owner::make_dependent) returns an + /// error. pub fn try_new(owner: O) -> Result where O: Sized, @@ -390,6 +403,10 @@ impl Owner = ()> + ?Sized> Pair { /// /// If this construction can't fail, consider the convenience constructor /// [`Pair::new_from_box`], which returns `Self` directly. + /// + /// # Errors + /// If [`::make_dependent`](Owner::make_dependent) returns an + /// error. pub fn try_new_from_box(owner: Box) -> Result, O::Error)> { Self::try_new_from_box_with_context(owner, ()) } diff --git a/src/panicking.rs b/src/panicking.rs index 7014ad4..b76921f 100644 --- a/src/panicking.rs +++ b/src/panicking.rs @@ -44,6 +44,13 @@ pub fn catch_unwind R, R>(f: F) -> Result { output } +#[cfg_attr( + not(feature = "std"), + expect( + clippy::needless_pass_by_value, + reason = "needs to be pass by value for std" + ) +)] /// [`std::panic::resume_unwind`], abstracted to work with and without `std`. /// /// With `std`, this function just delegates to [`std::panic::resume_unwind`]. diff --git a/tests/alternative_ctors.rs b/tests/alternative_ctors.rs index 59118f8..09b8f8f 100644 --- a/tests/alternative_ctors.rs +++ b/tests/alternative_ctors.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs index 14a06fd..24dbaab 100644 --- a/tests/basic_usage.rs +++ b/tests/basic_usage.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/concurrent_loom.rs b/tests/concurrent_loom.rs index 8396ee8..162fc23 100644 --- a/tests/concurrent_loom.rs +++ b/tests/concurrent_loom.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::sync::{Arc, Mutex, RwLock, mpsc}; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/concurrent_std.rs b/tests/concurrent_std.rs index c9fc87e..33982b1 100644 --- a/tests/concurrent_std.rs +++ b/tests/concurrent_std.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::sync::{Arc, Mutex, RwLock, mpsc}; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/contravariance.rs b/tests/contravariance.rs index c4b1d54..099ffc4 100644 --- a/tests/contravariance.rs +++ b/tests/contravariance.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/debug.rs b/tests/debug.rs index 60444ee..5f05ee9 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::{borrow::Cow, convert::Infallible, fmt::Debug}; use pair::{HasDependent, Owner}; @@ -75,6 +77,7 @@ macro_rules! debug_tests { $struct_or_enum $name $decl_body $(; $semicolon_exists)? impl<$lt> HasDependent<$lt> for $name { + #![allow(single_use_lifetimes, reason = "macros lol")] type Dependent = $dep_ty; } impl Owner for $name { diff --git a/tests/default.rs b/tests/default.rs index d6bffdc..128f7b8 100644 --- a/tests/default.rs +++ b/tests/default.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use pair::{HasDependent, Owner, Pair}; use std::{convert::Infallible, fmt::Debug}; diff --git a/tests/drop.rs b/tests/drop.rs index 529d251..0e33810 100644 --- a/tests/drop.rs +++ b/tests/drop.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::{cell::RefCell, convert::Infallible, rc::Rc}; use pair::{HasDependent, Owner, Pair}; @@ -22,7 +24,7 @@ impl Drop for OnDropDep { (self.f)(&mut self.value.borrow_mut()) } } -impl<'owner, T> HasDependent<'owner> for OnDrop { +impl HasDependent<'_> for OnDrop { type Dependent = OnDropDep; } diff --git a/tests/dyn_trait_owner.rs b/tests/dyn_trait_owner.rs index 093d7fc..c77abc2 100644 --- a/tests/dyn_trait_owner.rs +++ b/tests/dyn_trait_owner.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/interior_mutability.rs b/tests/interior_mutability.rs index 9dcd90d..ba5c0a5 100644 --- a/tests/interior_mutability.rs +++ b/tests/interior_mutability.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use pair::{HasDependent, Owner, Pair}; use std::cell::{Cell, RefCell}; use std::convert::Infallible; diff --git a/tests/multiborrow.rs b/tests/multiborrow.rs index ae56b98..111a100 100644 --- a/tests/multiborrow.rs +++ b/tests/multiborrow.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use pair::{HasDependent, Owner, Pair}; use std::convert::Infallible; diff --git a/tests/nested_pair.rs b/tests/nested_pair.rs index f74ec2b..328227c 100644 --- a/tests/nested_pair.rs +++ b/tests/nested_pair.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use pair::{HasDependent, Owner, Pair}; use std::convert::Infallible; diff --git a/tests/panic_safety.rs b/tests/panic_safety.rs index e7a2d42..e08a0b5 100644 --- a/tests/panic_safety.rs +++ b/tests/panic_safety.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::{ cell::RefCell, convert::Infallible, @@ -18,7 +20,7 @@ impl Drop for PanicOnMakeDependent { } } -impl<'owner> HasDependent<'owner> for PanicOnMakeDependent { +impl HasDependent<'_> for PanicOnMakeDependent { type Dependent = (); } @@ -58,7 +60,7 @@ impl Drop for PanicOnMakeDependentAndOwnerDrop { } } -impl<'owner> HasDependent<'owner> for PanicOnMakeDependentAndOwnerDrop { +impl HasDependent<'_> for PanicOnMakeDependentAndOwnerDrop { type Dependent = (); } @@ -104,7 +106,7 @@ impl Drop for PanicOnDepDropIntoOwnerDep { panic_any(MyPayload(11)); } } -impl<'owner> HasDependent<'owner> for PanicOnDepDropIntoOwner { +impl HasDependent<'_> for PanicOnDepDropIntoOwner { type Dependent = PanicOnDepDropIntoOwnerDep; } @@ -149,7 +151,7 @@ impl Drop for PanicOnDepAndOwnerDropIntoOwnerDep { panic_any(MyPayload(1)); } } -impl<'owner> HasDependent<'owner> for PanicOnDepAndOwnerDropIntoOwner { +impl HasDependent<'_> for PanicOnDepAndOwnerDropIntoOwner { type Dependent = PanicOnDepAndOwnerDropIntoOwnerDep; } @@ -195,7 +197,7 @@ impl Drop for PanicOnDepDropPairDropDep { panic_any(MyPayload(3)); } } -impl<'owner> HasDependent<'owner> for PanicOnDepDropPairDrop { +impl HasDependent<'_> for PanicOnDepDropPairDrop { type Dependent = PanicOnDepDropPairDropDep; } @@ -240,7 +242,7 @@ impl Drop for PanicOnDepAndOwnerDropPairDropDep { panic_any(MyPayload(5)); } } -impl<'owner> HasDependent<'owner> for PanicOnDepAndOwnerDropPairDrop { +impl HasDependent<'_> for PanicOnDepAndOwnerDropPairDrop { type Dependent = PanicOnDepAndOwnerDropPairDropDep; } diff --git a/tests/trybuild_test.rs b/tests/trybuild_test.rs index 65b4bbd..82be4fb 100644 --- a/tests/trybuild_test.rs +++ b/tests/trybuild_test.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + #[test] fn try_builds_nomiri() { let t = trybuild::TestCases::new(); diff --git a/tests/unsized_owner.rs b/tests/unsized_owner.rs index 252a721..fba2062 100644 --- a/tests/unsized_owner.rs +++ b/tests/unsized_owner.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/zst.rs b/tests/zst.rs index ea5e6de..fc75d0e 100644 --- a/tests/zst.rs +++ b/tests/zst.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs, reason = "integration test")] + use pair::{HasDependent, Owner, Pair}; use std::convert::Infallible; @@ -8,7 +10,7 @@ struct ZstOwner; #[derive(Debug, PartialEq)] struct NonZstDependent(i32); -impl<'owner> HasDependent<'owner> for ZstOwner { +impl HasDependent<'_> for ZstOwner { type Dependent = NonZstDependent; } @@ -41,7 +43,7 @@ struct NonZstOwner(i32); #[derive(Debug)] struct ZstDependent; -impl<'owner> HasDependent<'owner> for NonZstOwner { +impl HasDependent<'_> for NonZstOwner { type Dependent = ZstDependent; } @@ -70,7 +72,7 @@ fn test_zst_dependent() { // Both owner and dependent are ZSTs struct BothZstOwner; -impl<'owner> HasDependent<'owner> for BothZstOwner { +impl HasDependent<'_> for BothZstOwner { type Dependent = ZstDependent; } From f537c3f5d89ce23dd3e33b00279ea3da20fded2c Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Wed, 5 Mar 2025 19:18:13 -0500 Subject: [PATCH 068/110] Organized (perhaps excessively) Cargo.toml --- Cargo.toml | 48 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a71876..6c542d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,30 @@ +# # # # # # # # # # # # # # # # # # # # +# # +# PACKAGE # +# # +# # # # # # # # # # # # # # # # # # # # + [package] name = "pair" -# ON_RELEASE: Bump version. Don't forget to do all other "ON_RELEASE" tasks! -version = "0.1.0" +version = "0.1.0" # ON_RELEASE: Bump version. Also, do all "ON_RELEASE" tasks authors = ["Isaac Chen"] +edition = "2024" +# rust-version = "TODO" # TODO description = "Safe API for generic self-referential pairs of owner and dependent." +documentation = "https://docs.rs/pair" +readme = "README.md" +repository = "https://github.com/ijchen/pair" license = "MIT OR Apache-2.0" keywords = ["self-referential", "data-structures", "memory", "ownership", "no_std"] categories = ["memory-management", "rust-patterns", "no-std", "data-structures"] -readme = "README.md" -repository = "https://github.com/ijchen/pair" -documentation = "https://docs.rs/pair" -edition = "2024" include = ["/src/", "/Cargo.toml", "/README.md", "/CHANGELOG.md", "/LICENSE-*"] -# ON_RELEASE: Remove publish = false -publish = false - +publish = false # ON_RELEASE: Remove publish = false +# # # # # # # # # # # # # # # # # # # # +# # +# DEPENDENCIES # +# # +# # # # # # # # # # # # # # # # # # # # [dependencies] # No dependencies @@ -24,7 +33,11 @@ publish = false loom = "0.7.2" trybuild = "1.0.103" - +# # # # # # # # # # # # # # # # # # # # +# # +# FEATURES # +# # +# # # # # # # # # # # # # # # # # # # # [features] default = ["std"] @@ -34,15 +47,23 @@ default = ["std"] # gracefully. std = [] - +# # # # # # # # # # # # # # # # # # # # +# # +# LINTS # +# # +# # # # # # # # # # # # # # # # # # # # [lints.rust] + # "deprecated_safe" lint group deprecated_safe = { level = "warn", priority = -1 } + # "future_incompatible" lint group future_incompatible = { level = "warn", priority = -1 } + # "keyword_idents" lint group keyword_idents = { level = "warn", priority = -1 } + # Cherry-picked lint overrides unstable_features = "forbid" # do not permit nightly-only features elided_lifetimes_in_paths = "warn" # elided lifetimes are unclear @@ -55,12 +76,17 @@ single_use_lifetimes = "warn" # single-use lifetimes are unnecessary unnameable_types = "warn" # unnameable types should be considered case-by-case unused_qualifications = "warn" # overly-qualified paths decrease readability + + [lints.clippy] + # "pedantic" lint group pedantic = { level = "warn", priority = -1 } must_use_candidate = "allow" # too many false positives + # "cargo" lint group cargo = { level = "warn", priority = -1 } + # Cherry-picked lint overrides multiple_unsafe_ops_per_block = "forbid" # enforce all unsafe ops have a SAFETY comment undocumented_unsafe_blocks = "forbid" # enforce all unsafe ops have a SAFETY comment From c7d311d389e41885eb5dfa77abd7a0d97dc280bd Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 6 Mar 2025 20:07:02 -0500 Subject: [PATCH 069/110] updated to-do list --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index da3cabd..eeeee2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,6 @@ extern crate alloc; extern crate std; // TODO: move this to-do list to a to-do.md or smth -// - re-organize Cargo.toml to be more readable (esp. package section order) // - document feature flags in README // - clean up ci.sh From 4703a0febce52361cd815bec591f5057cb8900d9 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 8 Mar 2025 02:51:05 -0500 Subject: [PATCH 070/110] CI is real... surely it works first try --- .github/workflows/ci.yaml | 103 +++++++++++++++++++++ Cargo.toml | 2 +- ci.sh | 188 +++++++++++++++++++++++++++++++++++--- tests/basic_usage.rs | 7 +- tests/concurrent_loom.rs | 8 +- tests/debug.rs | 12 ++- tests/drop.rs | 4 +- tests/multiborrow.rs | 20 ++-- 8 files changed, 309 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..46eef16 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,103 @@ +name: CI + +# Run on any push or any PR (both for any branch) +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always # Pretty colors +jobs: + check_fmt: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh check_fmt + + check_docs: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh check_docs + + build: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh build + + lint: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh lint + + run_tests_stable: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh run_tests_stable + + run_tests_beta: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@beta + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh run_tests_beta + + run_tests_msrv: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.85.0 + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh run_tests_msrv + + run_tests_leak_sanitizer: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh run_tests_leak_sanitizer + + run_tests_miri: + name: TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: miri + - uses: Swatinem/rust-cache@v2 + - name: TODO # TODO + run: ./ci.sh run_tests_miri diff --git a/Cargo.toml b/Cargo.toml index 6c542d3..80c1f46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ name = "pair" version = "0.1.0" # ON_RELEASE: Bump version. Also, do all "ON_RELEASE" tasks authors = ["Isaac Chen"] edition = "2024" -# rust-version = "TODO" # TODO +rust-version = "1.85.0" description = "Safe API for generic self-referential pairs of owner and dependent." documentation = "https://docs.rs/pair" readme = "README.md" diff --git a/ci.sh b/ci.sh index ce75dbe..b547d81 100755 --- a/ci.sh +++ b/ci.sh @@ -1,17 +1,177 @@ -cargo fmt --check && \ -cargo clippy -- -Dwarnings && \ -cargo clippy --no-default-features -- -Dwarnings && \ -RUSTFLAGS="-Dwarnings" cargo test && \ -RUSTFLAGS="-Dwarnings" cargo test --no-default-features -- --skip std_only && \ -# TODO: trybuild tests fail on nightly (and beta) - error messages changed -RUSTFLAGS="-Dwarnings" cargo +nightly test -- --skip try_builds && \ -RUSTFLAGS="-Dwarnings" cargo +nightly test --no-default-features -- --skip try_builds --skip std_only && \ -RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test -- --skip try_builds --skip loom && \ -RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test --no-default-features -- --skip try_builds --skip loom --skip std_only && \ -( +#!/usr/bin/env bash +set -euo pipefail + +# Prints text formatted as a header (colors and arrows and stuff, very cool) +print_header() { + echo -e "\e[1;34m==>\e[0m \e[1m$1\e[0m" +} + +check_fmt() { + print_header 'Checking code formatting...' + RUSTFLAGS='-D warnings' cargo +stable fmt --check +} + +check_docs() { + print_header 'Building documentation...' + RUSTDOCFLAGS='-D warnings' cargo +stable doc --document-private-items --no-deps +} + +build() { + print_header 'Running cargo build (default features)...' + RUSTFLAGS='-D warnings' cargo +stable build --all-targets + + print_header 'Running cargo build (no features)...' + RUSTFLAGS='-D warnings' cargo +stable build --all-targets --no-default-features + + print_header 'Running cargo build (all features)...' + RUSTFLAGS='-D warnings' cargo +stable build --all-targets --all-features +} + +lint() { + print_header 'Linting with cargo clippy (default features)...' + cargo +stable clippy --no-deps --all-targets -- -D warnings + + print_header 'Linting with cargo clippy (no features)...' + cargo +stable clippy --no-deps --all-targets --no-default-features -- -D warnings + + print_header 'Linting with cargo clippy (all features)...' + cargo +stable clippy --no-deps --all-targets --all-features -- -D warnings +} + +run_tests_stable() { + print_header 'Running tests (stable compiler, default features)...' + RUSTFLAGS='-D warnings' cargo +stable test + + # NOTE: some tests (containing `std_only`) require the `std` feature to run. + print_header 'Running tests (stable compiler, no features)...' + RUSTFLAGS='-D warnings' cargo +stable test --no-default-features -- --skip std_only + + print_header 'Running tests (stable compiler, all features)...' + RUSTFLAGS='-D warnings' cargo +stable test --all-features +} + +run_tests_beta() { + # TODO: trybuild tests fail on nightly (and beta) - error messages changed + + print_header 'Running tests (beta compiler, default features)...' + RUSTFLAGS='-D warnings' cargo +beta test -- --skip try_builds + + # NOTE: some tests (containing `std_only`) require the `std` feature to run. + print_header 'Running tests (beta compiler, no features)...' + RUSTFLAGS='-D warnings' cargo +beta test --no-default-features -- --skip std_only --skip try_builds + + print_header 'Running tests (beta compiler, all features)...' + RUSTFLAGS='-D warnings' cargo +beta test --all-features -- --skip try_builds +} + +run_tests_msrv() { + local msrv="1.85.0" + + print_header "Running tests (MSRV compiler ($msrv), default features)..." + RUSTFLAGS='-D warnings' cargo "+$msrv" test + + # NOTE: some tests (containing `std_only`) require the `std` feature to run. + print_header "Running tests (MSRV compiler ($msrv), no features)..." + RUSTFLAGS='-D warnings' cargo "+$msrv" test --no-default-features -- --skip std_only + + print_header "Running tests (MSRV compiler ($msrv), all features)..." + RUSTFLAGS='-D warnings' cargo "+$msrv" test --all-features +} + +run_tests_leak_sanitizer() { + # NOTE: loom seems to make the leak sanitizer unhappy. I don't think that + # combination of tests is important, so we just skip loom tests here. + # TODO: trybuild tests fail on nightly (and beta) - error messages changed + + print_header 'Running tests with leak sanitizer (default features)...' + RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test -- --skip loom --skip try_builds + + # NOTE: some tests (containing `std_only`) require the `std` feature to run. + print_header 'Running tests with leak sanitizer (no features)...' + RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --no-default-features -- --skip std_only --skip loom --skip try_builds + + print_header 'Running tests with leak sanitizer (all features)...' + RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --all-features -- --skip loom --skip try_builds +} + +run_tests_miri() { + # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are + # skipped here. + # TODO: figure out any MIRI flags we want (retag fields thing?) + + # TODO: MIRI currently has a bug where it just hangs on doctests in edition + # 2024 - until this is fixed, we do some jank to revert edition temporarily cp Cargo.toml Cargo.toml.backup && \ trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ - MIRIFLAGS="-Zmiri-retag-fields" cargo +nightly miri test -- --skip nomiri --skip loom - MIRIFLAGS="-Zmiri-retag-fields" cargo +nightly miri test --no-default-features -- --skip nomiri --skip loom --skip std_only -) + + print_header 'Running tests with MIRI (default features)...' + RUSTFLAGS='-D warnings' cargo +nightly miri test -- --skip nomiri + + # NOTE: some tests (containing `std_only`) require the `std` feature to run. + print_header 'Running tests with MIRI (no features)...' + RUSTFLAGS='-D warnings' cargo +nightly miri test --no-default-features -- --skip nomiri --skip std_only + + print_header 'Running tests with MIRI (all features)...' + RUSTFLAGS='-D warnings' cargo +nightly miri test --all-features -- --skip nomiri +} + +# Run all checks +all_checks() { + check_fmt + check_docs + build + lint + run_tests_stable + run_tests_beta + run_tests_msrv + run_tests_leak_sanitizer + run_tests_miri + + print_header "All checks passed! 🎉" +} + +# Main function to handle command line arguments +main() { + local command="${1:-"all"}" + + case "$command" in + "all") + all_checks + ;; + "check_fmt") + check_fmt + ;; + "check_docs") + check_docs + ;; + "build") + build + ;; + "lint") + lint + ;; + "run_tests_stable") + run_tests_stable + ;; + "run_tests_beta") + run_tests_beta + ;; + "run_tests_msrv") + run_tests_msrv + ;; + "run_tests_leak_sanitizer") + run_tests_leak_sanitizer + ;; + "run_tests_miri") + run_tests_miri + ;; + *) + echo "Unknown command: $command" + echo "Available commands: all (default), check_fmt, check_docs, build, lint, run_tests_stable, run_tests_beta, run_tests_msrv, run_tests_leak_sanitizer, run_tests_miri" + exit 1 + ;; + esac +} + +main "$@" diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs index 24dbaab..09e15e7 100644 --- a/tests/basic_usage.rs +++ b/tests/basic_usage.rs @@ -38,12 +38,13 @@ fn basic_usage() { pair.with_dependent(|d| d), &["This", "is", "a", "test", "of", "pair.", "hi", "hey"] ); - pair.with_dependent_mut(|dep| dep.sort()); + pair.with_dependent_mut(|dep| dep.sort_unstable()); assert_eq!( pair.with_dependent(|d| d), &["This", "a", "hey", "hi", "is", "of", "pair.", "test"] ); + #[expect(clippy::redundant_closure_for_method_calls, reason = "false positive")] let last_word = pair.with_dependent_mut(|dep| dep.pop()); assert_eq!(last_word, Some("test")); assert_eq!( @@ -78,6 +79,10 @@ fn basic_api_stress_test() { let owner1 = pair.get_owner(); let owner2 = pair.get_owner(); println!("{owner1:?}{owner2:?}"); + #[expect( + clippy::redundant_closure_call, + reason = "I'm just doing weird stuff for funsies and testing" + )] let new_pair = (|x| x)(std::convert::identity(pair)); let owner1 = new_pair.get_owner(); let owner2 = new_pair.get_owner(); diff --git a/tests/concurrent_loom.rs b/tests/concurrent_loom.rs index 162fc23..ed5784d 100644 --- a/tests/concurrent_loom.rs +++ b/tests/concurrent_loom.rs @@ -37,7 +37,7 @@ impl Owner for Buff { // Test sending ownership of a Pair between threads through channels #[test] -fn pair_ownership_transfer_loom() { +fn pair_ownership_transfer_loom_nomiri() { loom::model(|| { let (tx, rx) = mpsc::channel(); @@ -77,7 +77,7 @@ fn pair_ownership_transfer_loom() { // Test sending and sharing a Pair via Arc> across multiple threads #[test] -fn pair_arc_sharing_loom() { +fn pair_arc_sharing_loom_nomiri() { loom::model(|| { let pair = Arc::new(Pair::new(Buff(String::from("arc sharing test")))); @@ -106,7 +106,7 @@ fn pair_arc_sharing_loom() { // Test concurrently accessing an Arc> from multiple threads #[test] -fn pair_mutex_concurrent_access_loom() { +fn pair_mutex_concurrent_access_loom_nomiri() { loom::model(|| { let pair = Arc::new(Mutex::new(Pair::new(Buff(String::from( "mutex concurrent test", @@ -150,7 +150,7 @@ fn pair_mutex_concurrent_access_loom() { // Test concurrently accessing an Arc> from multiple threads #[test] -fn pair_rwlock_concurrent_access_loom() { +fn pair_rwlock_concurrent_access_loom_nomiri() { loom::model(|| { let pair = Arc::new(RwLock::new(Pair::new(Buff(String::from( "rwlock concurrent test", diff --git a/tests/debug.rs b/tests/debug.rs index 5f05ee9..1a7e77a 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -25,6 +25,10 @@ mod fake { } } +#[expect( + clippy::needless_pass_by_value, + reason = "more ergonomic in the a later macro invocation" +)] fn debugs_match Owner = (), Error = Infallible> + Clone + Debug>( owner: O, ) where @@ -117,7 +121,7 @@ fn debug_impls_match_derive_nomiri() { debug_tests! { struct O3(char); &'owner self => Option>: - Some(std::iter::repeat(()).take(self.0 as usize % 20).collect()); + Some(std::iter::repeat_n((), self.0 as usize % 20).collect()); O3('🦀'), O3(' '), O3('!'), O3('a'), O3('A'), O3('*'), O3('-'), O3('~'), O3('\\'), O3('"'), O3('\x00'), O3('\n'), O3('\t'), O3('\''), O3('&'), @@ -130,11 +134,11 @@ fn debug_impls_match_derive_nomiri() { .to_be_bytes() .iter() .copied() - .reduce(|acc, e| u8::wrapping_add(acc, e)) + .reduce(u8::wrapping_add) .unwrap(); - O4(0.0), O4(-0.0), O4(1.0), O4(3.14), O4(f64::NAN), O4(f64::INFINITY), - O4(f64::NEG_INFINITY), O4(f64::EPSILON), + O4(0.0), O4(-0.0), O4(1.0), O4(std::f64::consts::PI), O4(f64::NAN), + O4(f64::INFINITY), O4(f64::NEG_INFINITY), O4(f64::EPSILON), } debug_tests! { diff --git a/tests/drop.rs b/tests/drop.rs index 0e33810..d7f2f47 100644 --- a/tests/drop.rs +++ b/tests/drop.rs @@ -11,7 +11,7 @@ struct OnDrop { } impl Drop for OnDrop { fn drop(&mut self) { - (self.f)(&mut self.value.borrow_mut()) + (self.f)(&mut self.value.borrow_mut()); } } @@ -21,7 +21,7 @@ struct OnDropDep { } impl Drop for OnDropDep { fn drop(&mut self) { - (self.f)(&mut self.value.borrow_mut()) + (self.f)(&mut self.value.borrow_mut()); } } impl HasDependent<'_> for OnDrop { diff --git a/tests/multiborrow.rs b/tests/multiborrow.rs index 111a100..dd035af 100644 --- a/tests/multiborrow.rs +++ b/tests/multiborrow.rs @@ -12,9 +12,9 @@ struct MultiPartOwner { } struct MultiPartDependent<'a> { - string_ref: &'a str, - int_ref: &'a i32, - bool_ref: &'a bool, + string: &'a str, + int: &'a i32, + boolean: &'a bool, } impl<'owner> HasDependent<'owner> for MultiPartOwner { @@ -30,15 +30,17 @@ impl Owner for MultiPartOwner { (): Self::Context<'_>, ) -> Result<>::Dependent, Self::Error> { Ok(MultiPartDependent { - string_ref: &self.field1, - int_ref: &self.field2[0], - bool_ref: &self.field3, + string: &self.field1, + int: &self.field2[0], + boolean: &self.field3, }) } } #[test] fn test_multiple_borrows() { + #![expect(clippy::bool_assert_comparison, reason = "for clarity and consistency")] + let owner = MultiPartOwner { field1: "Hello, world!".to_string(), field2: vec![3, 1, 4, 1, 5], @@ -49,9 +51,9 @@ fn test_multiple_borrows() { let pair = Pair::new(owner); pair.with_dependent(|dep| { - assert_eq!(dep.string_ref, "Hello, world!"); - assert_eq!(*dep.int_ref, 3); - assert_eq!(*dep.bool_ref, true); + assert_eq!(dep.string, "Hello, world!"); + assert_eq!(*dep.int, 3); + assert_eq!(*dep.boolean, true); }); let owner = pair.into_owner(); From 80ca26c17868dbad48c2d88aa103c3a4b0779ee3 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 8 Mar 2025 03:00:07 -0500 Subject: [PATCH 071/110] updated CI with names and cut cache --- .github/workflows/ci.yaml | 45 ++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 46eef16..97d4c47 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,97 +7,88 @@ env: CARGO_TERM_COLOR: always # Pretty colors jobs: check_fmt: - name: TODO + name: cargo fmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: cargo fmt run: ./ci.sh check_fmt check_docs: - name: TODO + name: Cargo docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: Cargo docs run: ./ci.sh check_docs build: - name: TODO + name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: Build run: ./ci.sh build lint: - name: TODO + name: Lint (clippy) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: Lint (clippy) run: ./ci.sh lint run_tests_stable: - name: TODO + name: Tests (stable) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: Tests (stable) run: ./ci.sh run_tests_stable run_tests_beta: - name: TODO + name: Tests (beta) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@beta - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: Tests (beta) run: ./ci.sh run_tests_beta run_tests_msrv: - name: TODO + name: Tests (MSRV) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@1.85.0 - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: Tests (MSRV) run: ./ci.sh run_tests_msrv run_tests_leak_sanitizer: - name: TODO + name: Tests (leak sanitizer) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: Tests (leak sanitizer) run: ./ci.sh run_tests_leak_sanitizer run_tests_miri: - name: TODO + name: Tests (MIRI) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: miri - - uses: Swatinem/rust-cache@v2 - - name: TODO # TODO + - name: Tests (MIRI) run: ./ci.sh run_tests_miri From ae21688482ae5115cba82896a9aeeaa335457216 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 8 Mar 2025 03:34:55 -0500 Subject: [PATCH 072/110] split MIRI tests into separate jobs --- .github/workflows/ci.yaml | 30 ++++++++++++++++++++---- ci.sh | 48 +++++++++++++++++++++++++++++++++------ 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 97d4c47..73e9dc9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -82,13 +82,35 @@ jobs: - name: Tests (leak sanitizer) run: ./ci.sh run_tests_leak_sanitizer - run_tests_miri: - name: Tests (MIRI) + run_tests_miri_default_features: + name: Tests (MIRI, default features) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: miri - - name: Tests (MIRI) - run: ./ci.sh run_tests_miri + - name: Tests (MIRI, default features) + run: ./ci.sh run_tests_miri_default_features + + run_tests_miri_no_features: + name: Tests (MIRI, no features) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: miri + - name: Tests (MIRI, no features) + run: ./ci.sh run_tests_miri_no_features + + run_tests_miri_all_features: + name: Tests (MIRI, all features) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: miri + - name: Tests (MIRI, all features) + run: ./ci.sh run_tests_miri_all_features diff --git a/ci.sh b/ci.sh index b547d81..5fd4201 100755 --- a/ci.sh +++ b/ci.sh @@ -94,9 +94,9 @@ run_tests_leak_sanitizer() { RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --all-features -- --skip loom --skip try_builds } -run_tests_miri() { - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are - # skipped here. +# # NOTE: MIRI runs pretty slowly, so splitting up the MIRI tests in CI actually +# # gives a pretty meaningful speedup. +run_tests_miri_default_features() { # TODO: figure out any MIRI flags we want (retag fields thing?) # TODO: MIRI currently has a bug where it just hangs on doctests in edition @@ -105,13 +105,39 @@ run_tests_miri() { trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ + # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are + # skipped here. print_header 'Running tests with MIRI (default features)...' RUSTFLAGS='-D warnings' cargo +nightly miri test -- --skip nomiri +} + +run_tests_miri_no_features() { + # TODO: figure out any MIRI flags we want (retag fields thing?) + # TODO: MIRI currently has a bug where it just hangs on doctests in edition + # 2024 - until this is fixed, we do some jank to revert edition temporarily + cp Cargo.toml Cargo.toml.backup && \ + trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ + sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ + + # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are + # skipped here. # NOTE: some tests (containing `std_only`) require the `std` feature to run. print_header 'Running tests with MIRI (no features)...' RUSTFLAGS='-D warnings' cargo +nightly miri test --no-default-features -- --skip nomiri --skip std_only +} + +run_tests_miri_all_features() { + # TODO: figure out any MIRI flags we want (retag fields thing?) + + # TODO: MIRI currently has a bug where it just hangs on doctests in edition + # 2024 - until this is fixed, we do some jank to revert edition temporarily + cp Cargo.toml Cargo.toml.backup && \ + trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ + sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ + # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are + # skipped here. print_header 'Running tests with MIRI (all features)...' RUSTFLAGS='-D warnings' cargo +nightly miri test --all-features -- --skip nomiri } @@ -126,7 +152,9 @@ all_checks() { run_tests_beta run_tests_msrv run_tests_leak_sanitizer - run_tests_miri + run_tests_miri_default_features + run_tests_miri_no_features + run_tests_miri_all_features print_header "All checks passed! 🎉" } @@ -163,12 +191,18 @@ main() { "run_tests_leak_sanitizer") run_tests_leak_sanitizer ;; - "run_tests_miri") - run_tests_miri + "run_tests_miri_default_features") + run_tests_miri_default_features + ;; + "run_tests_miri_no_features") + run_tests_miri_no_features + ;; + "run_tests_miri_all_features") + run_tests_miri_all_features ;; *) echo "Unknown command: $command" - echo "Available commands: all (default), check_fmt, check_docs, build, lint, run_tests_stable, run_tests_beta, run_tests_msrv, run_tests_leak_sanitizer, run_tests_miri" + echo "Available commands: all (default), check_fmt, check_docs, build, lint, run_tests_stable, run_tests_beta, run_tests_msrv, run_tests_leak_sanitizer, run_tests_miri_default_features, run_tests_miri_no_features, run_tests_miri_all_features" exit 1 ;; esac From 73d2aaf5a87644890ab1076b33b9e6f2b39eb780 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 8 Mar 2025 03:37:31 -0500 Subject: [PATCH 073/110] moved lint before build --- .github/workflows/ci.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 73e9dc9..14b98b8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,15 +26,6 @@ jobs: - name: Cargo docs run: ./ci.sh check_docs - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - name: Build - run: ./ci.sh build - lint: name: Lint (clippy) runs-on: ubuntu-latest @@ -46,6 +37,15 @@ jobs: - name: Lint (clippy) run: ./ci.sh lint + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Build + run: ./ci.sh build + run_tests_stable: name: Tests (stable) runs-on: ubuntu-latest From 00455c96088e8ac419034bcd6c987106f6821624 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 8 Mar 2025 03:40:13 -0500 Subject: [PATCH 074/110] updated to-do list --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index eeeee2a..5c96f5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ extern crate std; // TODO: move this to-do list to a to-do.md or smth // - document feature flags in README -// - clean up ci.sh +// - deal with trybuild tests failing on 1.86.0+ mod owner; mod pair; From aac1903d88e200cdb81c04c79afc3ab3da556a64 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 8 Mar 2025 03:41:29 -0500 Subject: [PATCH 075/110] OCD fix --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 14b98b8..c74891b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,14 +7,14 @@ env: CARGO_TERM_COLOR: always # Pretty colors jobs: check_fmt: - name: cargo fmt + name: Cargo fmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - - name: cargo fmt + - name: Cargo fmt run: ./ci.sh check_fmt check_docs: From 86d8af054a64c8b8deb491e0044d5b1e575b21d0 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 11:28:34 -0700 Subject: [PATCH 076/110] improved ci.sh --- ci.sh | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/ci.sh b/ci.sh index 5fd4201..0463f02 100755 --- a/ci.sh +++ b/ci.sh @@ -94,17 +94,11 @@ run_tests_leak_sanitizer() { RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --all-features -- --skip loom --skip try_builds } -# # NOTE: MIRI runs pretty slowly, so splitting up the MIRI tests in CI actually -# # gives a pretty meaningful speedup. +# NOTE: MIRI runs pretty slowly, so splitting up the MIRI tests in CI actually +# gives a pretty meaningful speedup. run_tests_miri_default_features() { # TODO: figure out any MIRI flags we want (retag fields thing?) - # TODO: MIRI currently has a bug where it just hangs on doctests in edition - # 2024 - until this is fixed, we do some jank to revert edition temporarily - cp Cargo.toml Cargo.toml.backup && \ - trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ - sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are # skipped here. print_header 'Running tests with MIRI (default features)...' @@ -114,12 +108,6 @@ run_tests_miri_default_features() { run_tests_miri_no_features() { # TODO: figure out any MIRI flags we want (retag fields thing?) - # TODO: MIRI currently has a bug where it just hangs on doctests in edition - # 2024 - until this is fixed, we do some jank to revert edition temporarily - cp Cargo.toml Cargo.toml.backup && \ - trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ - sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are # skipped here. # NOTE: some tests (containing `std_only`) require the `std` feature to run. @@ -130,12 +118,6 @@ run_tests_miri_no_features() { run_tests_miri_all_features() { # TODO: figure out any MIRI flags we want (retag fields thing?) - # TODO: MIRI currently has a bug where it just hangs on doctests in edition - # 2024 - until this is fixed, we do some jank to revert edition temporarily - cp Cargo.toml Cargo.toml.backup && \ - trap 'mv Cargo.toml.backup Cargo.toml' EXIT && \ - sed -i 's/edition = "2024"/edition = "2021"/' Cargo.toml && \ - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are # skipped here. print_header 'Running tests with MIRI (all features)...' From 96c78cc2d0d15ade658a492bca383c36b06dd31b Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 11:35:39 -0700 Subject: [PATCH 077/110] moved build after lint (minor) --- ci.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ci.sh b/ci.sh index 0463f02..b2dda04 100755 --- a/ci.sh +++ b/ci.sh @@ -16,17 +16,6 @@ check_docs() { RUSTDOCFLAGS='-D warnings' cargo +stable doc --document-private-items --no-deps } -build() { - print_header 'Running cargo build (default features)...' - RUSTFLAGS='-D warnings' cargo +stable build --all-targets - - print_header 'Running cargo build (no features)...' - RUSTFLAGS='-D warnings' cargo +stable build --all-targets --no-default-features - - print_header 'Running cargo build (all features)...' - RUSTFLAGS='-D warnings' cargo +stable build --all-targets --all-features -} - lint() { print_header 'Linting with cargo clippy (default features)...' cargo +stable clippy --no-deps --all-targets -- -D warnings @@ -38,6 +27,17 @@ lint() { cargo +stable clippy --no-deps --all-targets --all-features -- -D warnings } +build() { + print_header 'Running cargo build (default features)...' + RUSTFLAGS='-D warnings' cargo +stable build --all-targets + + print_header 'Running cargo build (no features)...' + RUSTFLAGS='-D warnings' cargo +stable build --all-targets --no-default-features + + print_header 'Running cargo build (all features)...' + RUSTFLAGS='-D warnings' cargo +stable build --all-targets --all-features +} + run_tests_stable() { print_header 'Running tests (stable compiler, default features)...' RUSTFLAGS='-D warnings' cargo +stable test From e5981da16905568d7fe165a9f5de6c6f44031692 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 11:38:15 -0700 Subject: [PATCH 078/110] moved to-do list from lib.rs to TO-DO.md --- TO-DO.md | 6 ++++++ src/lib.rs | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 TO-DO.md diff --git a/TO-DO.md b/TO-DO.md new file mode 100644 index 0000000..7c681ec --- /dev/null +++ b/TO-DO.md @@ -0,0 +1,6 @@ +# To-do + +- Delete this to-do list +- Document feature flags in README +- Deal with trybuild tests failing on 1.86.0+ (switch off trybuild) +- Figure out any MIRI flags we want in CI tests (retag fields thing?) diff --git a/src/lib.rs b/src/lib.rs index 5c96f5f..43dc03a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,6 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -// TODO: move this to-do list to a to-do.md or smth -// - document feature flags in README -// - deal with trybuild tests failing on 1.86.0+ - mod owner; mod pair; mod panicking; From 99bba34bf4099db506f1c27c1edc08f3aee5e95d Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 12:01:32 -0700 Subject: [PATCH 079/110] enabled strict provenance checks in MIRI, cleaned up ci.sh --- ci.sh | 61 ++++++++++++++++------------------------------------------- 1 file changed, 16 insertions(+), 45 deletions(-) diff --git a/ci.sh b/ci.sh index b2dda04..81fa9c9 100755 --- a/ci.sh +++ b/ci.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + set -euo pipefail # Prints text formatted as a header (colors and arrows and stuff, very cool) @@ -97,31 +98,25 @@ run_tests_leak_sanitizer() { # NOTE: MIRI runs pretty slowly, so splitting up the MIRI tests in CI actually # gives a pretty meaningful speedup. run_tests_miri_default_features() { - # TODO: figure out any MIRI flags we want (retag fields thing?) - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are # skipped here. print_header 'Running tests with MIRI (default features)...' - RUSTFLAGS='-D warnings' cargo +nightly miri test -- --skip nomiri + RUSTFLAGS='-D warnings' MIRIFLAGS='-Zmiri-strict-provenance' cargo +nightly miri test -- --skip nomiri } run_tests_miri_no_features() { - # TODO: figure out any MIRI flags we want (retag fields thing?) - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are # skipped here. # NOTE: some tests (containing `std_only`) require the `std` feature to run. print_header 'Running tests with MIRI (no features)...' - RUSTFLAGS='-D warnings' cargo +nightly miri test --no-default-features -- --skip nomiri --skip std_only + RUSTFLAGS='-D warnings' MIRIFLAGS='-Zmiri-strict-provenance' cargo +nightly miri test --no-default-features -- --skip nomiri --skip std_only } run_tests_miri_all_features() { - # TODO: figure out any MIRI flags we want (retag fields thing?) - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are # skipped here. print_header 'Running tests with MIRI (all features)...' - RUSTFLAGS='-D warnings' cargo +nightly miri test --all-features -- --skip nomiri + RUSTFLAGS='-D warnings' MIRIFLAGS='-Zmiri-strict-provenance' cargo +nightly miri test --all-features -- --skip nomiri } # Run all checks @@ -146,42 +141,18 @@ main() { local command="${1:-"all"}" case "$command" in - "all") - all_checks - ;; - "check_fmt") - check_fmt - ;; - "check_docs") - check_docs - ;; - "build") - build - ;; - "lint") - lint - ;; - "run_tests_stable") - run_tests_stable - ;; - "run_tests_beta") - run_tests_beta - ;; - "run_tests_msrv") - run_tests_msrv - ;; - "run_tests_leak_sanitizer") - run_tests_leak_sanitizer - ;; - "run_tests_miri_default_features") - run_tests_miri_default_features - ;; - "run_tests_miri_no_features") - run_tests_miri_no_features - ;; - "run_tests_miri_all_features") - run_tests_miri_all_features - ;; + "all") all_checks ;; + "check_fmt") check_fmt ;; + "check_docs") check_docs ;; + "build") build ;; + "lint") lint ;; + "run_tests_stable") run_tests_stable ;; + "run_tests_beta") run_tests_beta ;; + "run_tests_msrv") run_tests_msrv ;; + "run_tests_leak_sanitizer") run_tests_leak_sanitizer ;; + "run_tests_miri_default_features") run_tests_miri_default_features ;; + "run_tests_miri_no_features") run_tests_miri_no_features ;; + "run_tests_miri_all_features") run_tests_miri_all_features ;; *) echo "Unknown command: $command" echo "Available commands: all (default), check_fmt, check_docs, build, lint, run_tests_stable, run_tests_beta, run_tests_msrv, run_tests_leak_sanitizer, run_tests_miri_default_features, run_tests_miri_no_features, run_tests_miri_all_features" From 320d7e795a6132fb505f9ebce58e24c9145f8847 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 12:12:30 -0700 Subject: [PATCH 080/110] documented feature flags in README.md, updated TO-DO.md --- README.md | 6 ++++++ TO-DO.md | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b24ce9..4b0953a 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,12 @@ code could do. When the owner needs to be dropped or recovered, the dependent will first be recovered and dropped, ending the borrow of the owner. At that point, the owner can safely be recovered and the `Pair` deconstructed. +# Feature Flags + +The following feature flags are available: +- `std` (enabled by default) - Enables usage of the standard library within + pair. Without the standard library, pair cannot handle panics as gracefully. + # Related Projects | Crate | Macro free | No `alloc` | Maintained | Soundness | diff --git a/TO-DO.md b/TO-DO.md index 7c681ec..7c2cf73 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -1,6 +1,4 @@ # To-do - Delete this to-do list -- Document feature flags in README - Deal with trybuild tests failing on 1.86.0+ (switch off trybuild) -- Figure out any MIRI flags we want in CI tests (retag fields thing?) From 552304fff1c924370005083ba5435b4f722e4de3 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 16:44:04 -0700 Subject: [PATCH 081/110] cut trybuild, hand-rolled test framework --- Cargo.lock | 173 ------------------ Cargo.toml | 1 - TO-DO.md | 1 - ci.sh | 15 +- tests/compile_fails.rs | 112 ++++++++++++ .../extract_with_dep_mut.expected | 5 + .../extract_with_dep_mut.rs | 2 + .../invariant_dep_extraction.expected | 5 + .../invariant_dep_extraction.rs | 2 + .../keep_dep_after_into_owner.expected | 6 + .../keep_dep_after_into_owner.rs | 2 + .../keep_dep_after_pair_drop.expected | 6 + .../keep_dep_after_pair_drop.rs | 2 + tests/compile_fails/not_send_dep.expected | 3 + .../not_send_dep.rs | 2 + tests/compile_fails/not_send_owner.expected | 4 + .../not_send_owner.rs | 2 + tests/compile_fails/not_sync_dep.expected | 3 + .../not_sync_dep.rs | 2 + tests/compile_fails/not_sync_owner.expected | 3 + .../not_sync_owner.rs | 2 + .../compile_fails/pair_not_covariant.expected | 7 + .../pair_not_covariant.rs | 2 + .../send_sync_miserable_failure.expected | 5 + .../send_sync_miserable_failure.rs | 2 + .../contravariance_violation.rs | 37 ---- .../contravariance_violation.stderr | 13 -- .../extract_with_dep_mut.stderr | 11 -- .../invariant_dep_extraction.stderr | 12 -- .../keep_dep_after_into_owner.stderr | 13 -- .../keep_dep_after_pair_drop.stderr | 13 -- tests/trybuild_fails/not_send_dep.stderr | 18 -- tests/trybuild_fails/not_send_owner.stderr | 18 -- tests/trybuild_fails/not_sync_dep.stderr | 18 -- tests/trybuild_fails/not_sync_owner.stderr | 18 -- .../trybuild_fails/pair_not_covariant.stderr | 15 -- .../send_sync_miserable_failure.stderr | 37 ---- tests/trybuild_test.rs | 7 - 38 files changed, 185 insertions(+), 414 deletions(-) create mode 100644 tests/compile_fails.rs create mode 100644 tests/compile_fails/extract_with_dep_mut.expected rename tests/{trybuild_fails => compile_fails}/extract_with_dep_mut.rs (96%) create mode 100644 tests/compile_fails/invariant_dep_extraction.expected rename tests/{trybuild_fails => compile_fails}/invariant_dep_extraction.rs (96%) create mode 100644 tests/compile_fails/keep_dep_after_into_owner.expected rename tests/{trybuild_fails => compile_fails}/keep_dep_after_into_owner.rs (97%) create mode 100644 tests/compile_fails/keep_dep_after_pair_drop.expected rename tests/{trybuild_fails => compile_fails}/keep_dep_after_pair_drop.rs (97%) create mode 100644 tests/compile_fails/not_send_dep.expected rename tests/{trybuild_fails => compile_fails}/not_send_dep.rs (97%) create mode 100644 tests/compile_fails/not_send_owner.expected rename tests/{trybuild_fails => compile_fails}/not_send_owner.rs (97%) create mode 100644 tests/compile_fails/not_sync_dep.expected rename tests/{trybuild_fails => compile_fails}/not_sync_dep.rs (97%) create mode 100644 tests/compile_fails/not_sync_owner.expected rename tests/{trybuild_fails => compile_fails}/not_sync_owner.rs (97%) create mode 100644 tests/compile_fails/pair_not_covariant.expected rename tests/{trybuild_fails => compile_fails}/pair_not_covariant.rs (98%) create mode 100644 tests/compile_fails/send_sync_miserable_failure.expected rename tests/{trybuild_fails => compile_fails}/send_sync_miserable_failure.rs (96%) delete mode 100644 tests/trybuild_fails/contravariance_violation.rs delete mode 100644 tests/trybuild_fails/contravariance_violation.stderr delete mode 100644 tests/trybuild_fails/extract_with_dep_mut.stderr delete mode 100644 tests/trybuild_fails/invariant_dep_extraction.stderr delete mode 100644 tests/trybuild_fails/keep_dep_after_into_owner.stderr delete mode 100644 tests/trybuild_fails/keep_dep_after_pair_drop.stderr delete mode 100644 tests/trybuild_fails/not_send_dep.stderr delete mode 100644 tests/trybuild_fails/not_send_owner.stderr delete mode 100644 tests/trybuild_fails/not_sync_dep.stderr delete mode 100644 tests/trybuild_fails/not_sync_owner.stderr delete mode 100644 tests/trybuild_fails/pair_not_covariant.stderr delete mode 100644 tests/trybuild_fails/send_sync_miserable_failure.stderr delete mode 100644 tests/trybuild_test.rs diff --git a/Cargo.lock b/Cargo.lock index ee2da40..92ad151 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - [[package]] name = "generator" version = "0.8.4" @@ -36,34 +30,6 @@ dependencies = [ "windows", ] -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - [[package]] name = "lazy_static" version = "1.5.0" @@ -137,7 +103,6 @@ name = "pair" version = "0.1.0" dependencies = [ "loom", - "trybuild", ] [[package]] @@ -214,59 +179,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" -[[package]] -name = "ryu" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" - [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" -[[package]] -name = "serde" -version = "1.0.218" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.218" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.139" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -293,21 +211,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "target-triple" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thread_local" version = "1.1.8" @@ -318,40 +221,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "tracing" version = "0.1.41" @@ -401,21 +270,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "trybuild" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b812699e0c4f813b872b373a4471717d9eb550da14b311058a4d9cf4173cbca6" -dependencies = [ - "glob", - "serde", - "serde_derive", - "serde_json", - "target-triple", - "termcolor", - "toml", -] - [[package]] name = "unicode-ident" version = "1.0.17" @@ -444,15 +298,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -523,15 +368,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -595,12 +431,3 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" -dependencies = [ - "memchr", -] diff --git a/Cargo.toml b/Cargo.toml index 80c1f46..e4ee7c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,6 @@ publish = false # ON_RELEASE: Remove publish = false [dev-dependencies] loom = "0.7.2" -trybuild = "1.0.103" # # # # # # # # # # # # # # # # # # # # # # diff --git a/TO-DO.md b/TO-DO.md index 7c2cf73..c273b17 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -1,4 +1,3 @@ # To-do - Delete this to-do list -- Deal with trybuild tests failing on 1.86.0+ (switch off trybuild) diff --git a/ci.sh b/ci.sh index 81fa9c9..b83bcc9 100755 --- a/ci.sh +++ b/ci.sh @@ -52,17 +52,15 @@ run_tests_stable() { } run_tests_beta() { - # TODO: trybuild tests fail on nightly (and beta) - error messages changed - print_header 'Running tests (beta compiler, default features)...' - RUSTFLAGS='-D warnings' cargo +beta test -- --skip try_builds + RUSTFLAGS='-D warnings' cargo +beta test # NOTE: some tests (containing `std_only`) require the `std` feature to run. print_header 'Running tests (beta compiler, no features)...' - RUSTFLAGS='-D warnings' cargo +beta test --no-default-features -- --skip std_only --skip try_builds + RUSTFLAGS='-D warnings' cargo +beta test --no-default-features -- --skip std_only print_header 'Running tests (beta compiler, all features)...' - RUSTFLAGS='-D warnings' cargo +beta test --all-features -- --skip try_builds + RUSTFLAGS='-D warnings' cargo +beta test --all-features } run_tests_msrv() { @@ -82,17 +80,16 @@ run_tests_msrv() { run_tests_leak_sanitizer() { # NOTE: loom seems to make the leak sanitizer unhappy. I don't think that # combination of tests is important, so we just skip loom tests here. - # TODO: trybuild tests fail on nightly (and beta) - error messages changed print_header 'Running tests with leak sanitizer (default features)...' - RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test -- --skip loom --skip try_builds + RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test -- --skip loom # NOTE: some tests (containing `std_only`) require the `std` feature to run. print_header 'Running tests with leak sanitizer (no features)...' - RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --no-default-features -- --skip std_only --skip loom --skip try_builds + RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --no-default-features -- --skip std_only --skip loom print_header 'Running tests with leak sanitizer (all features)...' - RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --all-features -- --skip loom --skip try_builds + RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --all-features -- --skip loom } # NOTE: MIRI runs pretty slowly, so splitting up the MIRI tests in CI actually diff --git a/tests/compile_fails.rs b/tests/compile_fails.rs new file mode 100644 index 0000000..1250069 --- /dev/null +++ b/tests/compile_fails.rs @@ -0,0 +1,112 @@ +#![allow(missing_docs, reason = "integration test")] + +use std::{ + ffi::OsString, + path::{Path, PathBuf}, + process::Command, +}; + +/// Ensures `pair`s build artifacts are available in the target directory, and +/// returns a path to that target directory (relative to the current working +/// directory). +fn ensure_pair_available() -> Option { + Command::new("cargo") + .arg("build") + .status() + .ok()? + .success() + .then_some(())?; + + Some( + std::env::var("CARGO_TARGET_DIR") + .map_or_else(|_| PathBuf::from("target"), PathBuf::from) + .join("debug"), + ) +} + +/// Returns the stderr from rustc attempting to compile the given file. +/// +/// Makes quite a few assumptions about the environment, namely that +/// `ensure_pair_available` has been called. +/// +/// # Panics +/// In quite a few situations, read the code lol +fn get_compiler_err(target_dir: &Path, test_file_path: &Path) -> String { + let mut pair_path_arg = OsString::from("pair="); + pair_path_arg.push(target_dir.join("libpair.rlib")); + let mut dependency_arg = OsString::from("dependency="); + dependency_arg.push(target_dir.join("deps")); + + let output = Command::new("rustc") + .arg(test_file_path) + .arg("--extern") + .arg(pair_path_arg) + .arg("-L") + .arg(dependency_arg) + .output() + .expect("failed to get output of rustc command"); + + assert!( + !output.status.success(), + "test compiled, but was expected not to: {test_file_path:?}" + ); + + String::from_utf8(output.stderr).expect("rustc output was not UTF-8") +} + +#[test] +fn compile_fail_tests_nomiri() { + // I would have preferred to use trybuild or compiletest_rs, but both seem + // to require an exact .stderr match, which is not desirable for me. I don't + // care about the *exact* error message, which may change in small ways + // between different versions of the compiler. In fact, I was using trybuild + // until I discovered that my tests fail on beta (which was 1.86.0) due to a + // tiny change to what part of the source code gets highlighted. All I + // really care about is that the code doesn't compile, and is generally for + // the reason I expect. I wasn't able to find a better way to do this than a + // custom little test framework. If you have a better idea, I'd welcome an + // issue with the suggestion :) + + // Get a list of all test files in tests/compile_fails/ + // Some majorly sauced up functional magic + let test_file_paths: Vec<_> = std::fs::read_dir("tests/compile_fails") + .and_then(|dir_iter| { + dir_iter + .filter_map(|entry| { + entry + .and_then(|entry| { + Ok((entry.file_type()?.is_file() + && entry + .path() + .extension() + .is_some_and(|extension| extension == "rs")) + .then_some(entry.path())) + }) + .transpose() + }) + .collect() + }) + .expect("failed to read `tests/compile_fails` directory"); + + // Ensure `pair`'s build artifacts are available, and get the target dir + let target_dir = ensure_pair_available().expect("failed to compile `pair`"); + + // For each file, ensure it fails to compile with the expected error message + for test_file_path in test_file_paths { + let expected_path = test_file_path.with_extension("expected"); + let expected_substrings: Vec<_> = std::fs::read_to_string(&expected_path) + .unwrap_or_else(|_| panic!("failed to read file: {expected_path:?}")) + .lines() + .map(str::to_owned) + .collect(); + + let compiler_output = get_compiler_err(&target_dir, &test_file_path); + + for expected_substring in expected_substrings { + assert!( + compiler_output.contains(&expected_substring), + "compiler error did not contain expected substring: {expected_substring}" + ); + } + } +} diff --git a/tests/compile_fails/extract_with_dep_mut.expected b/tests/compile_fails/extract_with_dep_mut.expected new file mode 100644 index 0000000..599e01b --- /dev/null +++ b/tests/compile_fails/extract_with_dep_mut.expected @@ -0,0 +1,5 @@ +`pair` does not live long enough +tests/compile_fails/extract_with_dep_mut.rs +returning this value requires that `pair` is borrowed for `'static` +borrowed value does not live long enough +`pair` dropped here while still borrowed diff --git a/tests/trybuild_fails/extract_with_dep_mut.rs b/tests/compile_fails/extract_with_dep_mut.rs similarity index 96% rename from tests/trybuild_fails/extract_with_dep_mut.rs rename to tests/compile_fails/extract_with_dep_mut.rs index c0b80ea..1c2143d 100644 --- a/tests/trybuild_fails/extract_with_dep_mut.rs +++ b/tests/compile_fails/extract_with_dep_mut.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/invariant_dep_extraction.expected b/tests/compile_fails/invariant_dep_extraction.expected new file mode 100644 index 0000000..a447738 --- /dev/null +++ b/tests/compile_fails/invariant_dep_extraction.expected @@ -0,0 +1,5 @@ +`pair` does not live long enough +tests/compile_fails/invariant_dep_extraction.rs +returning this value requires that `pair` is borrowed for `'static` +borrowed value does not live long enough +`pair` dropped here while still borrowed diff --git a/tests/trybuild_fails/invariant_dep_extraction.rs b/tests/compile_fails/invariant_dep_extraction.rs similarity index 96% rename from tests/trybuild_fails/invariant_dep_extraction.rs rename to tests/compile_fails/invariant_dep_extraction.rs index a15c1f5..8ba976f 100644 --- a/tests/trybuild_fails/invariant_dep_extraction.rs +++ b/tests/compile_fails/invariant_dep_extraction.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::{cell::Cell, convert::Infallible, marker::PhantomData}; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/keep_dep_after_into_owner.expected b/tests/compile_fails/keep_dep_after_into_owner.expected new file mode 100644 index 0000000..f26e072 --- /dev/null +++ b/tests/compile_fails/keep_dep_after_into_owner.expected @@ -0,0 +1,6 @@ +cannot move out of `pair` because it is borrowed +tests/compile_fails/keep_dep_after_into_owner.rs +binding `pair` declared here +borrow of `pair` occurs here +move out of `pair` occurs here +borrow later used here diff --git a/tests/trybuild_fails/keep_dep_after_into_owner.rs b/tests/compile_fails/keep_dep_after_into_owner.rs similarity index 97% rename from tests/trybuild_fails/keep_dep_after_into_owner.rs rename to tests/compile_fails/keep_dep_after_into_owner.rs index a5d7781..a328f82 100644 --- a/tests/trybuild_fails/keep_dep_after_into_owner.rs +++ b/tests/compile_fails/keep_dep_after_into_owner.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/keep_dep_after_pair_drop.expected b/tests/compile_fails/keep_dep_after_pair_drop.expected new file mode 100644 index 0000000..0823c0b --- /dev/null +++ b/tests/compile_fails/keep_dep_after_pair_drop.expected @@ -0,0 +1,6 @@ +cannot move out of `pair` because it is borrowed +tests/compile_fails/keep_dep_after_pair_drop.rs +binding `pair` declared here +borrow of `pair` occurs here +move out of `pair` occurs here +borrow later used here diff --git a/tests/trybuild_fails/keep_dep_after_pair_drop.rs b/tests/compile_fails/keep_dep_after_pair_drop.rs similarity index 97% rename from tests/trybuild_fails/keep_dep_after_pair_drop.rs rename to tests/compile_fails/keep_dep_after_pair_drop.rs index 994684c..e008751 100644 --- a/tests/trybuild_fails/keep_dep_after_pair_drop.rs +++ b/tests/compile_fails/keep_dep_after_pair_drop.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/not_send_dep.expected b/tests/compile_fails/not_send_dep.expected new file mode 100644 index 0000000..f7219f5 --- /dev/null +++ b/tests/compile_fails/not_send_dep.expected @@ -0,0 +1,3 @@ +cannot be sent between threads safely +tests/compile_fails/not_send_dep.rs +required for `Pair` to implement `Send` diff --git a/tests/trybuild_fails/not_send_dep.rs b/tests/compile_fails/not_send_dep.rs similarity index 97% rename from tests/trybuild_fails/not_send_dep.rs rename to tests/compile_fails/not_send_dep.rs index 72de8d1..616c1e0 100644 --- a/tests/trybuild_fails/not_send_dep.rs +++ b/tests/compile_fails/not_send_dep.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::{convert::Infallible, sync::MutexGuard}; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/not_send_owner.expected b/tests/compile_fails/not_send_owner.expected new file mode 100644 index 0000000..f37905f --- /dev/null +++ b/tests/compile_fails/not_send_owner.expected @@ -0,0 +1,4 @@ +cannot be sent between threads safely +tests/compile_fails/not_send_owner.rs +note: required because it appears within the type `NotSend` +required for `Pair` to implement `Send` diff --git a/tests/trybuild_fails/not_send_owner.rs b/tests/compile_fails/not_send_owner.rs similarity index 97% rename from tests/trybuild_fails/not_send_owner.rs rename to tests/compile_fails/not_send_owner.rs index 003946a..27705fc 100644 --- a/tests/trybuild_fails/not_send_owner.rs +++ b/tests/compile_fails/not_send_owner.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::{convert::Infallible, sync::MutexGuard}; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/not_sync_dep.expected b/tests/compile_fails/not_sync_dep.expected new file mode 100644 index 0000000..7de0b37 --- /dev/null +++ b/tests/compile_fails/not_sync_dep.expected @@ -0,0 +1,3 @@ +cannot be shared between threads safely +tests/compile_fails/not_sync_dep.rs +required for `Pair` to implement `Sync` diff --git a/tests/trybuild_fails/not_sync_dep.rs b/tests/compile_fails/not_sync_dep.rs similarity index 97% rename from tests/trybuild_fails/not_sync_dep.rs rename to tests/compile_fails/not_sync_dep.rs index b394bb5..44c9d78 100644 --- a/tests/trybuild_fails/not_sync_dep.rs +++ b/tests/compile_fails/not_sync_dep.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::{cell::UnsafeCell, convert::Infallible}; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/not_sync_owner.expected b/tests/compile_fails/not_sync_owner.expected new file mode 100644 index 0000000..3bedcc8 --- /dev/null +++ b/tests/compile_fails/not_sync_owner.expected @@ -0,0 +1,3 @@ +cannot be shared between threads safely +tests/compile_fails/not_sync_owner.rs +required for `Pair` to implement `Sync` diff --git a/tests/trybuild_fails/not_sync_owner.rs b/tests/compile_fails/not_sync_owner.rs similarity index 97% rename from tests/trybuild_fails/not_sync_owner.rs rename to tests/compile_fails/not_sync_owner.rs index 840a7be..10969c9 100644 --- a/tests/trybuild_fails/not_sync_owner.rs +++ b/tests/compile_fails/not_sync_owner.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::{cell::UnsafeCell, convert::Infallible}; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/pair_not_covariant.expected b/tests/compile_fails/pair_not_covariant.expected new file mode 100644 index 0000000..65547b0 --- /dev/null +++ b/tests/compile_fails/pair_not_covariant.expected @@ -0,0 +1,7 @@ +lifetime may not live long enough +tests/compile_fails/pair_not_covariant.rs +lifetime `'longer` defined here +lifetime `'shorter` defined here +type annotation requires that `'shorter` must outlive `'longer` +requirement occurs because of the type `Pair>`, which makes the generic argument `Foo<'_>` invariant +the struct `Pair` is invariant over the parameter `O` diff --git a/tests/trybuild_fails/pair_not_covariant.rs b/tests/compile_fails/pair_not_covariant.rs similarity index 98% rename from tests/trybuild_fails/pair_not_covariant.rs rename to tests/compile_fails/pair_not_covariant.rs index 1d4c6e9..4eb9557 100644 --- a/tests/trybuild_fails/pair_not_covariant.rs +++ b/tests/compile_fails/pair_not_covariant.rs @@ -1,5 +1,7 @@ #![allow(unused)] +extern crate pair; + use std::{convert::Infallible, marker::PhantomData}; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/compile_fails/send_sync_miserable_failure.expected b/tests/compile_fails/send_sync_miserable_failure.expected new file mode 100644 index 0000000..122d540 --- /dev/null +++ b/tests/compile_fails/send_sync_miserable_failure.expected @@ -0,0 +1,5 @@ +cannot be sent between threads safely +tests/compile_fails/send_sync_miserable_failure.rs +required for `Pair` to implement `Send` +cannot be shared between threads safely +required for `Pair` to implement `Sync` diff --git a/tests/trybuild_fails/send_sync_miserable_failure.rs b/tests/compile_fails/send_sync_miserable_failure.rs similarity index 96% rename from tests/trybuild_fails/send_sync_miserable_failure.rs rename to tests/compile_fails/send_sync_miserable_failure.rs index a877f68..e6ec322 100644 --- a/tests/trybuild_fails/send_sync_miserable_failure.rs +++ b/tests/compile_fails/send_sync_miserable_failure.rs @@ -1,3 +1,5 @@ +extern crate pair; + use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; diff --git a/tests/trybuild_fails/contravariance_violation.rs b/tests/trybuild_fails/contravariance_violation.rs deleted file mode 100644 index 71c0a7c..0000000 --- a/tests/trybuild_fails/contravariance_violation.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::convert::Infallible; - -use pair::{HasDependent, Owner, Pair}; - -struct ContraOwner; - -impl<'owner> HasDependent<'owner> for ContraOwner { - type Dependent = fn(&'owner u32); -} -impl Owner for ContraOwner { - type Context<'a> = (); - type Error = Infallible; - - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { - Ok(|_| {}) - } -} - -fn contravariant_example<'self_borrow>(pair: &'self_borrow Pair) { - // The with_dependent function requires that the provided closure works for - // *any* dependent lifetime. Because of that, our closure cannot assume that - // the function pointer it's given can be called with any specific short - // lifetime. In fact, the *only* lifetime we can assume the function pointer - // requires is 'static - this is okay since we can give a 'static reference - // to a function pointer that expects any shorter lifetime (since &'a u32 is - // covariant in 'a). But any lifetime potentially less than 'static might - // not live as long as the body of the function pointer was allowed to - // assume - therefore, this cannot compile: - let _: &'self_borrow for<'a> fn(&'a u32) = pair.with_dependent(|f| f); -} - -fn main() { - contravariant_example(&Pair::new(ContraOwner)); -} diff --git a/tests/trybuild_fails/contravariance_violation.stderr b/tests/trybuild_fails/contravariance_violation.stderr deleted file mode 100644 index 574d846..0000000 --- a/tests/trybuild_fails/contravariance_violation.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0308]: mismatched types - --> tests/trybuild_fails/contravariance_violation.rs:32:48 - | -32 | let _: &'self_borrow for<'a> fn(&'a u32) = pair.with_dependent(|f| f); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other - | - = note: expected reference `&for<'a> fn(&'a u32)` - found reference `&fn(&u32)` -note: the lifetime requirement is introduced here - --> src/pair.rs - | - | F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T, - | ^ diff --git a/tests/trybuild_fails/extract_with_dep_mut.stderr b/tests/trybuild_fails/extract_with_dep_mut.stderr deleted file mode 100644 index 482dbe1..0000000 --- a/tests/trybuild_fails/extract_with_dep_mut.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0597]: `pair` does not live long enough - --> tests/trybuild_fails/extract_with_dep_mut.rs:26:29 - | -25 | let mut pair = Pair::new(Buff(String::from("This is a test of pair."))); - | -------- binding `pair` declared here -26 | let _: &mut Vec<&str> = pair.with_dependent_mut(|dep| dep); - | ^^^^ --- returning this value requires that `pair` is borrowed for `'static` - | | - | borrowed value does not live long enough -27 | } - | - `pair` dropped here while still borrowed diff --git a/tests/trybuild_fails/invariant_dep_extraction.stderr b/tests/trybuild_fails/invariant_dep_extraction.stderr deleted file mode 100644 index a62bcc5..0000000 --- a/tests/trybuild_fails/invariant_dep_extraction.stderr +++ /dev/null @@ -1,12 +0,0 @@ -error[E0597]: `pair` does not live long enough - --> tests/trybuild_fails/invariant_dep_extraction.rs:27:5 - | -24 | let pair: Pair = Pair::new(InvarOwner); - | ---- binding `pair` declared here -... -27 | pair.with_dependent(|dep| dep); - | ^^^^ --- returning this value requires that `pair` is borrowed for `'static` - | | - | borrowed value does not live long enough -28 | } - | - `pair` dropped here while still borrowed diff --git a/tests/trybuild_fails/keep_dep_after_into_owner.stderr b/tests/trybuild_fails/keep_dep_after_into_owner.stderr deleted file mode 100644 index fad5609..0000000 --- a/tests/trybuild_fails/keep_dep_after_into_owner.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0505]: cannot move out of `pair` because it is borrowed - --> tests/trybuild_fails/keep_dep_after_into_owner.rs:28:17 - | -25 | let pair = Pair::new(Buff(String::from("This is a test of pair."))); - | ---- binding `pair` declared here -26 | let dep: &Vec<&str> = pair.with_dependent(|dep| dep); - | ---- borrow of `pair` occurs here -27 | -28 | let owner = pair.into_owner(); - | ^^^^ move out of `pair` occurs here -29 | -30 | let _ = dep; - | --- borrow later used here diff --git a/tests/trybuild_fails/keep_dep_after_pair_drop.stderr b/tests/trybuild_fails/keep_dep_after_pair_drop.stderr deleted file mode 100644 index 358d1af..0000000 --- a/tests/trybuild_fails/keep_dep_after_pair_drop.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0505]: cannot move out of `pair` because it is borrowed - --> tests/trybuild_fails/keep_dep_after_pair_drop.rs:28:10 - | -25 | let pair = Pair::new(Buff(String::from("This is a test of pair."))); - | ---- binding `pair` declared here -26 | let dep: &Vec<&str> = pair.with_dependent(|dep| dep); - | ---- borrow of `pair` occurs here -27 | -28 | drop(pair); - | ^^^^ move out of `pair` occurs here -29 | -30 | let _ = dep; - | --- borrow later used here diff --git a/tests/trybuild_fails/not_send_dep.stderr b/tests/trybuild_fails/not_send_dep.stderr deleted file mode 100644 index ae65b7b..0000000 --- a/tests/trybuild_fails/not_send_dep.stderr +++ /dev/null @@ -1,18 +0,0 @@ -error[E0277]: `MutexGuard<'static, ()>` cannot be sent between threads safely - --> tests/trybuild_fails/not_send_dep.rs:31:18 - | -31 | check_send::>(); - | ^^^^^^^^^^^^^ `MutexGuard<'static, ()>` cannot be sent between threads safely - | - = help: within `NotSend`, the trait `Send` is not implemented for `MutexGuard<'static, ()>` -note: required because it appears within the type `NotSend` - --> tests/trybuild_fails/not_send_dep.rs:6:8 - | -6 | struct NotSend(MutexGuard<'static, ()>); - | ^^^^^^^ - = note: required for `Pair` to implement `Send` -note: required by a bound in `check_send` - --> tests/trybuild_fails/not_send_dep.rs:24:18 - | -24 | fn check_send() {} - | ^^^^ required by this bound in `check_send` diff --git a/tests/trybuild_fails/not_send_owner.stderr b/tests/trybuild_fails/not_send_owner.stderr deleted file mode 100644 index d605a3f..0000000 --- a/tests/trybuild_fails/not_send_owner.stderr +++ /dev/null @@ -1,18 +0,0 @@ -error[E0277]: `MutexGuard<'static, ()>` cannot be sent between threads safely - --> tests/trybuild_fails/not_send_owner.rs:30:18 - | -30 | check_send::>(); - | ^^^^^^^^^^^^^ `MutexGuard<'static, ()>` cannot be sent between threads safely - | - = help: within `NotSend`, the trait `Send` is not implemented for `MutexGuard<'static, ()>` -note: required because it appears within the type `NotSend` - --> tests/trybuild_fails/not_send_owner.rs:5:8 - | -5 | struct NotSend(MutexGuard<'static, ()>); - | ^^^^^^^ - = note: required for `Pair` to implement `Send` -note: required by a bound in `check_send` - --> tests/trybuild_fails/not_send_owner.rs:23:18 - | -23 | fn check_send() {} - | ^^^^ required by this bound in `check_send` diff --git a/tests/trybuild_fails/not_sync_dep.stderr b/tests/trybuild_fails/not_sync_dep.stderr deleted file mode 100644 index b63bc53..0000000 --- a/tests/trybuild_fails/not_sync_dep.stderr +++ /dev/null @@ -1,18 +0,0 @@ -error[E0277]: `UnsafeCell<()>` cannot be shared between threads safely - --> tests/trybuild_fails/not_sync_dep.rs:32:18 - | -32 | check_sync::>(); - | ^^^^^^^^^^^^^ `UnsafeCell<()>` cannot be shared between threads safely - | - = help: within `NotSync`, the trait `Sync` is not implemented for `UnsafeCell<()>` -note: required because it appears within the type `NotSync` - --> tests/trybuild_fails/not_sync_dep.rs:6:8 - | -6 | struct NotSync(UnsafeCell<()>); - | ^^^^^^^ - = note: required for `Pair` to implement `Sync` -note: required by a bound in `check_sync` - --> tests/trybuild_fails/not_sync_dep.rs:25:18 - | -25 | fn check_sync() {} - | ^^^^ required by this bound in `check_sync` diff --git a/tests/trybuild_fails/not_sync_owner.stderr b/tests/trybuild_fails/not_sync_owner.stderr deleted file mode 100644 index 67ece17..0000000 --- a/tests/trybuild_fails/not_sync_owner.stderr +++ /dev/null @@ -1,18 +0,0 @@ -error[E0277]: `UnsafeCell<()>` cannot be shared between threads safely - --> tests/trybuild_fails/not_sync_owner.rs:31:18 - | -31 | check_sync::>(); - | ^^^^^^^^^^^^^ `UnsafeCell<()>` cannot be shared between threads safely - | - = help: within `NotSync`, the trait `Sync` is not implemented for `UnsafeCell<()>` -note: required because it appears within the type `NotSync` - --> tests/trybuild_fails/not_sync_owner.rs:5:8 - | -5 | struct NotSync(UnsafeCell<()>); - | ^^^^^^^ - = note: required for `Pair` to implement `Sync` -note: required by a bound in `check_sync` - --> tests/trybuild_fails/not_sync_owner.rs:24:18 - | -24 | fn check_sync() {} - | ^^^^ required by this bound in `check_sync` diff --git a/tests/trybuild_fails/pair_not_covariant.stderr b/tests/trybuild_fails/pair_not_covariant.stderr deleted file mode 100644 index 6436ae7..0000000 --- a/tests/trybuild_fails/pair_not_covariant.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: lifetime may not live long enough - --> tests/trybuild_fails/pair_not_covariant.rs:44:22 - | -28 | fn uh_oh<'shorter, 'longer>() - | -------- ------- lifetime `'longer` defined here - | | - | lifetime `'shorter` defined here -... -44 | let pair_longer: Pair> = Pair::new(Foo(PhantomData)); - | ^^^^^^^^^^^^^^^^^^ type annotation requires that `'shorter` must outlive `'longer` - | - = help: consider adding the following bound: `'shorter: 'longer` - = note: requirement occurs because of the type `Pair>`, which makes the generic argument `Foo<'_>` invariant - = note: the struct `Pair` is invariant over the parameter `O` - = help: see for more information about variance diff --git a/tests/trybuild_fails/send_sync_miserable_failure.stderr b/tests/trybuild_fails/send_sync_miserable_failure.stderr deleted file mode 100644 index d6406c9..0000000 --- a/tests/trybuild_fails/send_sync_miserable_failure.stderr +++ /dev/null @@ -1,37 +0,0 @@ -error[E0277]: `*mut ()` cannot be sent between threads safely - --> tests/trybuild_fails/send_sync_miserable_failure.rs:27:18 - | -27 | check_send::>(); - | ^^^^^^^^^^^^^^^^^^^ `*mut ()` cannot be sent between threads safely - | - = help: within `NotSendOrSync`, the trait `Send` is not implemented for `*mut ()` -note: required because it appears within the type `NotSendOrSync` - --> tests/trybuild_fails/send_sync_miserable_failure.rs:5:8 - | -5 | struct NotSendOrSync(*mut ()); - | ^^^^^^^^^^^^^ - = note: required for `Pair` to implement `Send` -note: required by a bound in `check_send` - --> tests/trybuild_fails/send_sync_miserable_failure.rs:23:18 - | -23 | fn check_send() {} - | ^^^^ required by this bound in `check_send` - -error[E0277]: `*mut ()` cannot be shared between threads safely - --> tests/trybuild_fails/send_sync_miserable_failure.rs:28:18 - | -28 | check_sync::>(); - | ^^^^^^^^^^^^^^^^^^^ `*mut ()` cannot be shared between threads safely - | - = help: within `NotSendOrSync`, the trait `Sync` is not implemented for `*mut ()` -note: required because it appears within the type `NotSendOrSync` - --> tests/trybuild_fails/send_sync_miserable_failure.rs:5:8 - | -5 | struct NotSendOrSync(*mut ()); - | ^^^^^^^^^^^^^ - = note: required for `Pair` to implement `Sync` -note: required by a bound in `check_sync` - --> tests/trybuild_fails/send_sync_miserable_failure.rs:24:18 - | -24 | fn check_sync() {} - | ^^^^ required by this bound in `check_sync` diff --git a/tests/trybuild_test.rs b/tests/trybuild_test.rs deleted file mode 100644 index 82be4fb..0000000 --- a/tests/trybuild_test.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![allow(missing_docs, reason = "integration test")] - -#[test] -fn try_builds_nomiri() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/trybuild_fails/*.rs"); -} From a0bdbaf31302518060f26ba5c557531b2e324c5e Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 18:28:11 -0700 Subject: [PATCH 082/110] deleted TO-DO.md --- TO-DO.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 TO-DO.md diff --git a/TO-DO.md b/TO-DO.md deleted file mode 100644 index c273b17..0000000 --- a/TO-DO.md +++ /dev/null @@ -1,3 +0,0 @@ -# To-do - -- Delete this to-do list From 1712673080537dc2812157d86986659e2cc67b37 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 18:52:32 -0700 Subject: [PATCH 083/110] TO-DO.md is so back --- TO-DO.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 TO-DO.md diff --git a/TO-DO.md b/TO-DO.md new file mode 100644 index 0000000..711d36e --- /dev/null +++ b/TO-DO.md @@ -0,0 +1,8 @@ +# To-do + +- Code coverage +- Badges +- CI doc on nightly compiler (for docs.rs) +- Check out generated documentation to ensure everything looks good +- Update changelog +- Add nostd tests that build against targets that don't have a standard library From ffe005f6bc4a7ebe5067b3e8a909aea164ade671 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 18:53:56 -0700 Subject: [PATCH 084/110] updated docs CI job to run on nightly as well --- .github/workflows/ci.yaml | 1 + TO-DO.md | 1 - ci.sh | 5 ++++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c74891b..6bb9a9b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,6 +23,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly - name: Cargo docs run: ./ci.sh check_docs diff --git a/TO-DO.md b/TO-DO.md index 711d36e..8220601 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -2,7 +2,6 @@ - Code coverage - Badges -- CI doc on nightly compiler (for docs.rs) - Check out generated documentation to ensure everything looks good - Update changelog - Add nostd tests that build against targets that don't have a standard library diff --git a/ci.sh b/ci.sh index b83bcc9..d374d16 100755 --- a/ci.sh +++ b/ci.sh @@ -13,8 +13,11 @@ check_fmt() { } check_docs() { - print_header 'Building documentation...' + print_header 'Building documentation (stable)...' RUSTDOCFLAGS='-D warnings' cargo +stable doc --document-private-items --no-deps + + print_header 'Building documentation (nightly)...' + RUSTDOCFLAGS='-D warnings' cargo +nightly doc --document-private-items --no-deps } lint() { From f1e3431ee5f10a19912800633e0158ba395b26e8 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 19:28:59 -0700 Subject: [PATCH 085/110] improved documentation --- README.md | 7 +++++-- src/owner.rs | 15 ++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4b0953a..bfc9d7d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Safe API for generic self-referential pairs of owner and dependent. You define how to construct a dependent type from a reference to an owning type, -and `pair` will carefully bundle them together in a safe and freely movable +and [`Pair`] will carefully bundle them together in a safe and freely movable self-referential struct. @@ -96,6 +96,9 @@ fn main() { # How it Works +*Note: the implementation details described in this section are not part of the +crate's public API, and are subject to change.* + Under the hood, [`Pair`] moves the owner onto the heap, giving it a stable memory address. It is then borrowed and used to construct the dependent, which is also moved onto the heap. The dependent is type-erased, so that its @@ -108,7 +111,7 @@ point, the owner can safely be recovered and the `Pair` deconstructed. # Feature Flags The following feature flags are available: -- `std` (enabled by default) - Enables usage of the standard library within +- `std` *(enabled by default)* - Enables usage of the standard library within pair. Without the standard library, pair cannot handle panics as gracefully. # Related Projects diff --git a/src/owner.rs b/src/owner.rs index f63b905..0bd789b 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -8,8 +8,8 @@ /// same role in defining a [`Dependent`](HasDependent::Dependent) type, generic /// over some lifetime. /// -/// A real GAT is not used due to limitations in the current Rust compiler. For -/// the technical details on this, I recommend Sabrina Jewson's blog post on +/// A real GAT is not used due to limitations in the Rust compiler. For the +/// technical details on this, I recommend Sabrina Jewson's blog post on /// [The Better Alternative to Lifetime GATs]. /// /// [The Better Alternative to Lifetime GATs]: https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats @@ -27,15 +27,16 @@ pub trait HasDependent<'owner, ForImpliedBound: Sealed = Bounds<&'owner Self>> { /// dependent type which borrows from `Self`. Used for the [`Pair`](crate::Pair) /// struct. /// -/// The supertrait [`HasDependent<'_>`] defines the dependent type, acting as a -/// sort of generic associated type - see its documentation for more -/// information. The [`make_dependent`](Owner::make_dependent) function defines -/// how to create a dependent from a reference to an owner. +/// The supertrait [`HasDependent`] defines the dependent type, acting as a sort +/// of generic associated type - see its documentation for more information. The +/// [`make_dependent`](Owner::make_dependent) function defines how to create a +/// dependent from a reference to an owner. pub trait Owner: for<'any> HasDependent<'any> { /// Additional context provided to [`make_dependent`](Owner::make_dependent) /// as an argument. /// - /// If additional context is not necessary, this should be set to `()`. + /// If additional context is not necessary, this should be set to + /// [`()`](prim@unit). // // TODO(ichen): default this to () when associated type defaults are // stabilized (https://github.com/rust-lang/rust/issues/29661) From 6316b013c4300cf3aeec75f0d4433cf2317440bd Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 19:29:27 -0700 Subject: [PATCH 086/110] updated TO-DO.md --- TO-DO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TO-DO.md b/TO-DO.md index 8220601..bcdf53c 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -2,6 +2,5 @@ - Code coverage - Badges -- Check out generated documentation to ensure everything looks good - Update changelog - Add nostd tests that build against targets that don't have a standard library From 9756375af8109907dde3a8de8949d29c30481c86 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 19:56:51 -0700 Subject: [PATCH 087/110] added header for first section --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bfc9d7d..0771afe 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Pair + Safe API for generic self-referential pairs of owner and dependent. You define how to construct a dependent type from a reference to an owning type, From 8b7167ed3f2e6586e0bd3872a22b4b5ce127d702 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 12 Mar 2025 21:06:57 -0700 Subject: [PATCH 088/110] updated TO-DO.md --- TO-DO.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TO-DO.md b/TO-DO.md index bcdf53c..72508d3 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -2,5 +2,11 @@ - Code coverage - Badges + - crates.io + - repo (github) + - docs.rs + - license + - MSRV - Update changelog - Add nostd tests that build against targets that don't have a standard library +- Test with non-1ZST in ZST tests From 47cea669c7719db2ab8b1889c8c75df4f7372484 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 13:41:56 -0400 Subject: [PATCH 089/110] added test that builds against nostd target --- TO-DO.md | 1 - ci.sh | 11 +++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/TO-DO.md b/TO-DO.md index 72508d3..1bb8890 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -8,5 +8,4 @@ - license - MSRV - Update changelog -- Add nostd tests that build against targets that don't have a standard library - Test with non-1ZST in ZST tests diff --git a/ci.sh b/ci.sh index d374d16..8c9d093 100755 --- a/ci.sh +++ b/ci.sh @@ -42,6 +42,11 @@ build() { RUSTFLAGS='-D warnings' cargo +stable build --all-targets --all-features } +build_nostd() { + print_header 'Building on no_std target...' + RUSTFLAGS='-D warnings' cargo +stable build --target thumbv6m-none-eabi --no-default-features +} + run_tests_stable() { print_header 'Running tests (stable compiler, default features)...' RUSTFLAGS='-D warnings' cargo +stable test @@ -124,6 +129,7 @@ all_checks() { check_fmt check_docs build + build_nostd lint run_tests_stable run_tests_beta @@ -144,8 +150,9 @@ main() { "all") all_checks ;; "check_fmt") check_fmt ;; "check_docs") check_docs ;; - "build") build ;; "lint") lint ;; + "build") build ;; + "build_nostd") build_nostd ;; "run_tests_stable") run_tests_stable ;; "run_tests_beta") run_tests_beta ;; "run_tests_msrv") run_tests_msrv ;; @@ -155,7 +162,7 @@ main() { "run_tests_miri_all_features") run_tests_miri_all_features ;; *) echo "Unknown command: $command" - echo "Available commands: all (default), check_fmt, check_docs, build, lint, run_tests_stable, run_tests_beta, run_tests_msrv, run_tests_leak_sanitizer, run_tests_miri_default_features, run_tests_miri_no_features, run_tests_miri_all_features" + echo "Available commands: all (default), check_fmt, check_docs, lint, build, build_nostd, run_tests_stable, run_tests_beta, run_tests_msrv, run_tests_leak_sanitizer, run_tests_miri_default_features, run_tests_miri_no_features, run_tests_miri_all_features" exit 1 ;; esac From a2aaa9490d49edc33c35b4112f956ddebf9c6b5e Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 13:44:36 -0400 Subject: [PATCH 090/110] added nostd tests to CI --- .github/workflows/ci.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6bb9a9b..7965503 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,6 +38,17 @@ jobs: - name: Lint (clippy) run: ./ci.sh lint + build_nostd: + name: Build (nostd) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: thumbv6m-none-eabi + - name: Build (nostd) + run: ./ci.sh build_nostd + build: name: Build runs-on: ubuntu-latest From 4dbcc56f9ff63b3d606c85c047de2e99e57f3516 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 13:45:35 -0400 Subject: [PATCH 091/110] swapped test order in ci.yaml --- .github/workflows/ci.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7965503..f2f67e5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,6 +38,15 @@ jobs: - name: Lint (clippy) run: ./ci.sh lint + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Build + run: ./ci.sh build + build_nostd: name: Build (nostd) runs-on: ubuntu-latest @@ -49,15 +58,6 @@ jobs: - name: Build (nostd) run: ./ci.sh build_nostd - build: - name: Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - name: Build - run: ./ci.sh build - run_tests_stable: name: Tests (stable) runs-on: ubuntu-latest From c763d1fb20544c8810186e8091181cb471de2604 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 14:12:54 -0400 Subject: [PATCH 092/110] added non-1-ZSTs to ZST tests --- TO-DO.md | 1 - tests/zst.rs | 153 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 120 insertions(+), 34 deletions(-) diff --git a/TO-DO.md b/TO-DO.md index 1bb8890..c9c424f 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -8,4 +8,3 @@ - license - MSRV - Update changelog -- Test with non-1ZST in ZST tests diff --git a/tests/zst.rs b/tests/zst.rs index fc75d0e..6e1a4ab 100644 --- a/tests/zst.rs +++ b/tests/zst.rs @@ -3,18 +3,19 @@ use pair::{HasDependent, Owner, Pair}; use std::convert::Infallible; -// ZST owner with non-ZST dependent +// 1-ZST owner with non-ZST dependent #[derive(Debug)] -struct ZstOwner; +struct OneZstOwner; +const _: () = assert!(align_of::() == 1); #[derive(Debug, PartialEq)] struct NonZstDependent(i32); -impl HasDependent<'_> for ZstOwner { +impl HasDependent<'_> for OneZstOwner { type Dependent = NonZstDependent; } -impl Owner for ZstOwner { +impl Owner for OneZstOwner { type Context<'a> = (); type Error = Infallible; @@ -27,27 +28,26 @@ impl Owner for ZstOwner { } #[test] -fn test_zst_owner() { - let pair = Pair::new(ZstOwner); +fn test_1zst_owner() { + let pair = Pair::new(OneZstOwner); assert_eq!(size_of_val(pair.get_owner()), 0); - pair.with_dependent(|dep| { - assert_eq!(*dep, NonZstDependent(42)); - }); + assert_eq!(*pair.with_dependent(|dep| dep), NonZstDependent(42)); } -// Non-ZST owner with ZST dependent +// Non-ZST owner with 1-ZST dependent #[derive(Debug, PartialEq)] -struct NonZstOwner(i32); +struct Non1ZstOwner(i32); #[derive(Debug)] -struct ZstDependent; +struct OneZstDependent; +const _: () = assert!(align_of::() == 1); -impl HasDependent<'_> for NonZstOwner { - type Dependent = ZstDependent; +impl HasDependent<'_> for Non1ZstOwner { + type Dependent = OneZstDependent; } -impl Owner for NonZstOwner { +impl Owner for Non1ZstOwner { type Context<'a> = (); type Error = Infallible; @@ -55,28 +55,27 @@ impl Owner for NonZstOwner { &self, (): Self::Context<'_>, ) -> Result<>::Dependent, Self::Error> { - Ok(ZstDependent) + Ok(OneZstDependent) } } #[test] -fn test_zst_dependent() { - let pair = Pair::new(NonZstOwner(123)); +fn test_1zst_dependent() { + let pair = Pair::new(Non1ZstOwner(123)); - assert_eq!(*pair.get_owner(), NonZstOwner(123)); - pair.with_dependent(|dep| { - assert_eq!(size_of_val(dep), 0); - }); + assert_eq!(*pair.get_owner(), Non1ZstOwner(123)); + assert_eq!(size_of_val(pair.with_dependent(|dep| dep)), 0); } -// Both owner and dependent are ZSTs -struct BothZstOwner; +// Both owner and dependent are 1-ZSTs +struct Both1ZstOwner; +const _: () = assert!(align_of::() == 1); -impl HasDependent<'_> for BothZstOwner { - type Dependent = ZstDependent; +impl HasDependent<'_> for Both1ZstOwner { + type Dependent = OneZstDependent; } -impl Owner for BothZstOwner { +impl Owner for Both1ZstOwner { type Context<'a> = (); type Error = Infallible; @@ -84,15 +83,103 @@ impl Owner for BothZstOwner { &self, (): Self::Context<'_>, ) -> Result<>::Dependent, Self::Error> { - Ok(ZstDependent) + Ok(OneZstDependent) } } #[test] -fn test_both_zst() { - let pair = Pair::new(BothZstOwner); +fn test_both_1zst() { + let pair = Pair::new(Both1ZstOwner); assert_eq!(size_of_val(pair.get_owner()), 0); - pair.with_dependent(|dep| { - assert_eq!(size_of_val(dep), 0); - }); + assert_eq!(size_of_val(pair.with_dependent(|dep| dep)), 0); +} + +// ///////////////////////////////////////////////////////////////////////////// + +// Non-1 alignment ZST owner with non-ZST dependent +#[derive(Debug)] +struct BigZstOwner([u64; 0]); +const _: () = assert!(align_of::() > 1); + +impl HasDependent<'_> for BigZstOwner { + type Dependent = NonZstDependent; +} + +impl Owner for BigZstOwner { + type Context<'a> = (); + type Error = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + Ok(NonZstDependent(23)) + } +} + +#[test] +fn test_bigzst_owner() { + let pair = Pair::new(BigZstOwner([])); + + assert_eq!(size_of_val(pair.get_owner()), 0); + assert_eq!(*pair.with_dependent(|dep| dep), NonZstDependent(23)); +} + +// Non-ZST owner with Non-1 alignment ZST dependent +#[derive(Debug, PartialEq)] +struct NonBigZstOwner(i32); + +#[derive(Debug)] +struct BigZstDependent([u64; 0]); +const _: () = assert!(align_of::() > 1); + +impl HasDependent<'_> for NonBigZstOwner { + type Dependent = BigZstDependent; +} + +impl Owner for NonBigZstOwner { + type Context<'a> = (); + type Error = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + Ok(BigZstDependent([])) + } +} + +#[test] +fn test_bigzst_dependent() { + let pair = Pair::new(NonBigZstOwner(789)); + + assert_eq!(*pair.get_owner(), NonBigZstOwner(789)); + assert_eq!(size_of_val(pair.with_dependent(|dep| dep)), 0); +} + +// Both owner and dependent are Non-1 alignment ZSTs +struct BothBigZstOwner([u64; 0]); +const _: () = assert!(align_of::() > 1); + +impl HasDependent<'_> for BothBigZstOwner { + type Dependent = BigZstDependent; +} + +impl Owner for BothBigZstOwner { + type Context<'a> = (); + type Error = Infallible; + + fn make_dependent( + &self, + (): Self::Context<'_>, + ) -> Result<>::Dependent, Self::Error> { + Ok(BigZstDependent([])) + } +} + +#[test] +fn test_both_bigzst() { + let pair = Pair::new(BothBigZstOwner([])); + assert_eq!(size_of_val(pair.get_owner()), 0); + assert_eq!(size_of_val(pair.with_dependent(|dep| dep)), 0); } From 68b58913caebc442266adf243a39beb2b0182906 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 14:36:18 -0400 Subject: [PATCH 093/110] cut code coverage (too many false positives) --- TO-DO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TO-DO.md b/TO-DO.md index c9c424f..f2a2318 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -1,6 +1,5 @@ # To-do -- Code coverage - Badges - crates.io - repo (github) From 3df4611a58064f94513fb97e61553bf7947a6f89 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 20:10:31 -0400 Subject: [PATCH 094/110] added badges --- README.md | 6 ++++++ TO-DO.md | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0771afe..7daab24 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # Pair + +[![GitHub](https://img.shields.io/badge/Source-ijchen/pair-FFD639?labelColor=555555&logo=github)](https://github.com/ijchen/pair) +[![crates.io](https://img.shields.io/crates/v/pair?logo=rust)](https://crates.io/crates/pair) +[![docs.rs](https://img.shields.io/docsrs/pair?logo=docs.rs)](https://docs.rs/pair) +![License](https://img.shields.io/crates/l/pair) + Safe API for generic self-referential pairs of owner and dependent. You define how to construct a dependent type from a reference to an owning type, diff --git a/TO-DO.md b/TO-DO.md index f2a2318..ed4fc8b 100644 --- a/TO-DO.md +++ b/TO-DO.md @@ -1,9 +1,3 @@ # To-do -- Badges - - crates.io - - repo (github) - - docs.rs - - license - - MSRV - Update changelog From 86af9ce9baef548bfb7b8c5c345c58db0a43f496 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 20:14:00 -0400 Subject: [PATCH 095/110] made license badge ignore clicks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7daab24..c40f926 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![GitHub](https://img.shields.io/badge/Source-ijchen/pair-FFD639?labelColor=555555&logo=github)](https://github.com/ijchen/pair) [![crates.io](https://img.shields.io/crates/v/pair?logo=rust)](https://crates.io/crates/pair) [![docs.rs](https://img.shields.io/docsrs/pair?logo=docs.rs)](https://docs.rs/pair) -![License](https://img.shields.io/crates/l/pair) +[![License](https://img.shields.io/crates/l/pair)](#) Safe API for generic self-referential pairs of owner and dependent. From 6331612632f8e06890f679dfcf24082bd3370e8c Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 20:18:23 -0400 Subject: [PATCH 096/110] updated CHANGELOG.md for v1.0.0 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af5dec5..12b8c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ +# Major Version 1 + +## v1.0.0 + +Implemented and stabilized the core functionality of this crate: +- Defined `Pair` struct +- Defined `Owner` and `HasDependent` traits + # Major Version 0 ## v0.1.0 + Initial (empty) release. From 292ce330c5768eb025c97766daec119b028fc2d9 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Thu, 13 Mar 2025 20:18:44 -0400 Subject: [PATCH 097/110] removed TO-DO.md --- TO-DO.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 TO-DO.md diff --git a/TO-DO.md b/TO-DO.md deleted file mode 100644 index ed4fc8b..0000000 --- a/TO-DO.md +++ /dev/null @@ -1,3 +0,0 @@ -# To-do - -- Update changelog From 0a9382a9f99aeabd09c93ad8ae6a68b8745b50b8 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 14 Mar 2025 09:50:23 -0400 Subject: [PATCH 098/110] added forgotten ?Sized relaxing bound --- src/pair.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pair.rs b/src/pair.rs index 9d1fc67..f842de7 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -541,7 +541,7 @@ where // problems if sharing a reference to either the owner or the dependent across // multiple threads could cause problems (since references to both are made // accessible through references to the `Pair`). -unsafe impl Sync for Pair +unsafe impl Sync for Pair where O: Sync, for<'any> >::Dependent: Sync, From 13a513839da8a0814dc2a60bd7ee7d256317a5e7 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 14 Mar 2025 11:08:54 -0400 Subject: [PATCH 099/110] added TODO comment to look into dropck mismatch --- src/pair.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pair.rs b/src/pair.rs index f842de7..a9028ce 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -470,6 +470,11 @@ impl + ?Sized> Pair { // drop of the Pair. As far as I know, this is the only concern surrounding // dropck not understanding the semantics of Pair, and cannot cause unsoundness // for the reasons described above. +// +// TODO(ichen): Dig into the above and determine more concretely whether or not +// this could be a problem, even theoretically. This may require having more +// formally defined language semantics first, like a memory model and more +// general information about what unsafe code can and cannot assume. impl Drop for Pair { fn drop(&mut self) { // Drop the dependent `Box<>::Dependent>` From 14e243e9a1f324c539cf18f86fc0bc39fc10eeb7 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 14 Mar 2025 18:14:34 -0400 Subject: [PATCH 100/110] refactored panic handling to use drop guards --- .github/workflows/ci.yaml | 30 +------ Cargo.toml | 14 ---- README.md | 6 -- ci.sh | 105 ++++++------------------- src/drop_guard.rs | 9 +++ src/lib.rs | 5 +- src/owner.rs | 2 +- src/pair.rs | 160 +++++++++++++++++++++++--------------- src/panicking.rs | 69 ---------------- tests/panic_safety.rs | 140 +-------------------------------- 10 files changed, 137 insertions(+), 403 deletions(-) create mode 100644 src/drop_guard.rs delete mode 100644 src/panicking.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f2f67e5..923b07c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -94,35 +94,13 @@ jobs: - name: Tests (leak sanitizer) run: ./ci.sh run_tests_leak_sanitizer - run_tests_miri_default_features: - name: Tests (MIRI, default features) + run_tests_miri: + name: Tests (MIRI) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: miri - - name: Tests (MIRI, default features) - run: ./ci.sh run_tests_miri_default_features - - run_tests_miri_no_features: - name: Tests (MIRI, no features) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - components: miri - - name: Tests (MIRI, no features) - run: ./ci.sh run_tests_miri_no_features - - run_tests_miri_all_features: - name: Tests (MIRI, all features) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - components: miri - - name: Tests (MIRI, all features) - run: ./ci.sh run_tests_miri_all_features + - name: Tests (MIRI) + run: ./ci.sh run_tests_miri diff --git a/Cargo.toml b/Cargo.toml index e4ee7c1..c10b83c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,20 +32,6 @@ publish = false # ON_RELEASE: Remove publish = false [dev-dependencies] loom = "0.7.2" -# # # # # # # # # # # # # # # # # # # # -# # -# FEATURES # -# # -# # # # # # # # # # # # # # # # # # # # - -[features] -default = ["std"] - -# The `std` feature (enabled by default) enables usage of the standard library -# within pair. Without the standard library, pair cannot handle panics as -# gracefully. -std = [] - # # # # # # # # # # # # # # # # # # # # # # # LINTS # diff --git a/README.md b/README.md index c40f926..eeac683 100644 --- a/README.md +++ b/README.md @@ -116,12 +116,6 @@ code could do. When the owner needs to be dropped or recovered, the dependent will first be recovered and dropped, ending the borrow of the owner. At that point, the owner can safely be recovered and the `Pair` deconstructed. -# Feature Flags - -The following feature flags are available: -- `std` *(enabled by default)* - Enables usage of the standard library within - pair. Without the standard library, pair cannot handle panics as gracefully. - # Related Projects | Crate | Macro free | No `alloc` | Maintained | Soundness | diff --git a/ci.sh b/ci.sh index 8c9d093..01be4b6 100755 --- a/ci.sh +++ b/ci.sh @@ -21,109 +21,52 @@ check_docs() { } lint() { - print_header 'Linting with cargo clippy (default features)...' + print_header 'Linting with cargo clippy...' cargo +stable clippy --no-deps --all-targets -- -D warnings - - print_header 'Linting with cargo clippy (no features)...' - cargo +stable clippy --no-deps --all-targets --no-default-features -- -D warnings - - print_header 'Linting with cargo clippy (all features)...' - cargo +stable clippy --no-deps --all-targets --all-features -- -D warnings } build() { - print_header 'Running cargo build (default features)...' + print_header 'Running cargo build...' RUSTFLAGS='-D warnings' cargo +stable build --all-targets - - print_header 'Running cargo build (no features)...' - RUSTFLAGS='-D warnings' cargo +stable build --all-targets --no-default-features - - print_header 'Running cargo build (all features)...' - RUSTFLAGS='-D warnings' cargo +stable build --all-targets --all-features } build_nostd() { print_header 'Building on no_std target...' - RUSTFLAGS='-D warnings' cargo +stable build --target thumbv6m-none-eabi --no-default-features + RUSTFLAGS='-D warnings' cargo +stable build --target thumbv6m-none-eabi } run_tests_stable() { - print_header 'Running tests (stable compiler, default features)...' + print_header 'Running tests (stable compiler)...' RUSTFLAGS='-D warnings' cargo +stable test - - # NOTE: some tests (containing `std_only`) require the `std` feature to run. - print_header 'Running tests (stable compiler, no features)...' - RUSTFLAGS='-D warnings' cargo +stable test --no-default-features -- --skip std_only - - print_header 'Running tests (stable compiler, all features)...' - RUSTFLAGS='-D warnings' cargo +stable test --all-features } run_tests_beta() { - print_header 'Running tests (beta compiler, default features)...' + print_header 'Running tests (beta compiler)...' RUSTFLAGS='-D warnings' cargo +beta test - - # NOTE: some tests (containing `std_only`) require the `std` feature to run. - print_header 'Running tests (beta compiler, no features)...' - RUSTFLAGS='-D warnings' cargo +beta test --no-default-features -- --skip std_only - - print_header 'Running tests (beta compiler, all features)...' - RUSTFLAGS='-D warnings' cargo +beta test --all-features } run_tests_msrv() { local msrv="1.85.0" - print_header "Running tests (MSRV compiler ($msrv), default features)..." + print_header "Running tests (MSRV compiler ($msrv))..." RUSTFLAGS='-D warnings' cargo "+$msrv" test - - # NOTE: some tests (containing `std_only`) require the `std` feature to run. - print_header "Running tests (MSRV compiler ($msrv), no features)..." - RUSTFLAGS='-D warnings' cargo "+$msrv" test --no-default-features -- --skip std_only - - print_header "Running tests (MSRV compiler ($msrv), all features)..." - RUSTFLAGS='-D warnings' cargo "+$msrv" test --all-features } run_tests_leak_sanitizer() { # NOTE: loom seems to make the leak sanitizer unhappy. I don't think that # combination of tests is important, so we just skip loom tests here. - print_header 'Running tests with leak sanitizer (default features)...' + print_header 'Running tests with leak sanitizer...' RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test -- --skip loom - - # NOTE: some tests (containing `std_only`) require the `std` feature to run. - print_header 'Running tests with leak sanitizer (no features)...' - RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --no-default-features -- --skip std_only --skip loom - - print_header 'Running tests with leak sanitizer (all features)...' - RUSTFLAGS='-D warnings -Z sanitizer=leak' cargo +nightly test --all-features -- --skip loom } -# NOTE: MIRI runs pretty slowly, so splitting up the MIRI tests in CI actually -# gives a pretty meaningful speedup. -run_tests_miri_default_features() { +run_tests_miri() { # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are # skipped here. - print_header 'Running tests with MIRI (default features)...' + print_header 'Running tests with MIRI...' RUSTFLAGS='-D warnings' MIRIFLAGS='-Zmiri-strict-provenance' cargo +nightly miri test -- --skip nomiri } -run_tests_miri_no_features() { - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are - # skipped here. - # NOTE: some tests (containing `std_only`) require the `std` feature to run. - print_header 'Running tests with MIRI (no features)...' - RUSTFLAGS='-D warnings' MIRIFLAGS='-Zmiri-strict-provenance' cargo +nightly miri test --no-default-features -- --skip nomiri --skip std_only -} - -run_tests_miri_all_features() { - # NOTE: some tests (containing `nomiri`) can't run under MIRI, and are - # skipped here. - print_header 'Running tests with MIRI (all features)...' - RUSTFLAGS='-D warnings' MIRIFLAGS='-Zmiri-strict-provenance' cargo +nightly miri test --all-features -- --skip nomiri -} - # Run all checks all_checks() { check_fmt @@ -135,9 +78,7 @@ all_checks() { run_tests_beta run_tests_msrv run_tests_leak_sanitizer - run_tests_miri_default_features - run_tests_miri_no_features - run_tests_miri_all_features + run_tests_miri print_header "All checks passed! 🎉" } @@ -147,22 +88,20 @@ main() { local command="${1:-"all"}" case "$command" in - "all") all_checks ;; - "check_fmt") check_fmt ;; - "check_docs") check_docs ;; - "lint") lint ;; - "build") build ;; - "build_nostd") build_nostd ;; - "run_tests_stable") run_tests_stable ;; - "run_tests_beta") run_tests_beta ;; - "run_tests_msrv") run_tests_msrv ;; - "run_tests_leak_sanitizer") run_tests_leak_sanitizer ;; - "run_tests_miri_default_features") run_tests_miri_default_features ;; - "run_tests_miri_no_features") run_tests_miri_no_features ;; - "run_tests_miri_all_features") run_tests_miri_all_features ;; + "all") all_checks ;; + "check_fmt") check_fmt ;; + "check_docs") check_docs ;; + "lint") lint ;; + "build") build ;; + "build_nostd") build_nostd ;; + "run_tests_stable") run_tests_stable ;; + "run_tests_beta") run_tests_beta ;; + "run_tests_msrv") run_tests_msrv ;; + "run_tests_leak_sanitizer") run_tests_leak_sanitizer ;; + "run_tests_miri") run_tests_miri ;; *) echo "Unknown command: $command" - echo "Available commands: all (default), check_fmt, check_docs, lint, build, build_nostd, run_tests_stable, run_tests_beta, run_tests_msrv, run_tests_leak_sanitizer, run_tests_miri_default_features, run_tests_miri_no_features, run_tests_miri_all_features" + echo "Available commands: all (default), check_fmt, check_docs, lint, build, build_nostd, run_tests_stable, run_tests_beta, run_tests_msrv, run_tests_leak_sanitizer, run_tests_miri" exit 1 ;; esac diff --git a/src/drop_guard.rs b/src/drop_guard.rs new file mode 100644 index 0000000..8bbeddf --- /dev/null +++ b/src/drop_guard.rs @@ -0,0 +1,9 @@ +/// A simple struct that runs a closure on drop. Used to clean up resources +/// during panic unwinding within [`Pair`](crate::pair). +pub struct DropGuard(pub F); + +impl Drop for DropGuard { + fn drop(&mut self) { + (self.0)(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 43dc03a..60bd3ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,14 +7,11 @@ #![cfg_attr(any(doc, test), doc = include_str!("../README.md"))] #![no_std] -#[cfg(not(feature = "std"))] extern crate alloc; -#[cfg(feature = "std")] -extern crate std; +mod drop_guard; mod owner; mod pair; -mod panicking; pub use owner::{HasDependent, Owner}; pub use pair::Pair; diff --git a/src/owner.rs b/src/owner.rs index 0bd789b..7f08a97 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -46,7 +46,7 @@ pub trait Owner: for<'any> HasDependent<'any> { /// the event of an error. /// /// If `make_dependent` can't fail, this should be set to - /// [`Infallible`](std::convert::Infallible). + /// [`Infallible`](core::convert::Infallible). // // TODO(ichen): default this to core::convert::Infallible (or preferably !) // when associated type defaults are stabilized diff --git a/src/pair.rs b/src/pair.rs index a9028ce..22141ae 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -2,15 +2,9 @@ use core::{convert::Infallible, fmt::Debug, marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; -#[cfg(not(feature = "std"))] use alloc::boxed::Box; -#[cfg(feature = "std")] -use std::boxed::Box; -use crate::{ - HasDependent, Owner, - panicking::{catch_unwind, resume_unwind}, -}; +use crate::{HasDependent, Owner, drop_guard::DropGuard}; /// A self-referential pair containing both some [`Owner`] and its /// [`Dependent`](HasDependent::Dependent). @@ -51,8 +45,8 @@ pub struct Pair { /// - The returned `NonNull` will be suitably aligned for T /// - The returned `NonNull` will point to a valid T /// - The returned `NonNull` was allocated with the -/// [`Global`](std::alloc::Global) allocator and a valid -/// [`Layout`](std::alloc::Layout) for `T`. +/// [`Global`](alloc::alloc::Global) allocator and a valid +/// [`Layout`](alloc::alloc::Layout) for `T`. fn non_null_from_box(value: Box) -> NonNull { // See: https://github.com/rust-lang/rust/issues/47336#issuecomment-586578713 NonNull::from(Box::leak(value)) @@ -114,40 +108,41 @@ impl Pair { // be able to drop the boxed owner before unwinding the rest of the // stack to avoid unnecessarily leaking memory (and potentially other // resources). - let maybe_dependent = match catch_unwind(|| { + let panic_drop_guard = DropGuard(|| { + // If this code is executed, it means make_dependent panicked and we + // never `mem::forget(..)`'d this drop guard. Recover and drop the + // boxed owner. + + // SAFETY: `owner` was just created from a Box earlier in + // `try_new_from_box_with_context`, and not invalidated since then. + // Because we haven't given away access to a `Self`, and the one + // borrow we took of the owner to pass to `make_dependent` has + // expired (since it panicked), we know there are no outstanding + // borrows to owner. Therefore, reconstructing the original Box + // is okay. + let owner: Box = unsafe { Box::from_raw(owner.as_ptr()) }; + + // If the owner's drop *also* panics, that will be a double-panic. + // This will cause an abort, which is fine - drops generally + // shouldn't panic, and if the user *really* wants to handle this, + // they can check if the thread is panicking within owner's drop + // before performing any operations which could panic. + drop(owner); + }); + + let maybe_dependent = { // SAFETY: `owner` was just converted from a valid Box, and inherits // the alignment and validity guarantees of Box. Additionally, the // value behind the pointer is currently not borrowed at all - this // marks the beginning of a shared borrow which will last until the - // returned `Pair` is dropped (or immediately, if `make_dependent` + // returned `Pair` is dropped (or ends immediately if make_dependent // panics or returns an error). unsafe { owner.as_ref() }.make_dependent(context) - }) { - Ok(maybe_dependent) => maybe_dependent, - Err(payload) => { - // make_dependent panicked - drop the owner, then resume_unwind - - // SAFETY: `owner` was just created from a Box earlier in this - // function, and not invalidated since then. Because we haven't - // given away access to a `Self`, and the one borrow we took of - // the dependent to pass to `make_dependent` has expired, we - // know there are no outstanding borrows to owner. Therefore, - // reconstructing the original Box is okay. - let owner: Box = unsafe { Box::from_raw(owner.as_ptr()) }; - - // If the owner's drop *also* panics, we're in a super weird - // state. I think it makes more sense to resume_unwind with the - // payload of the first panic (from `make_dependent`), so if the - // owner's drop panics we just ignore it and continue on to - // resume_unwind with `make_dependent`'s payload. - let _ = catch_unwind(|| drop(owner)); - - // It's very important that we diverge here - carrying on to the - // rest of this constructor would be unsound. - resume_unwind(payload); - } }; + // The call to `make_dependent` didn't panic - disarm our drop guard + core::mem::forget(panic_drop_guard); + // If `make_dependent(..)` failed, early return out from this function. let dependent = match maybe_dependent { Ok(dependent) => dependent, @@ -164,11 +159,42 @@ impl Pair { } }; + // We're about to call `Box::new(..)` - if it panics, we want to be able + // to drop the boxed owner before unwinding the rest of the stack to + // avoid unnecessarily leaking memory (and potentially other resources). + let panic_drop_guard = DropGuard(|| { + // If this code is executed, it means `Box::new(..)` panicked and we + // never `mem::forget(..)`'d this drop guard. Recover and drop the + // boxed owner. + + // SAFETY: `owner` was just created from a Box earlier in + // `try_new_from_box_with_context`, and not invalidated since then. + // Because we haven't given away access to a `Self`, and the one + // borrow of the owner stored in the dependent has expired (since we + // gave ownership of the dependent to the `Box::new(..)` call that + // panicked), we know there are no outstanding borrows to owner. + // Therefore, reconstructing the original Box is okay. + let owner: Box = unsafe { Box::from_raw(owner.as_ptr()) }; + + // If the owner's drop *also* panics, that will be a double-panic. + // This will cause an abort, which is fine - drops generally + // shouldn't panic, and if the user *really* wants to handle this, + // they can check if the thread is panicking within owner's drop + // before performing any operations which could panic. + drop(owner); + }); + + // Move `dependent` to the heap, so we can store it as a type-erased + // pointer. + let dependent = Box::new(dependent); + + // The call to `Box::new(..)` didn't panic - disarm our drop guard + core::mem::forget(panic_drop_guard); + // Type-erase dependent so it's inexpressible self-referential lifetime // goes away (we know that it's borrowing self.owner immutably from // construction (now) until drop) - let dependent: NonNull<>::Dependent> = - non_null_from_box(Box::new(dependent)); + let dependent: NonNull<>::Dependent> = non_null_from_box(dependent); let dependent: NonNull<()> = dependent.cast(); Ok(Self { @@ -286,8 +312,10 @@ impl Pair { // We're about to drop the dependent - if it panics, we want to be able // to drop the boxed owner before unwinding the rest of the stack to // avoid unnecessarily leaking memory (and potentially other resources). - if let Err(payload) = catch_unwind(|| drop(dependent)) { - // Dependent's drop panicked - drop the owner, then resume_unwind + let panic_drop_guard = DropGuard(|| { + // If this code is executed, it means the dependent's drop panicked + // and we never `mem::forget(..)`'d this drop guard. Recover and + // drop the boxed owner. // SAFETY: `this.owner` was originally created from a Box, and never // invalidated since then. Because we took ownership of `self`, and @@ -297,17 +325,19 @@ impl Pair { // original Box is okay. let owner: Box = unsafe { Box::from_raw(this.owner.as_ptr()) }; - // If the owner's drop *also* panics, we're in a super weird state. - // I think it makes more sense to resume_unwind with the payload of - // the first panic (from dependent's drop), so if the owner's drop - // panics we just ignore it and continue on to resume_unwind with - // the dependent's payload. - let _ = catch_unwind(|| drop(owner)); + // If the owner's drop *also* panics, that will be a double-panic. + // This will cause an abort, which is fine - drops generally + // shouldn't panic, and if the user *really* wants to handle this, + // they can check if the thread is panicking within owner's drop + // before performing any operations which could panic. + drop(owner); + }); - // It's very important that we diverge here - carrying on to the - // rest of this function would be unsound. - resume_unwind(payload); - } + // Drop the dependent + drop(dependent); + + // The dependent's drop didn't panic - disarm our drop guard + core::mem::forget(panic_drop_guard); // SAFETY: `this.owner` was originally created from a Box, and never // invalidated since then. Because we took ownership of `self`, and we @@ -494,10 +524,12 @@ impl Drop for Pair { // We're about to drop the dependent - if it panics, we want to be able // to drop the boxed owner before unwinding the rest of the stack to // avoid unnecessarily leaking memory (and potentially other resources). - if let Err(payload) = catch_unwind(|| drop(dependent)) { - // Dependent's drop panicked - drop the owner, then resume_unwind + let panic_drop_guard = DropGuard(|| { + // If this code is executed, it means the dependent's drop panicked + // and we never `mem::forget(..)`'d this drop guard. Recover and + // drop the boxed owner. - // SAFETY: `this.owner` was originally created from a Box, and never + // SAFETY: `self.owner` was originally created from a Box, and never // invalidated since then. Because we are in drop, and we just // dropped the dependent (well, the drop panicked - but its borrow // of the owner has certainly expired), we know there are no @@ -505,21 +537,23 @@ impl Drop for Pair { // original Box is okay. let owner: Box = unsafe { Box::from_raw(self.owner.as_ptr()) }; - // If the owner's drop *also* panics, we're in a super weird state. - // I think it makes more sense to resume_unwind with the payload of - // the first panic (from dependent's drop), so if the owner's drop - // panics we just ignore it and continue on to resume_unwind with - // the dependent's payload. - let _ = catch_unwind(|| drop(owner)); + // If the owner's drop *also* panics, that will be a double-panic. + // This will cause an abort, which is fine - drops generally + // shouldn't panic, and if the user *really* wants to handle this, + // they can check if the thread is panicking within owner's drop + // before performing any operations which could panic. + drop(owner); + }); - // It's very important that we diverge here - carrying on to the - // rest of drop would be unsound. - resume_unwind(payload); - } + // Drop the dependent + drop(dependent); + + // The dependent's drop didn't panic - disarm our drop guard + core::mem::forget(panic_drop_guard); // Drop the owner `Box` - // SAFETY: `this.owner` was originally created from a Box, and never + // SAFETY: `self.owner` was originally created from a Box, and never // invalidated since then. Because we are in drop, and we just dropped // the dependent, we know there are no outstanding borrows to owner. // Therefore, reconstructing the original Box is okay. diff --git a/src/panicking.rs b/src/panicking.rs deleted file mode 100644 index b76921f..0000000 --- a/src/panicking.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Panic handling abstracted to work with and without `#[cfg(feature = "std")]` - -#[cfg(feature = "std")] -use std::boxed::Box; - -#[cfg(feature = "std")] -type PanicPayloadInner = Box; -#[cfg(not(feature = "std"))] -type PanicPayloadInner = core::convert::Infallible; - -/// A panic payload, abstracted to work with and without -/// `#[cfg(feature = "std")]`. -/// -/// With `std`, this will contain a normal panic payload -/// (`Box`). -/// -/// Without `std`, this will never be constructed, and contains -/// `std::convert::Infallible`. -pub struct PanicPayload(PanicPayloadInner); - -/// [`std::panic::catch_unwind`], abstracted to work with and without `std`. -/// -/// With `std`, this function just delegates to [`std::panic::catch_unwind`]. -/// -/// Without `std`, this function will call the provided closure without -/// attempting to catch panics at all - it will therefore always either return -/// [`Ok`] or diverge. -/// -/// Note that this function additionally does not require the closure is -/// [`UnwindSafe`](core::panic::UnwindSafe) - our usage within this crate would -/// be wrapping all calls in [`AssertUnwindSafe`](core::panic::AssertUnwindSafe) -/// anyway. It would be difficult for an API consumer to observe violated -/// invariants through unwind unsafety, and the API burden on normal use cases -/// would be too heavy if we didn't assert unwind safety on their behalf. -pub fn catch_unwind R, R>(f: F) -> Result { - // If we have `std`, delegate to `catch_unwind` - #[cfg(feature = "std")] - let output = std::panic::catch_unwind(core::panic::AssertUnwindSafe(f)).map_err(PanicPayload); - - // If we don't have `std`, just call the function and let panics go uncaught - #[cfg(not(feature = "std"))] - let output = Ok(f()); - - output -} - -#[cfg_attr( - not(feature = "std"), - expect( - clippy::needless_pass_by_value, - reason = "needs to be pass by value for std" - ) -)] -/// [`std::panic::resume_unwind`], abstracted to work with and without `std`. -/// -/// With `std`, this function just delegates to [`std::panic::resume_unwind`]. -/// -/// Without `std`, this function is impossible to call - a [`PanicPayload`] is -/// never produced by [`catch_unwind`] without `std`. -pub fn resume_unwind(payload: PanicPayload) -> ! { - // If we have `std`, delegate to `resume_unwind` - #[cfg(feature = "std")] - std::panic::resume_unwind(payload.0); - - // If we don't have `std`, a PanicPayload can never be produced, so this - // function can't be called in the first place - #[cfg(not(feature = "std"))] - match payload {} -} diff --git a/tests/panic_safety.rs b/tests/panic_safety.rs index e08a0b5..6ba7c47 100644 --- a/tests/panic_safety.rs +++ b/tests/panic_safety.rs @@ -37,7 +37,7 @@ impl Owner for PanicOnMakeDependent { } #[test] -fn make_dependent_panic_handled_std_only() { +fn make_dependent_panic_handled() { let owner_drop_called = Rc::new(RefCell::new(false)); let owner_drop_called2 = Rc::clone(&owner_drop_called); let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| { @@ -51,46 +51,6 @@ fn make_dependent_panic_handled_std_only() { assert!(*owner_drop_called.borrow()); } -// make_dependent panics and owner drop panics -struct PanicOnMakeDependentAndOwnerDrop(Rc>); -impl Drop for PanicOnMakeDependentAndOwnerDrop { - fn drop(&mut self) { - *self.0.borrow_mut() = true; - panic!("lol"); - } -} - -impl HasDependent<'_> for PanicOnMakeDependentAndOwnerDrop { - type Dependent = (); -} - -impl Owner for PanicOnMakeDependentAndOwnerDrop { - type Context<'a> = (); - type Error = Infallible; - - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { - panic_any(MyPayload(42)); - } -} - -#[test] -fn make_dependent_and_owner_drop_panic_handled_std_only() { - let owner_drop_called = Rc::new(RefCell::new(false)); - let owner_drop_called2 = Rc::clone(&owner_drop_called); - let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| { - Pair::new(PanicOnMakeDependentAndOwnerDrop(owner_drop_called2)); - })) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(payload, MyPayload(42)); - assert!(*owner_drop_called.borrow()); -} - // dependent drop panics in into_owner #[derive(Debug)] struct PanicOnDepDropIntoOwner(Rc>); @@ -123,7 +83,7 @@ impl Owner for PanicOnDepDropIntoOwner { } #[test] -fn dependent_drop_panic_handled_in_into_owner_std_only() { +fn dependent_drop_panic_handled_in_into_owner() { let owner_drop_called = Rc::new(RefCell::new(false)); let pair = Pair::new(PanicOnDepDropIntoOwner(Rc::clone(&owner_drop_called))); let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| pair.into_owner())) @@ -135,53 +95,6 @@ fn dependent_drop_panic_handled_in_into_owner_std_only() { assert!(*owner_drop_called.borrow()); } -// dependent drop panics and owner drop panics in into_owner -#[derive(Debug)] -struct PanicOnDepAndOwnerDropIntoOwner(Rc>); -impl Drop for PanicOnDepAndOwnerDropIntoOwner { - fn drop(&mut self) { - *self.0.borrow_mut() = true; - panic!("ruh roh, raggy"); - } -} - -struct PanicOnDepAndOwnerDropIntoOwnerDep; -impl Drop for PanicOnDepAndOwnerDropIntoOwnerDep { - fn drop(&mut self) { - panic_any(MyPayload(1)); - } -} -impl HasDependent<'_> for PanicOnDepAndOwnerDropIntoOwner { - type Dependent = PanicOnDepAndOwnerDropIntoOwnerDep; -} - -impl Owner for PanicOnDepAndOwnerDropIntoOwner { - type Context<'a> = (); - type Error = Infallible; - - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { - Ok(PanicOnDepAndOwnerDropIntoOwnerDep) - } -} - -#[test] -fn dependent_and_owner_drop_panic_handled_in_into_owner_std_only() { - let owner_drop_called = Rc::new(RefCell::new(false)); - let pair = Pair::new(PanicOnDepAndOwnerDropIntoOwner(Rc::clone( - &owner_drop_called, - ))); - let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| pair.into_owner())) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(payload, MyPayload(1)); - assert!(*owner_drop_called.borrow()); -} - // dependent drop panics in pair drop #[derive(Debug)] struct PanicOnDepDropPairDrop(Rc>); @@ -214,7 +127,7 @@ impl Owner for PanicOnDepDropPairDrop { } #[test] -fn dependent_drop_panic_handled_in_pair_drop_std_only() { +fn dependent_drop_panic_handled_in_pair_drop() { let owner_drop_called = Rc::new(RefCell::new(false)); let pair = Pair::new(PanicOnDepDropPairDrop(Rc::clone(&owner_drop_called))); let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| drop(pair))) @@ -225,50 +138,3 @@ fn dependent_drop_panic_handled_in_pair_drop_std_only() { assert_eq!(payload, MyPayload(3)); assert!(*owner_drop_called.borrow()); } - -// dependent drop panics and owner drop panics in into_owner -#[derive(Debug)] -struct PanicOnDepAndOwnerDropPairDrop(Rc>); -impl Drop for PanicOnDepAndOwnerDropPairDrop { - fn drop(&mut self) { - *self.0.borrow_mut() = true; - panic!("ruh roh, raggy"); - } -} - -struct PanicOnDepAndOwnerDropPairDropDep; -impl Drop for PanicOnDepAndOwnerDropPairDropDep { - fn drop(&mut self) { - panic_any(MyPayload(5)); - } -} -impl HasDependent<'_> for PanicOnDepAndOwnerDropPairDrop { - type Dependent = PanicOnDepAndOwnerDropPairDropDep; -} - -impl Owner for PanicOnDepAndOwnerDropPairDrop { - type Context<'a> = (); - type Error = Infallible; - - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { - Ok(PanicOnDepAndOwnerDropPairDropDep) - } -} - -#[test] -fn dependent_and_owner_drop_panic_handled_in_pair_drop_std_only() { - let owner_drop_called = Rc::new(RefCell::new(false)); - let pair = Pair::new(PanicOnDepAndOwnerDropPairDrop(Rc::clone( - &owner_drop_called, - ))); - let payload: MyPayload = *catch_unwind(AssertUnwindSafe(|| drop(pair))) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(payload, MyPayload(5)); - assert!(*owner_drop_called.borrow()); -} From 7129cc03327a0a3f260bcf5bb8a7dcd241e23dbf Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 14 Mar 2025 18:16:09 -0400 Subject: [PATCH 101/110] cut dropck to-do comment (I feel relatively certain it is not a problem, and even if it is I don't think there's much more research to be done) --- src/pair.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index 22141ae..37bde54 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -500,11 +500,6 @@ impl + ?Sized> Pair { // drop of the Pair. As far as I know, this is the only concern surrounding // dropck not understanding the semantics of Pair, and cannot cause unsoundness // for the reasons described above. -// -// TODO(ichen): Dig into the above and determine more concretely whether or not -// this could be a problem, even theoretically. This may require having more -// formally defined language semantics first, like a memory model and more -// general information about what unsafe code can and cannot assume. impl Drop for Pair { fn drop(&mut self) { // Drop the dependent `Box<>::Dependent>` From b085c342635c3b11e0bd0b67080151fa25f2ea82 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Fri, 14 Mar 2025 18:53:44 -0400 Subject: [PATCH 102/110] moved constructor documentation to a dedicated section in Pair's docs --- src/pair.rs | 116 ++++++++++++++++++++-------------------------------- 1 file changed, 44 insertions(+), 72 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index 37bde54..d7a40bb 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -18,6 +18,34 @@ use crate::{HasDependent, Owner, drop_guard::DropGuard}; /// Conceptually, the pair itself has ownership over the owner `O`, the owner is /// immutably borrowed by the dependent for the lifetime of the pair, and the /// dependent is owned by the pair and valid for the pair's lifetime. +/// +/// # Constructors +/// +/// There are many different constructors for `Pair`, each serving a different +/// use case. There are three relevant factors to consider when deciding which +/// constructor to use: +/// +/// 1. Can [`make_dependent`](Owner::make_dependent) fail (return [`Err`])? +/// 2. Does `make_dependent` require additional context? +/// 3. Is your owner already stored in a [`Box`]? +/// +/// The simplest constructor, which you should use if you answered "no" to all +/// of the above questions, is [`Pair::new`]. It takes an `O: Owner`, and gives +/// you a `Pair` - doesn't get much easier than that! +/// +/// If your `make_dependent` can fail (meaning [`Owner::Error`] is not +/// [`Infallible`]), you should use one of the `try_*` constructors. +/// +/// If your `make_dependent` requires additional context (meaning +/// [`Owner::Context`] is not [`()`](prim@unit)), you should use one of the +/// `*_with_context` constructors. +/// +/// If your owner is already stored in a `Box`, you should use one of the +/// `*_from_box` constructors. +/// +/// Every combination of these is supported, up to the most powerful (and least +/// ergonomic) [`Pair::try_new_from_box_with_context`]. You should use the +/// simplest constructor you can for your implementation of `Owner`. pub struct Pair { // Derived from a Box // Immutably borrowed by `self.dependent` from construction until drop @@ -56,14 +84,8 @@ impl Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// - /// If you already have a [`Box`]ed owner, consider - /// [`Pair::try_new_from_box_with_context`] to avoid redundant reallocation. - /// - /// If you don't need to provide any context, consider the convenience - /// constructor [`Pair::try_new`], which doesn't require a context. - /// - /// If this construction can't fail, consider the convenience constructor - /// [`Pair::new_with_context`], which returns `Self` directly. + /// See the "Constructors" section in the documentation of [`Pair`] for + /// information on the differences between constructors. /// /// # Errors /// If [`::make_dependent`](Owner::make_dependent) returns an @@ -79,15 +101,8 @@ impl Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// - /// If you have an unboxed `O` and only box it for this function, consider - /// the convenience constructor [`Pair::try_new_with_context`], which boxes - /// the owner for you. - /// - /// If you don't need to provide any context, consider the convenience - /// constructor [`Pair::try_new_from_box`], which doesn't require a context. - /// - /// If this construction can't fail, consider the convenience constructor - /// [`Pair::new_from_box_with_context`], which returns `Self` directly. + /// See the "Constructors" section in the documentation of [`Pair`] for + /// information on the differences between constructors. /// /// # Errors /// If [`::make_dependent`](Owner::make_dependent) returns an @@ -362,15 +377,8 @@ impl Owner = (), Error = Infallible> + ?Sized> Pair Self where O: Sized, @@ -381,16 +389,8 @@ impl Owner = (), Error = Infallible> + ?Sized> Pair) -> Self { Self::new_from_box_with_context(owner, ()) } @@ -400,15 +400,8 @@ impl Owner = ()> + ?Sized> Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// - /// If you already have a [`Box`]ed owner, consider - /// [`Pair::try_new_from_box`] to avoid redundant reallocation. - /// - /// If you need to provide some additional arguments/context to this - /// constructor, consider [`Pair::try_new_with_context`], which allows - /// passing in additional data. - /// - /// If this construction can't fail, consider the convenience constructor - /// [`Pair::new`], which returns `Self` directly. + /// See the "Constructors" section in the documentation of [`Pair`] for + /// information on the differences between constructors. /// /// # Errors /// If [`::make_dependent`](Owner::make_dependent) returns an @@ -423,16 +416,8 @@ impl Owner = ()> + ?Sized> Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// - /// If you have an unboxed `O` and only box it for this function, consider - /// the convenience constructor [`Pair::try_new`], which boxes the owner for - /// you. - /// - /// If you need to provide some additional arguments/context to this - /// constructor, consider [`Pair::try_new_from_box_with_context`], which - /// allows passing in additional data. - /// - /// If this construction can't fail, consider the convenience constructor - /// [`Pair::new_from_box`], which returns `Self` directly. + /// See the "Constructors" section in the documentation of [`Pair`] for + /// information on the differences between constructors. /// /// # Errors /// If [`::make_dependent`](Owner::make_dependent) returns an @@ -446,14 +431,8 @@ impl + ?Sized> Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// - /// If you already have a [`Box`]ed owner, consider - /// [`Pair::new_from_box_with_context`] to avoid redundant reallocation. - /// - /// If you don't need to provide any context, consider the convenience - /// constructor [`Pair::new`], which doesn't require a context. - /// - /// If this construction can fail, consider [`Pair::try_new_with_context`], - /// which returns a [`Result`]. + /// See the "Constructors" section in the documentation of [`Pair`] for + /// information on the differences between constructors. pub fn new_with_context(owner: O, context: O::Context<'_>) -> Self where O: Sized, @@ -465,15 +444,8 @@ impl + ?Sized> Pair { /// Constructs a new [`Pair`] with the given [`Owner`]. The dependent will /// be computed through [`Owner::make_dependent`] during this construction. /// - /// If you have an unboxed `O` and only box it for this function, consider - /// the convenience constructor [`Pair::new_with_context`], which boxes the - /// owner for you. - /// - /// If you don't need to provide any context, consider the convenience - /// constructor [`Pair::new_from_box`], which doesn't require a context. - /// - /// If this construction can fail, consider - /// [`Pair::try_new_from_box_with_context`], which returns a [`Result`]. + /// See the "Constructors" section in the documentation of [`Pair`] for + /// information on the differences between constructors. pub fn new_from_box_with_context(owner: Box, context: O::Context<'_>) -> Self { let Ok(pair) = Self::try_new_from_box_with_context(owner, context); pair From 276a4a0a176b4075470d111b336e49a1c6808446 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 15 Mar 2025 10:18:17 -0400 Subject: [PATCH 103/110] fixed grammatical error --- src/pair.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pair.rs b/src/pair.rs index d7a40bb..89eaa08 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -206,7 +206,7 @@ impl Pair { // The call to `Box::new(..)` didn't panic - disarm our drop guard core::mem::forget(panic_drop_guard); - // Type-erase dependent so it's inexpressible self-referential lifetime + // Type-erase dependent so its inexpressible self-referential lifetime // goes away (we know that it's borrowing self.owner immutably from // construction (now) until drop) let dependent: NonNull<>::Dependent> = non_null_from_box(dependent); From c572b43ef3b842744f1f3a8ee36cde1f12150d73 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 15 Mar 2025 10:57:52 -0400 Subject: [PATCH 104/110] renamed get_owner(..) to just owner(..) --- README.md | 2 +- src/pair.rs | 4 ++-- tests/alternative_ctors.rs | 12 ++++++------ tests/basic_usage.rs | 26 +++++++++++++------------- tests/concurrent_loom.rs | 10 +++++----- tests/concurrent_std.rs | 10 +++++----- tests/default.rs | 2 +- tests/dyn_trait_owner.rs | 2 +- tests/interior_mutability.rs | 6 +++--- tests/nested_pair.rs | 4 ++-- tests/unsized_owner.rs | 2 +- tests/zst.rs | 12 ++++++------ 12 files changed, 46 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index eeac683..436a540 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ fn main() { }); // You can obtain a reference to the owner via a reference to the pair - let owner: &MyBuffer = pair.get_owner(); + let owner: &MyBuffer = pair.owner(); assert_eq!(owner.data, "this is an example"); // You can access a reference to the dependent via a reference to the pair, diff --git a/src/pair.rs b/src/pair.rs index 89eaa08..89bef99 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -220,7 +220,7 @@ impl Pair { } /// Returns a reference to the owner. - pub fn get_owner(&self) -> &O { + pub fn owner(&self) -> &O { // SAFETY: `self.owner` was originally converted from a valid Box, and // inherited the alignment and validity guarantees of Box - and neither // our code nor any of our exposed APIs could have invalidated those @@ -561,7 +561,7 @@ where fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.with_dependent(|dependent| { f.debug_struct("Pair") - .field("owner", &self.get_owner()) + .field("owner", &self.owner()) .field("dependent", dependent) .finish() }) diff --git a/tests/alternative_ctors.rs b/tests/alternative_ctors.rs index 09b8f8f..b45d2b1 100644 --- a/tests/alternative_ctors.rs +++ b/tests/alternative_ctors.rs @@ -32,7 +32,7 @@ impl Owner for BuffFallible { #[test] fn fallible() { let pair = Pair::try_new(BuffFallible(String::from("This is a test of pair."))).unwrap(); - assert_eq!(pair.get_owner().0, "This is a test of pair."); + assert_eq!(pair.owner().0, "This is a test of pair."); let (buff, err) = Pair::try_new(BuffFallible(String::from(" "))).unwrap_err(); assert_eq!(buff.0, " "); @@ -42,7 +42,7 @@ fn fallible() { "This is a test of pair.", )))) .unwrap(); - assert_eq!(pair.get_owner().0, "This is a test of pair."); + assert_eq!(pair.owner().0, "This is a test of pair."); let (buff, err) = Pair::try_new_from_box(Box::new(BuffFallible(String::from(" ")))).unwrap_err(); @@ -72,13 +72,13 @@ impl Owner for BuffWithContext { #[test] fn with_context() { let pair = Pair::new_with_context(BuffWithContext(String::from("foo, bar, bat, baz")), ", "); - assert_eq!(pair.get_owner().0, "foo, bar, bat, baz"); + assert_eq!(pair.owner().0, "foo, bar, bat, baz"); let pair = Pair::new_from_box_with_context( Box::new(BuffWithContext(String::from("foo, bar, bat, baz"))), ", ", ); - assert_eq!(pair.get_owner().0, "foo, bar, bat, baz"); + assert_eq!(pair.owner().0, "foo, bar, bat, baz"); } #[derive(Debug)] @@ -116,7 +116,7 @@ fn fallible_with_context() { ", ", ) .unwrap(); - assert_eq!(pair.get_owner().0, "foo, bar, bat, baz"); + assert_eq!(pair.owner().0, "foo, bar, bat, baz"); let (buff, err) = Pair::try_new_with_context( BuffFallibleWithContext(String::from("This is a test of pair.")), @@ -134,7 +134,7 @@ fn fallible_with_context() { ", ", ) .unwrap(); - assert_eq!(pair.get_owner().0, "foo, bar, bat, baz"); + assert_eq!(pair.owner().0, "foo, bar, bat, baz"); let (buff, err) = Pair::try_new_from_box_with_context( Box::new(BuffFallibleWithContext(String::from( diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs index 09e15e7..72cdb2a 100644 --- a/tests/basic_usage.rs +++ b/tests/basic_usage.rs @@ -26,7 +26,7 @@ impl Owner for Buff { #[test] fn basic_usage() { let mut pair = Pair::new(Buff(String::from("This is a test of pair."))); - let owner: &Buff = pair.get_owner(); + let owner: &Buff = pair.owner(); let dep: &Vec<&str> = pair.with_dependent(|dep| dep); assert_eq!(owner.0, "This is a test of pair."); @@ -61,34 +61,34 @@ fn basic_api_stress_test() { // Let's just do a bunch of the basic API functions interlaced together and // see what MIRI thinks let mut pair = Pair::new(Buff(String::from("This is a test of pair."))); - let owner1 = pair.get_owner(); - let owner2 = pair.get_owner(); - let owner3 = pair.get_owner(); + let owner1 = pair.owner(); + let owner2 = pair.owner(); + let owner3 = pair.owner(); let dep1 = pair.with_dependent(|dep| dep); - let owner4 = pair.get_owner(); + let owner4 = pair.owner(); let dep2 = pair.with_dependent(|dep| dep); println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{dep1:?}{dep2:?}"); pair.with_dependent_mut(|dep| dep.push("hey")); - let owner1 = pair.get_owner(); + let owner1 = pair.owner(); let dep1 = pair.with_dependent(|dep| dep); - let owner2 = pair.get_owner(); + let owner2 = pair.owner(); let dep2 = pair.with_dependent(|dep| dep); println!("{owner1:?}{owner2:?}{dep1:?}{dep2:?}"); pair.with_dependent_mut(|dep| dep.push("what's up")); pair.with_dependent_mut(|dep| dep.push("hello")); - let owner1 = pair.get_owner(); - let owner2 = pair.get_owner(); + let owner1 = pair.owner(); + let owner2 = pair.owner(); println!("{owner1:?}{owner2:?}"); #[expect( clippy::redundant_closure_call, reason = "I'm just doing weird stuff for funsies and testing" )] let new_pair = (|x| x)(std::convert::identity(pair)); - let owner1 = new_pair.get_owner(); - let owner2 = new_pair.get_owner(); - let owner3 = new_pair.get_owner(); + let owner1 = new_pair.owner(); + let owner2 = new_pair.owner(); + let owner3 = new_pair.owner(); let dep1 = new_pair.with_dependent(|dep| dep); - let owner4 = new_pair.get_owner(); + let owner4 = new_pair.owner(); let dep2 = new_pair.with_dependent(|dep| dep); println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{dep1:?}{dep2:?}"); } diff --git a/tests/concurrent_loom.rs b/tests/concurrent_loom.rs index ed5784d..d5166f3 100644 --- a/tests/concurrent_loom.rs +++ b/tests/concurrent_loom.rs @@ -44,7 +44,7 @@ fn pair_ownership_transfer_loom_nomiri() { let pair = Pair::new(Buff(String::from("this is a test"))); let t1 = loom::thread::spawn(move || { - assert_eq!(pair.get_owner().0, "this is a test"); + assert_eq!(pair.owner().0, "this is a test"); assert_eq!( pair.with_dependent(|parsed| parsed).tokens, ["this", "is", "a", "test"] @@ -55,7 +55,7 @@ fn pair_ownership_transfer_loom_nomiri() { let t2 = loom::thread::spawn(move || { let received_pair = rx.recv().unwrap(); - assert_eq!(received_pair.get_owner().0, "this is a test"); + assert_eq!(received_pair.owner().0, "this is a test"); assert_eq!( received_pair.with_dependent(|parsed| parsed).tokens, ["this", "is", "a", "test"] @@ -67,7 +67,7 @@ fn pair_ownership_transfer_loom_nomiri() { t1.join().unwrap(); let received_pair = t2.join().unwrap(); - assert_eq!(received_pair.get_owner().0, "this is a test"); + assert_eq!(received_pair.owner().0, "this is a test"); assert_eq!( received_pair.with_dependent(|parsed| parsed).tokens, ["this", "is", "a", "test"] @@ -83,7 +83,7 @@ fn pair_arc_sharing_loom_nomiri() { let pair1 = Arc::clone(&pair); let t1 = loom::thread::spawn(move || { - assert_eq!(pair1.get_owner().0, "arc sharing test"); + assert_eq!(pair1.owner().0, "arc sharing test"); assert_eq!( pair1.with_dependent(|parsed| parsed).tokens, ["arc", "sharing", "test"] @@ -92,7 +92,7 @@ fn pair_arc_sharing_loom_nomiri() { let pair2 = Arc::clone(&pair); let t2 = loom::thread::spawn(move || { - assert_eq!(pair2.get_owner().0, "arc sharing test"); + assert_eq!(pair2.owner().0, "arc sharing test"); assert_eq!( pair2.with_dependent(|parsed| parsed).tokens, ["arc", "sharing", "test"] diff --git a/tests/concurrent_std.rs b/tests/concurrent_std.rs index 33982b1..566b3ab 100644 --- a/tests/concurrent_std.rs +++ b/tests/concurrent_std.rs @@ -43,7 +43,7 @@ fn pair_ownership_transfer() { let pair = Pair::new(Buff(String::from("this is a test"))); let t1 = std::thread::spawn(move || { - assert_eq!(pair.get_owner().0, "this is a test"); + assert_eq!(pair.owner().0, "this is a test"); assert_eq!( pair.with_dependent(|parsed| parsed).tokens, ["this", "is", "a", "test"] @@ -54,7 +54,7 @@ fn pair_ownership_transfer() { let t2 = std::thread::spawn(move || { let received_pair = rx.recv().unwrap(); - assert_eq!(received_pair.get_owner().0, "this is a test"); + assert_eq!(received_pair.owner().0, "this is a test"); assert_eq!( received_pair.with_dependent(|parsed| parsed).tokens, ["this", "is", "a", "test"] @@ -66,7 +66,7 @@ fn pair_ownership_transfer() { t1.join().unwrap(); let received_pair = t2.join().unwrap(); - assert_eq!(received_pair.get_owner().0, "this is a test"); + assert_eq!(received_pair.owner().0, "this is a test"); assert_eq!( received_pair.with_dependent(|parsed| parsed).tokens, ["this", "is", "a", "test"] @@ -80,7 +80,7 @@ fn pair_arc_sharing() { let pair1 = Arc::clone(&pair); let t1 = std::thread::spawn(move || { - assert_eq!(pair1.get_owner().0, "arc sharing test"); + assert_eq!(pair1.owner().0, "arc sharing test"); assert_eq!( pair1.with_dependent(|parsed| parsed).tokens, ["arc", "sharing", "test"] @@ -89,7 +89,7 @@ fn pair_arc_sharing() { let pair2 = Arc::clone(&pair); let t2 = std::thread::spawn(move || { - assert_eq!(pair2.get_owner().0, "arc sharing test"); + assert_eq!(pair2.owner().0, "arc sharing test"); assert_eq!( pair2.with_dependent(|parsed| parsed).tokens, ["arc", "sharing", "test"] diff --git a/tests/default.rs b/tests/default.rs index 128f7b8..c00fd84 100644 --- a/tests/default.rs +++ b/tests/default.rs @@ -25,7 +25,7 @@ impl Owner for DefaultOwner { fn test_default() { let pair: Pair> = Pair::default(); - assert_eq!(pair.get_owner().0, T::default()); + assert_eq!(pair.owner().0, T::default()); pair.with_dependent(|dep| { assert_eq!(dep, &&T::default()); }); diff --git a/tests/dyn_trait_owner.rs b/tests/dyn_trait_owner.rs index c77abc2..b6c652e 100644 --- a/tests/dyn_trait_owner.rs +++ b/tests/dyn_trait_owner.rs @@ -34,7 +34,7 @@ impl Owner for dyn MyTrait { #[test] fn dyn_trait_owner() { let pair = Pair::new_from_box(Box::new(MyConcrete(69)) as Box); - let owner: &dyn MyTrait = pair.get_owner(); + let owner: &dyn MyTrait = pair.owner(); let dep: &i32 = pair.with_dependent(|dep| dep); assert_eq!(owner.get(), &69); diff --git a/tests/interior_mutability.rs b/tests/interior_mutability.rs index ba5c0a5..eac0699 100644 --- a/tests/interior_mutability.rs +++ b/tests/interior_mutability.rs @@ -32,7 +32,7 @@ fn test_interior_mutable_owner() { }); // Mutate the owner's value through the RefCell - *pair.get_owner().value.borrow_mut() = 100; + *pair.owner().value.borrow_mut() = 100; // Verify the change is visible to the dependent assert_eq!(*pair.with_dependent(|dep| dep).borrow(), 100); @@ -41,7 +41,7 @@ fn test_interior_mutable_owner() { *pair.with_dependent(|dep| dep).borrow_mut() = 210; // Verify the change is visible to the owner - assert_eq!(*pair.get_owner().value.borrow(), 210); + assert_eq!(*pair.owner().value.borrow(), 210); } // Test with interior-mutable dependent @@ -120,7 +120,7 @@ fn test_both_interior_mutable() { }); // Mutate the owner - *pair.get_owner().value.borrow_mut() = 100; + *pair.owner().value.borrow_mut() = 100; assert_eq!(*pair.with_dependent(|dep| dep).owner_ref.borrow(), 100); assert_eq!(pair.with_dependent(|dep| dep).local_value.get(), 42); diff --git a/tests/nested_pair.rs b/tests/nested_pair.rs index 328227c..ae8e4c0 100644 --- a/tests/nested_pair.rs +++ b/tests/nested_pair.rs @@ -69,12 +69,12 @@ fn test_pair_owning_pair() { assert_eq!(*outer_dep.value_ref, 100); // Access the inner pair's owner and dependent - assert_eq!(outer_dep.inner_pair_ref.get_owner().0, 42); + assert_eq!(outer_dep.inner_pair_ref.owner().0, 42); assert_eq!(outer_dep.inner_pair_ref.with_dependent(|dep| dep), &&42); }); let PairOwner { value, inner_pair } = outer_pair.into_owner(); assert_eq!(value, 100); - assert_eq!(inner_pair.get_owner().0, 42); + assert_eq!(inner_pair.owner().0, 42); assert_eq!(inner_pair.with_dependent(|dep| dep), &&42); } diff --git a/tests/unsized_owner.rs b/tests/unsized_owner.rs index fba2062..d365e1e 100644 --- a/tests/unsized_owner.rs +++ b/tests/unsized_owner.rs @@ -34,7 +34,7 @@ impl Owner for Buff<[u8]> { #[test] fn unsized_owner() { let mut pair = Pair::new_from_box(Buff::new([2, 69, 42, 5, 6, 7, 8])); - let owner: &Buff<[u8]> = pair.get_owner(); + let owner: &Buff<[u8]> = pair.owner(); let dep: &(&[u8], usize) = pair.with_dependent(|dep| dep); assert_eq!(owner.0, [2, 69, 42, 5, 6, 7, 8]); diff --git a/tests/zst.rs b/tests/zst.rs index 6e1a4ab..9b02dfe 100644 --- a/tests/zst.rs +++ b/tests/zst.rs @@ -31,7 +31,7 @@ impl Owner for OneZstOwner { fn test_1zst_owner() { let pair = Pair::new(OneZstOwner); - assert_eq!(size_of_val(pair.get_owner()), 0); + assert_eq!(size_of_val(pair.owner()), 0); assert_eq!(*pair.with_dependent(|dep| dep), NonZstDependent(42)); } @@ -63,7 +63,7 @@ impl Owner for Non1ZstOwner { fn test_1zst_dependent() { let pair = Pair::new(Non1ZstOwner(123)); - assert_eq!(*pair.get_owner(), Non1ZstOwner(123)); + assert_eq!(*pair.owner(), Non1ZstOwner(123)); assert_eq!(size_of_val(pair.with_dependent(|dep| dep)), 0); } @@ -90,7 +90,7 @@ impl Owner for Both1ZstOwner { #[test] fn test_both_1zst() { let pair = Pair::new(Both1ZstOwner); - assert_eq!(size_of_val(pair.get_owner()), 0); + assert_eq!(size_of_val(pair.owner()), 0); assert_eq!(size_of_val(pair.with_dependent(|dep| dep)), 0); } @@ -121,7 +121,7 @@ impl Owner for BigZstOwner { fn test_bigzst_owner() { let pair = Pair::new(BigZstOwner([])); - assert_eq!(size_of_val(pair.get_owner()), 0); + assert_eq!(size_of_val(pair.owner()), 0); assert_eq!(*pair.with_dependent(|dep| dep), NonZstDependent(23)); } @@ -153,7 +153,7 @@ impl Owner for NonBigZstOwner { fn test_bigzst_dependent() { let pair = Pair::new(NonBigZstOwner(789)); - assert_eq!(*pair.get_owner(), NonBigZstOwner(789)); + assert_eq!(*pair.owner(), NonBigZstOwner(789)); assert_eq!(size_of_val(pair.with_dependent(|dep| dep)), 0); } @@ -180,6 +180,6 @@ impl Owner for BothBigZstOwner { #[test] fn test_both_bigzst() { let pair = Pair::new(BothBigZstOwner([])); - assert_eq!(size_of_val(pair.get_owner()), 0); + assert_eq!(size_of_val(pair.owner()), 0); assert_eq!(size_of_val(pair.with_dependent(|dep| dep)), 0); } From b67b89e1bffe5b8e58f38412950b56c7c23a5f8a Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 15 Mar 2025 13:53:25 -0400 Subject: [PATCH 105/110] added with_both[_mut] methods to Pair --- src/pair.rs | 47 +++++++++++++++++++++++++++++++++++++++++++- tests/basic_usage.rs | 32 +++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index 89bef99..f7f1b06 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -278,6 +278,51 @@ impl Pair { where F: for<'any> FnOnce(&'self_borrow mut >::Dependent) -> T, { + self.with_both_mut(|_, dependent| f(dependent)) + } + + /// Calls the given closure, providing shared access to both the owner and + /// the dependent, and returns the value computed by the closure. + /// + /// The closure must be able to work with a + /// [`Dependent`](HasDependent::Dependent) with any arbitrary lifetime that + /// lives at least as long as the borrow of `self`. See the documentation of + /// [`with_dependent`](Pair::with_dependent) for more information on this. + pub fn with_both<'self_borrow, F, T>(&'self_borrow self, f: F) -> T + where + F: for<'any> FnOnce( + &'self_borrow O, + &'self_borrow >::Dependent, + ) -> T, + { + self.with_dependent(|dependent| f(self.owner(), dependent)) + } + + /// Calls the given closure, providing shared access to the owner and + /// exclusive access to the dependent, and returns the value computed by the + /// closure. + /// + /// The closure must be able to work with a + /// [`Dependent`](HasDependent::Dependent) with any arbitrary lifetime that + /// lives at least as long as the borrow of `self`. See the documentation of + /// [`with_dependent_mut`](Pair::with_dependent_mut) for more information on + /// this. + pub fn with_both_mut<'self_borrow, F, T>(&'self_borrow mut self, f: F) -> T + where + F: for<'any> FnOnce( + &'self_borrow O, + &'self_borrow mut >::Dependent, + ) -> T, + { + // SAFETY: `self.owner` was originally converted from a valid Box, and + // inherited the alignment and validity guarantees of Box - and neither + // our code nor any of our exposed APIs could have invalidated those + // since construction. Additionally, the value behind the pointer is + // currently in a shared borrow state (no exclusive borrows, no other + // code assuming unique ownership), and will be until the Pair is + // dropped. Here, we only add another shared borrow. + let owner: &O = unsafe { self.owner.as_ref() }; + // SAFETY: `self.dependent` was originally converted from a valid // Box<>::Dependent>, and type-erased to a // NonNull<()>. As such, it inherited the alignment and validity @@ -293,7 +338,7 @@ impl Pair { .as_mut() }; - f(dependent) + f(owner, dependent) } /// Consumes the [`Pair`], dropping the dependent and returning the owner. diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs index 72cdb2a..aedf089 100644 --- a/tests/basic_usage.rs +++ b/tests/basic_usage.rs @@ -4,7 +4,7 @@ use std::convert::Infallible; use pair::{HasDependent, Owner, Pair}; -#[derive(Debug)] +#[derive(Debug, PartialEq)] struct Buff(String); impl<'owner> HasDependent<'owner> for Buff { @@ -35,8 +35,11 @@ fn basic_usage() { pair.with_dependent_mut(|dep| dep.push("hi")); pair.with_dependent_mut(|dep| dep.push("hey")); assert_eq!( - pair.with_dependent(|d| d), - &["This", "is", "a", "test", "of", "pair.", "hi", "hey"] + pair.with_both(|owner, dep| (owner, dep)), + ( + &Buff(String::from("This is a test of pair.")), + &vec!["This", "is", "a", "test", "of", "pair.", "hi", "hey"] + ) ); pair.with_dependent_mut(|dep| dep.sort_unstable()); assert_eq!( @@ -44,8 +47,10 @@ fn basic_usage() { &["This", "a", "hey", "hi", "is", "of", "pair.", "test"] ); - #[expect(clippy::redundant_closure_for_method_calls, reason = "false positive")] - let last_word = pair.with_dependent_mut(|dep| dep.pop()); + let last_word = pair.with_both_mut(|owner, dep| { + assert_eq!(owner, &Buff(String::from("This is a test of pair."))); + dep.pop() + }); assert_eq!(last_word, Some("test")); assert_eq!( pair.with_dependent(|d| d), @@ -66,15 +71,19 @@ fn basic_api_stress_test() { let owner3 = pair.owner(); let dep1 = pair.with_dependent(|dep| dep); let owner4 = pair.owner(); - let dep2 = pair.with_dependent(|dep| dep); - println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{dep1:?}{dep2:?}"); + let (owner5, dep2) = pair.with_both(|owner, dep| (owner, dep)); + println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{owner5:?}{dep1:?}{dep2:?}"); pair.with_dependent_mut(|dep| dep.push("hey")); let owner1 = pair.owner(); let dep1 = pair.with_dependent(|dep| dep); let owner2 = pair.owner(); let dep2 = pair.with_dependent(|dep| dep); println!("{owner1:?}{owner2:?}{dep1:?}{dep2:?}"); - pair.with_dependent_mut(|dep| dep.push("what's up")); + pair.with_both_mut(|owner, dep| { + if owner.0.contains("hey") { + dep.push("what's up"); + } + }); pair.with_dependent_mut(|dep| dep.push("hello")); let owner1 = pair.owner(); let owner2 = pair.owner(); @@ -89,6 +98,11 @@ fn basic_api_stress_test() { let owner3 = new_pair.owner(); let dep1 = new_pair.with_dependent(|dep| dep); let owner4 = new_pair.owner(); + let (owner5, dep2) = new_pair.with_both(|owner, dep| (owner, dep)); + println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{owner5:?}{dep1:?}{dep2:?}"); + let owner1 = new_pair.owner(); + let dep1 = new_pair.with_dependent(|dep| dep); + let owner2 = new_pair.owner(); let dep2 = new_pair.with_dependent(|dep| dep); - println!("{owner1:?}{owner2:?}{owner3:?}{owner4:?}{dep1:?}{dep2:?}"); + println!("{owner1:?}{owner2:?}{dep1:?}{dep2:?}"); } From a6a9d3dd57abd6d8f18833af90be5efb4547917c Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 15 Mar 2025 13:58:29 -0400 Subject: [PATCH 106/110] updated CHANGELOG.md to specify 0.2.0 instead of 1.0.0 --- CHANGELOG.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b8c9a..baf0038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,13 @@ -# Major Version 1 +# Major Version 0 -## v1.0.0 +## v0.2.0 -Implemented and stabilized the core functionality of this crate: +Implemented the core functionality of this crate: - Defined `Pair` struct - Defined `Owner` and `HasDependent` traits -# Major Version 0 - ## v0.1.0 Initial (empty) release. From 7c53ba35e9f7dce6b36916b2e294b96635404e63 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 15 Mar 2025 14:10:20 -0400 Subject: [PATCH 107/110] cleaned up implementation of with_both_mut slightly --- src/pair.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/pair.rs b/src/pair.rs index f7f1b06..d8ab77a 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -314,14 +314,7 @@ impl Pair { &'self_borrow mut >::Dependent, ) -> T, { - // SAFETY: `self.owner` was originally converted from a valid Box, and - // inherited the alignment and validity guarantees of Box - and neither - // our code nor any of our exposed APIs could have invalidated those - // since construction. Additionally, the value behind the pointer is - // currently in a shared borrow state (no exclusive borrows, no other - // code assuming unique ownership), and will be until the Pair is - // dropped. Here, we only add another shared borrow. - let owner: &O = unsafe { self.owner.as_ref() }; + let owner: &O = self.owner(); // SAFETY: `self.dependent` was originally converted from a valid // Box<>::Dependent>, and type-erased to a @@ -329,9 +322,9 @@ impl Pair { // guarantees of Box (for an >::Dependent) - and // neither our code nor any of our exposed APIs could have invalidated // those since construction. Additionally, because we have an exclusive - // reference to self, we know that the value behind the pointer is - // currently not borrowed at all, and can't be until our exclusive - // borrow of `self` expires. + // reference to self (and Pair::owner(..) doesn't borrow the dependent), + // we know that the value behind the pointer is currently not borrowed + // at all, and can't be until our exclusive borrow of `self` expires. let dependent: &mut >::Dependent = unsafe { self.dependent .cast::<>::Dependent>() From fa36691aca4d0b6ef365513117b51b7af76040b5 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 15 Mar 2025 19:21:03 -0400 Subject: [PATCH 108/110] added Dependent<'owner, O> type alias --- CHANGELOG.md | 1 + README.md | 7 +- src/lib.rs | 2 +- src/owner.rs | 6 +- src/pair.rs | 154 ++++++++---------- tests/alternative_ctors.rs | 11 +- tests/basic_usage.rs | 7 +- tests/compile_fails/extract_with_dep_mut.rs | 2 +- .../compile_fails/invariant_dep_extraction.rs | 2 +- .../keep_dep_after_into_owner.rs | 2 +- .../compile_fails/keep_dep_after_pair_drop.rs | 2 +- tests/compile_fails/not_send_dep.rs | 6 +- tests/compile_fails/not_send_owner.rs | 8 +- tests/compile_fails/not_sync_dep.rs | 6 +- tests/compile_fails/not_sync_owner.rs | 8 +- tests/compile_fails/pair_not_covariant.rs | 2 +- .../send_sync_miserable_failure.rs | 2 +- tests/concurrent_loom.rs | 7 +- tests/concurrent_std.rs | 7 +- tests/contravariance.rs | 7 +- tests/debug.rs | 12 +- tests/default.rs | 7 +- tests/drop.rs | 4 +- tests/dyn_trait_owner.rs | 7 +- tests/interior_mutability.rs | 17 +- tests/multiborrow.rs | 7 +- tests/nested_pair.rs | 12 +- tests/panic_safety.rs | 17 +- tests/unsized_owner.rs | 7 +- tests/zst.rs | 32 +--- 30 files changed, 143 insertions(+), 228 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index baf0038..51f33a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Implemented the core functionality of this crate: - Defined `Pair` struct - Defined `Owner` and `HasDependent` traits +- Defined `Dependent` type alias ## v0.1.0 diff --git a/README.md b/README.md index 436a540..a2dd0af 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ in time. A typical use case might look something like this: ```rust -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; // Let's say you have some buffer type that contains a string #[derive(Debug)] @@ -57,10 +57,7 @@ impl Owner for MyBuffer { type Context<'a> = (); // We don't need any extra args to `make_dependent` type Error = std::convert::Infallible; // Our example parsing can't fail - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, _: ()) -> Result, Self::Error> { Ok(parse(self)) } } diff --git a/src/lib.rs b/src/lib.rs index 60bd3ea..16c08ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,5 +13,5 @@ mod drop_guard; mod owner; mod pair; -pub use owner::{HasDependent, Owner}; +pub use owner::{Dependent, HasDependent, Owner}; pub use pair::Pair; diff --git a/src/owner.rs b/src/owner.rs index 7f08a97..5cd36a3 100644 --- a/src/owner.rs +++ b/src/owner.rs @@ -19,6 +19,10 @@ pub trait HasDependent<'owner, ForImpliedBound: Sealed = Bounds<&'owner Self>> { type Dependent; } +/// A type alias for the [`Dependent`](HasDependent::Dependent) of some +/// [`Owner`] with a specific lifetime `'owner`. +pub type Dependent<'owner, O> = >::Dependent; + #[expect( clippy::missing_errors_doc, reason = "failure modes are specific to the trait's implementation" @@ -58,7 +62,7 @@ pub trait Owner: for<'any> HasDependent<'any> { fn make_dependent<'owner>( &'owner self, context: Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error>; + ) -> Result, Self::Error>; } /// Used to prevent implementors of [`HasDependent`] from overriding the diff --git a/src/pair.rs b/src/pair.rs index d8ab77a..0b6394e 100644 --- a/src/pair.rs +++ b/src/pair.rs @@ -4,10 +4,9 @@ use core::{convert::Infallible, fmt::Debug, marker::PhantomData, mem::ManuallyDr use alloc::boxed::Box; -use crate::{HasDependent, Owner, drop_guard::DropGuard}; +use crate::{Dependent, Owner, drop_guard::DropGuard}; -/// A self-referential pair containing both some [`Owner`] and its -/// [`Dependent`](HasDependent::Dependent). +/// A self-referential pair containing both some [`Owner`] and its [`Dependent`]. /// /// The owner must be provided to construct a [`Pair`], and the dependent is /// immediately created (borrowing from the owner). Both are stored together in @@ -46,12 +45,14 @@ use crate::{HasDependent, Owner, drop_guard::DropGuard}; /// Every combination of these is supported, up to the most powerful (and least /// ergonomic) [`Pair::try_new_from_box_with_context`]. You should use the /// simplest constructor you can for your implementation of `Owner`. +/// +/// [`Dependent`]: crate::HasDependent::Dependent pub struct Pair { // Derived from a Box // Immutably borrowed by `self.dependent` from construction until drop owner: NonNull, - // Type-erased Box<>::Dependent> + // Type-erased Box> dependent: NonNull<()>, // Need invariance over O - if we were covariant or contravariant, two @@ -209,7 +210,7 @@ impl Pair { // Type-erase dependent so its inexpressible self-referential lifetime // goes away (we know that it's borrowing self.owner immutably from // construction (now) until drop) - let dependent: NonNull<>::Dependent> = non_null_from_box(dependent); + let dependent: NonNull> = non_null_from_box(dependent); let dependent: NonNull<()> = dependent.cast(); Ok(Self { @@ -234,33 +235,31 @@ impl Pair { /// Calls the given closure, providing shared access to the dependent, and /// returns the value computed by the closure. /// - /// The closure must be able to work with a - /// [`Dependent`](HasDependent::Dependent) with any arbitrary lifetime that - /// lives at least as long as the borrow of `self`. This is important - /// because the dependent may be invariant over its lifetime, and the - /// correct lifetime (lasting from the construction of `self` until drop) is - /// inexpressible. For dependent types covariant over their lifetime, the - /// closure may simply return the reference to the dependent, which may then - /// be used as if this function directly returned a reference. + /// The closure must be able to work with a [`Dependent`] with any arbitrary + /// lifetime that lives at least as long as the borrow of `self`. This is + /// important because the dependent may be invariant over its lifetime, and + /// the correct lifetime (lasting from the construction of `self` until + /// drop) is inexpressible. For dependent types covariant over their + /// lifetime, the closure may simply return the reference to the dependent, + /// which may then be used as if this function directly returned a + /// reference. + /// + /// [`Dependent`]: crate::HasDependent::Dependent pub fn with_dependent<'self_borrow, F, T>(&'self_borrow self, f: F) -> T where - F: for<'any> FnOnce(&'self_borrow >::Dependent) -> T, + F: for<'any> FnOnce(&'self_borrow Dependent<'_, O>) -> T, { // SAFETY: `self.dependent` was originally converted from a valid - // Box<>::Dependent>, and type-erased to a - // NonNull<()>. As such, it inherited the alignment and validity - // guarantees of Box (for an >::Dependent) - and - // neither our code nor any of our exposed APIs could have invalidated - // those since construction. Additionally, because we have a shared - // reference to self, we know that the value behind the pointer is - // currently either not borrowed at all, or in a shared borrow state - // (no exclusive borrows, no other code assuming unique ownership). - // Here, we only either create the first shared borrow, or add another. - let dependent: &>::Dependent = unsafe { - self.dependent - .cast::<>::Dependent>() - .as_ref() - }; + // Box>, and type-erased to a NonNull<()>. As such, it + // inherited the alignment and validity guarantees of Box (for a + // Dependent<'_, O>) - and neither our code nor any of our exposed APIs + // could have invalidated those since construction. Additionally, + // because we have a shared reference to self, we know that the value + // behind the pointer is currently either not borrowed at all, or in a + // shared borrow state (no exclusive borrows, no other code assuming + // unique ownership). Here, we only either create the first shared + // borrow, or add another. + let dependent = unsafe { self.dependent.cast::>().as_ref() }; f(dependent) } @@ -268,15 +267,16 @@ impl Pair { /// Calls the given closure, providing exclusive access to the dependent, /// and returns the value computed by the closure. /// - /// The closure must be able to work with a - /// [`Dependent`](HasDependent::Dependent) with any arbitrary lifetime that - /// lives at least as long as the borrow of `self`. This is important - /// because mutable references are invariant over their type `T`, and the - /// exact T here (a `Dependent` with a very specific lifetime lasting from - /// the construction of `self` until drop) is inexpressible. + /// The closure must be able to work with a [`Dependent`] with any arbitrary + /// lifetime that lives at least as long as the borrow of `self`. This is + /// important because mutable references are invariant over their type `T`, + /// and the exact T here (a `Dependent` with a very specific lifetime + /// lasting from the construction of `self` until drop) is inexpressible. + /// + /// [`Dependent`]: crate::HasDependent::Dependent pub fn with_dependent_mut<'self_borrow, F, T>(&'self_borrow mut self, f: F) -> T where - F: for<'any> FnOnce(&'self_borrow mut >::Dependent) -> T, + F: for<'any> FnOnce(&'self_borrow mut Dependent<'_, O>) -> T, { self.with_both_mut(|_, dependent| f(dependent)) } @@ -284,16 +284,15 @@ impl Pair { /// Calls the given closure, providing shared access to both the owner and /// the dependent, and returns the value computed by the closure. /// - /// The closure must be able to work with a - /// [`Dependent`](HasDependent::Dependent) with any arbitrary lifetime that - /// lives at least as long as the borrow of `self`. See the documentation of - /// [`with_dependent`](Pair::with_dependent) for more information on this. + /// The closure must be able to work with a [`Dependent`] with any arbitrary + /// lifetime that lives at least as long as the borrow of `self`. See the + /// documentation of [`with_dependent`](Pair::with_dependent) for more + /// information on this. + /// + /// [`Dependent`]: crate::HasDependent::Dependent pub fn with_both<'self_borrow, F, T>(&'self_borrow self, f: F) -> T where - F: for<'any> FnOnce( - &'self_borrow O, - &'self_borrow >::Dependent, - ) -> T, + F: for<'any> FnOnce(&'self_borrow O, &'self_borrow Dependent<'_, O>) -> T, { self.with_dependent(|dependent| f(self.owner(), dependent)) } @@ -302,34 +301,28 @@ impl Pair { /// exclusive access to the dependent, and returns the value computed by the /// closure. /// - /// The closure must be able to work with a - /// [`Dependent`](HasDependent::Dependent) with any arbitrary lifetime that - /// lives at least as long as the borrow of `self`. See the documentation of - /// [`with_dependent_mut`](Pair::with_dependent_mut) for more information on - /// this. + /// The closure must be able to work with a [`Dependent`] with any arbitrary + /// lifetime that lives at least as long as the borrow of `self`. See the + /// documentation of [`with_dependent_mut`](Pair::with_dependent_mut) for + /// more information on this. + /// + /// [`Dependent`]: crate::HasDependent::Dependent pub fn with_both_mut<'self_borrow, F, T>(&'self_borrow mut self, f: F) -> T where - F: for<'any> FnOnce( - &'self_borrow O, - &'self_borrow mut >::Dependent, - ) -> T, + F: for<'any> FnOnce(&'self_borrow O, &'self_borrow mut Dependent<'_, O>) -> T, { let owner: &O = self.owner(); // SAFETY: `self.dependent` was originally converted from a valid - // Box<>::Dependent>, and type-erased to a - // NonNull<()>. As such, it inherited the alignment and validity - // guarantees of Box (for an >::Dependent) - and - // neither our code nor any of our exposed APIs could have invalidated - // those since construction. Additionally, because we have an exclusive - // reference to self (and Pair::owner(..) doesn't borrow the dependent), - // we know that the value behind the pointer is currently not borrowed - // at all, and can't be until our exclusive borrow of `self` expires. - let dependent: &mut >::Dependent = unsafe { - self.dependent - .cast::<>::Dependent>() - .as_mut() - }; + // Box>, and type-erased to a NonNull<()>. As such, it + // inherited the alignment and validity guarantees of Box (for a + // Dependent<'_, O>) - and neither our code nor any of our exposed APIs + // could have invalidated those since construction. Additionally, + // because we have an exclusive reference to self (and Pair::owner(..) + // doesn't borrow the dependent), we know that the value behind the + // pointer is currently not borrowed at all, and can't be until our + // exclusive borrow of `self` expires. + let dependent = unsafe { self.dependent.cast::>().as_mut() }; f(owner, dependent) } @@ -352,15 +345,9 @@ impl Pair { // SAFETY: `this.dependent` was originally created from a Box, and never // invalidated since then. Because we took ownership of `self`, we know // there are no outstanding borrows to the dependent. Therefore, - // reconstructing the original Box<>::Dependent> - // is okay. - let dependent: Box<>::Dependent> = unsafe { - Box::from_raw( - this.dependent - .cast::<>::Dependent>() - .as_ptr(), - ) - }; + // reconstructing the original Box> is okay. + let dependent: Box> = + unsafe { Box::from_raw(this.dependent.cast::>().as_ptr()) }; // We're about to drop the dependent - if it panics, we want to be able // to drop the boxed owner before unwinding the rest of the stack to @@ -512,19 +499,14 @@ impl + ?Sized> Pair { // for the reasons described above. impl Drop for Pair { fn drop(&mut self) { - // Drop the dependent `Box<>::Dependent>` + // Drop the dependent `Box>` // SAFETY: `self.dependent` was originally created from a Box, and never // invalidated since then. Because we are in drop, we know there are no // outstanding borrows to the dependent. Therefore, reconstructing the - // original Box<>::Dependent> is okay. - let dependent = unsafe { - Box::from_raw( - self.dependent - .cast::<>::Dependent>() - .as_ptr(), - ) - }; + // original Box> is okay. + let dependent = + unsafe { Box::from_raw(self.dependent.cast::>().as_ptr()) }; // We're about to drop the dependent - if it panics, we want to be able // to drop the boxed owner before unwinding the rest of the stack to @@ -576,7 +558,7 @@ impl Drop for Pair { unsafe impl Send for Pair where O: Send, - for<'any> >::Dependent: Send, + for<'any> Dependent<'any, O>: Send, { } @@ -588,13 +570,13 @@ where unsafe impl Sync for Pair where O: Sync, - for<'any> >::Dependent: Sync, + for<'any> Dependent<'any, O>: Sync, { } impl Debug for Pair where - for<'any> >::Dependent: Debug, + for<'any> Dependent<'any, O>: Debug, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.with_dependent(|dependent| { diff --git a/tests/alternative_ctors.rs b/tests/alternative_ctors.rs index b45d2b1..2b299c4 100644 --- a/tests/alternative_ctors.rs +++ b/tests/alternative_ctors.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; #[derive(Debug)] struct BuffFallible(String); @@ -15,10 +15,7 @@ impl Owner for BuffFallible { type Context<'a> = (); type Error = String; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { let parts: Vec<_> = self.0.split_whitespace().collect(); if parts.is_empty() { @@ -64,7 +61,7 @@ impl Owner for BuffWithContext { fn make_dependent( &self, context: Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { Ok(self.0.split(context).collect()) } } @@ -95,7 +92,7 @@ impl Owner for BuffFallibleWithContext { fn make_dependent( &self, context: Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { let parts: Vec<_> = self.0.split(context).collect(); if parts.len() > 1 { diff --git a/tests/basic_usage.rs b/tests/basic_usage.rs index aedf089..bf69a0d 100644 --- a/tests/basic_usage.rs +++ b/tests/basic_usage.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; #[derive(Debug, PartialEq)] struct Buff(String); @@ -15,10 +15,7 @@ impl Owner for Buff { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(self.0.split_whitespace().collect()) } } diff --git a/tests/compile_fails/extract_with_dep_mut.rs b/tests/compile_fails/extract_with_dep_mut.rs index 1c2143d..b8fe553 100644 --- a/tests/compile_fails/extract_with_dep_mut.rs +++ b/tests/compile_fails/extract_with_dep_mut.rs @@ -18,7 +18,7 @@ impl Owner for Buff { fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { Ok(self.0.split_whitespace().collect()) } } diff --git a/tests/compile_fails/invariant_dep_extraction.rs b/tests/compile_fails/invariant_dep_extraction.rs index 8ba976f..c0eba60 100644 --- a/tests/compile_fails/invariant_dep_extraction.rs +++ b/tests/compile_fails/invariant_dep_extraction.rs @@ -17,7 +17,7 @@ impl Owner for InvarOwner { fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { Ok(PhantomData) } } diff --git a/tests/compile_fails/keep_dep_after_into_owner.rs b/tests/compile_fails/keep_dep_after_into_owner.rs index a328f82..93cccc3 100644 --- a/tests/compile_fails/keep_dep_after_into_owner.rs +++ b/tests/compile_fails/keep_dep_after_into_owner.rs @@ -18,7 +18,7 @@ impl Owner for Buff { fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { Ok(self.0.split_whitespace().collect()) } } diff --git a/tests/compile_fails/keep_dep_after_pair_drop.rs b/tests/compile_fails/keep_dep_after_pair_drop.rs index e008751..0bb3a9a 100644 --- a/tests/compile_fails/keep_dep_after_pair_drop.rs +++ b/tests/compile_fails/keep_dep_after_pair_drop.rs @@ -18,7 +18,7 @@ impl Owner for Buff { fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { Ok(self.0.split_whitespace().collect()) } } diff --git a/tests/compile_fails/not_send_dep.rs b/tests/compile_fails/not_send_dep.rs index 616c1e0..072c8a7 100644 --- a/tests/compile_fails/not_send_dep.rs +++ b/tests/compile_fails/not_send_dep.rs @@ -2,7 +2,7 @@ extern crate pair; use std::{convert::Infallible, sync::MutexGuard}; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; struct MyOwner; struct NotSend(MutexGuard<'static, ()>); @@ -18,7 +18,7 @@ impl Owner for MyOwner { fn make_dependent<'owner>( &'owner self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { unimplemented!() } } @@ -28,7 +28,7 @@ fn check_sync() {} fn main() { check_send::(); - check_sync::<(MyOwner, ::Dependent)>(); + check_sync::<(MyOwner, Dependent<'_, MyOwner>)>(); check_send::>(); check_sync::>(); diff --git a/tests/compile_fails/not_send_owner.rs b/tests/compile_fails/not_send_owner.rs index 27705fc..a605712 100644 --- a/tests/compile_fails/not_send_owner.rs +++ b/tests/compile_fails/not_send_owner.rs @@ -2,7 +2,7 @@ extern crate pair; use std::{convert::Infallible, sync::MutexGuard}; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; struct NotSend(MutexGuard<'static, ()>); @@ -17,7 +17,7 @@ impl Owner for NotSend { fn make_dependent<'owner>( &'owner self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { unimplemented!() } } @@ -26,8 +26,8 @@ fn check_send() {} fn check_sync() {} fn main() { - check_send::<::Dependent>(); - check_sync::<(NotSend, ::Dependent)>(); + check_send::>(); + check_sync::<(NotSend, Dependent<'_, NotSend>)>(); check_send::>(); check_sync::>(); diff --git a/tests/compile_fails/not_sync_dep.rs b/tests/compile_fails/not_sync_dep.rs index 44c9d78..ec01b53 100644 --- a/tests/compile_fails/not_sync_dep.rs +++ b/tests/compile_fails/not_sync_dep.rs @@ -2,7 +2,7 @@ extern crate pair; use std::{cell::UnsafeCell, convert::Infallible}; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; struct MyOwner; struct NotSync(UnsafeCell<()>); @@ -18,7 +18,7 @@ impl Owner for MyOwner { fn make_dependent<'owner>( &'owner self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { unimplemented!() } } @@ -27,7 +27,7 @@ fn check_send() {} fn check_sync() {} fn main() { - check_send::<(MyOwner, ::Dependent)>(); + check_send::<(MyOwner, Dependent<'_, MyOwner>)>(); check_sync::(); check_send::>(); diff --git a/tests/compile_fails/not_sync_owner.rs b/tests/compile_fails/not_sync_owner.rs index 10969c9..75b0ef9 100644 --- a/tests/compile_fails/not_sync_owner.rs +++ b/tests/compile_fails/not_sync_owner.rs @@ -2,7 +2,7 @@ extern crate pair; use std::{cell::UnsafeCell, convert::Infallible}; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; struct NotSync(UnsafeCell<()>); @@ -17,7 +17,7 @@ impl Owner for NotSync { fn make_dependent<'owner>( &'owner self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { unimplemented!() } } @@ -26,8 +26,8 @@ fn check_send() {} fn check_sync() {} fn main() { - check_send::<(NotSync, ::Dependent)>(); - check_sync::<::Dependent>(); + check_send::<(NotSync, Dependent<'_, NotSync>)>(); + check_sync::>(); check_send::>(); check_sync::>(); diff --git a/tests/compile_fails/pair_not_covariant.rs b/tests/compile_fails/pair_not_covariant.rs index 4eb9557..19c5369 100644 --- a/tests/compile_fails/pair_not_covariant.rs +++ b/tests/compile_fails/pair_not_covariant.rs @@ -22,7 +22,7 @@ impl Owner for Foo<'_> { fn make_dependent( &self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { unimplemented!() } } diff --git a/tests/compile_fails/send_sync_miserable_failure.rs b/tests/compile_fails/send_sync_miserable_failure.rs index e6ec322..3f85080 100644 --- a/tests/compile_fails/send_sync_miserable_failure.rs +++ b/tests/compile_fails/send_sync_miserable_failure.rs @@ -17,7 +17,7 @@ impl Owner for NotSendOrSync { fn make_dependent<'owner>( &'owner self, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { unimplemented!() } } diff --git a/tests/concurrent_loom.rs b/tests/concurrent_loom.rs index d5166f3..58827cf 100644 --- a/tests/concurrent_loom.rs +++ b/tests/concurrent_loom.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex, RwLock, mpsc}; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; // A simple owner type for testing thread safety #[derive(Debug)] @@ -27,10 +27,7 @@ impl Owner for Buff { type Context<'a> = (); type Error = std::convert::Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(parse(self)) } } diff --git a/tests/concurrent_std.rs b/tests/concurrent_std.rs index 566b3ab..9a6019c 100644 --- a/tests/concurrent_std.rs +++ b/tests/concurrent_std.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex, RwLock, mpsc}; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; // A simple owner type for testing thread safety #[derive(Debug)] @@ -27,10 +27,7 @@ impl Owner for Buff { type Context<'a> = (); type Error = std::convert::Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(parse(self)) } } diff --git a/tests/contravariance.rs b/tests/contravariance.rs index 099ffc4..e3a7771 100644 --- a/tests/contravariance.rs +++ b/tests/contravariance.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; struct ContraOwner; @@ -13,10 +13,7 @@ impl Owner for ContraOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(|_| {}) } } diff --git a/tests/debug.rs b/tests/debug.rs index 1a7e77a..6721a30 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::Infallible, fmt::Debug}; -use pair::{HasDependent, Owner}; +use pair::{Dependent, HasDependent, Owner}; mod real { pub use pair::Pair; @@ -11,17 +11,17 @@ mod real { mod fake { use std::fmt::Debug; - use pair::{HasDependent, Owner}; + use pair::{Dependent, Owner}; #[derive(Debug)] pub struct Pair<'a, O: Owner> where - >::Dependent: Debug, + Dependent<'a, O>: Debug, { #[expect(dead_code, reason = "we actually care about the derived Debug")] pub owner: O, #[expect(dead_code, reason = "we actually care about the derived Debug")] - pub dependent: >::Dependent, + pub dependent: Dependent<'a, O>, } } @@ -32,7 +32,7 @@ mod fake { fn debugs_match Owner = (), Error = Infallible> + Clone + Debug>( owner: O, ) where - for<'any> >::Dependent: Debug, + for<'any> Dependent<'any, O>: Debug, { let Ok(dependent) = owner.make_dependent(()); let pair_real = real::Pair::new(owner.clone()); @@ -90,7 +90,7 @@ macro_rules! debug_tests { fn make_dependent( &$self_kw, (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { Ok($dep_expr) } } diff --git a/tests/default.rs b/tests/default.rs index c00fd84..5cf423f 100644 --- a/tests/default.rs +++ b/tests/default.rs @@ -1,6 +1,6 @@ #![allow(missing_docs, reason = "integration test")] -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; use std::{convert::Infallible, fmt::Debug}; #[derive(Default, Debug, PartialEq)] @@ -14,10 +14,7 @@ impl Owner for DefaultOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(&self.0) } } diff --git a/tests/drop.rs b/tests/drop.rs index d7f2f47..158e10f 100644 --- a/tests/drop.rs +++ b/tests/drop.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, convert::Infallible, rc::Rc}; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; #[derive(Debug)] struct OnDrop { @@ -35,7 +35,7 @@ impl Owner for OnDrop { fn make_dependent( &self, context: Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + ) -> Result, Self::Error> { Ok(OnDropDep { value: context.0, f: context.1, diff --git a/tests/dyn_trait_owner.rs b/tests/dyn_trait_owner.rs index b6c652e..e96af12 100644 --- a/tests/dyn_trait_owner.rs +++ b/tests/dyn_trait_owner.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; trait MyTrait { fn get(&self) -> &i32; @@ -23,10 +23,7 @@ impl Owner for dyn MyTrait { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(self.get()) } } diff --git a/tests/interior_mutability.rs b/tests/interior_mutability.rs index eac0699..22a9d60 100644 --- a/tests/interior_mutability.rs +++ b/tests/interior_mutability.rs @@ -1,6 +1,6 @@ #![allow(missing_docs, reason = "integration test")] -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; use std::cell::{Cell, RefCell}; use std::convert::Infallible; @@ -17,10 +17,7 @@ impl Owner for InteriorMutableOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(&self.value) } } @@ -62,10 +59,7 @@ impl Owner for RegularOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(InteriorMutableDependent { value_cell: Cell::new(self.value), original_ref: &self.value, @@ -102,10 +96,7 @@ impl Owner for BothInteriorMutableOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(BothInteriorMutableDependent { owner_ref: &self.value, local_value: Cell::new(*self.value.borrow()), diff --git a/tests/multiborrow.rs b/tests/multiborrow.rs index dd035af..8a56999 100644 --- a/tests/multiborrow.rs +++ b/tests/multiborrow.rs @@ -1,6 +1,6 @@ #![allow(missing_docs, reason = "integration test")] -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; use std::convert::Infallible; #[derive(PartialEq)] @@ -25,10 +25,7 @@ impl Owner for MultiPartOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(MultiPartDependent { string: &self.field1, int: &self.field2[0], diff --git a/tests/nested_pair.rs b/tests/nested_pair.rs index ae8e4c0..3f3d533 100644 --- a/tests/nested_pair.rs +++ b/tests/nested_pair.rs @@ -1,6 +1,6 @@ #![allow(missing_docs, reason = "integration test")] -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; use std::convert::Infallible; #[derive(Debug)] @@ -14,10 +14,7 @@ impl Owner for SimpleOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(&self.0) } } @@ -43,10 +40,7 @@ impl Owner for PairOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(PairOwnerDependent { value_ref: &self.value, inner_pair_ref: &self.inner_pair, diff --git a/tests/panic_safety.rs b/tests/panic_safety.rs index 6ba7c47..7a1aa40 100644 --- a/tests/panic_safety.rs +++ b/tests/panic_safety.rs @@ -7,7 +7,7 @@ use std::{ rc::Rc, }; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; #[derive(Debug, PartialEq, Eq)] struct MyPayload(u8); @@ -28,10 +28,7 @@ impl Owner for PanicOnMakeDependent { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { panic_any(MyPayload(7)); } } @@ -74,10 +71,7 @@ impl Owner for PanicOnDepDropIntoOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(PanicOnDepDropIntoOwnerDep) } } @@ -118,10 +112,7 @@ impl Owner for PanicOnDepDropPairDrop { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(PanicOnDepDropPairDropDep) } } diff --git a/tests/unsized_owner.rs b/tests/unsized_owner.rs index d365e1e..7b5706f 100644 --- a/tests/unsized_owner.rs +++ b/tests/unsized_owner.rs @@ -2,7 +2,7 @@ use std::convert::Infallible; -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; #[derive(Debug)] struct Buff(T); @@ -21,10 +21,7 @@ impl Owner for Buff<[u8]> { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { let start = usize::min(1, self.0.len()); let end = self.0.len().saturating_sub(1); Ok((&self.0[start..end], self.0.len())) diff --git a/tests/zst.rs b/tests/zst.rs index 9b02dfe..7d9d031 100644 --- a/tests/zst.rs +++ b/tests/zst.rs @@ -1,6 +1,6 @@ #![allow(missing_docs, reason = "integration test")] -use pair::{HasDependent, Owner, Pair}; +use pair::{Dependent, HasDependent, Owner, Pair}; use std::convert::Infallible; // 1-ZST owner with non-ZST dependent @@ -19,10 +19,7 @@ impl Owner for OneZstOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(NonZstDependent(42)) } } @@ -51,10 +48,7 @@ impl Owner for Non1ZstOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(OneZstDependent) } } @@ -79,10 +73,7 @@ impl Owner for Both1ZstOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(OneZstDependent) } } @@ -109,10 +100,7 @@ impl Owner for BigZstOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(NonZstDependent(23)) } } @@ -141,10 +129,7 @@ impl Owner for NonBigZstOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(BigZstDependent([])) } } @@ -169,10 +154,7 @@ impl Owner for BothBigZstOwner { type Context<'a> = (); type Error = Infallible; - fn make_dependent( - &self, - (): Self::Context<'_>, - ) -> Result<>::Dependent, Self::Error> { + fn make_dependent(&self, (): Self::Context<'_>) -> Result, Self::Error> { Ok(BigZstDependent([])) } } From 88a8fc1fe09724803a3924eae30ade3f1abe0069 Mon Sep 17 00:00:00 2001 From: Isaac Chen Date: Sat, 15 Mar 2025 14:01:30 -0400 Subject: [PATCH 109/110] addressed ON_RELEASE chores --- CHANGELOG.md | 2 -- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 10 +--------- src/lib.rs | 2 -- 5 files changed, 3 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f33a5..50837fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,3 @@ - - # Major Version 0 ## v0.2.0 diff --git a/Cargo.lock b/Cargo.lock index 92ad151..f4557aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,7 +100,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pair" -version = "0.1.0" +version = "0.2.0" dependencies = [ "loom", ] diff --git a/Cargo.toml b/Cargo.toml index c10b83c..dea3779 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ [package] name = "pair" -version = "0.1.0" # ON_RELEASE: Bump version. Also, do all "ON_RELEASE" tasks +version = "0.2.0" authors = ["Isaac Chen"] edition = "2024" rust-version = "1.85.0" diff --git a/README.md b/README.md index a2dd0af..5d1b25a 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,6 @@ You define how to construct a dependent type from a reference to an owning type, and [`Pair`] will carefully bundle them together in a safe and freely movable self-referential struct. - -# DO NOT USE THIS LIBRARY - -As of right now, I have absolutely no idea whether or not this API is sound. You -should *absolutely not* use this library for anything that matters at this point -in time. - # Example Usage A typical use case might look something like this: @@ -152,5 +145,4 @@ dual licensed as above, without any additional terms or conditions. docs.rs documentation links for rendered markdown (ex, on GitHub) These are overridden when include_str!(..)'d in lib.rs --> - -[`Pair`]: https://docs.rs/pair/__CRATE_VERSION_HERE__/pair/struct.Pair.html +[`Pair`]: https://docs.rs/pair/0.2.0/pair/struct.Pair.html diff --git a/src/lib.rs b/src/lib.rs index 16c08ca..6aafd3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,6 @@ // These markdown links are used to override the docs.rs web links present at // the bottom of README.md. These two lists must be kept in sync, or the links // included here will be hard-coded links to docs.rs, not relative doc links. -// ON_RELEASE: the below link(s) should be verified to match the readme, and -// this "on release" comment removed (the above one should stay). //! [`Pair`]: Pair #![cfg_attr(any(doc, test), doc = include_str!("../README.md"))] #![no_std] From 41fe479789aab21ef2d678d4a99ec5f362efb26a Mon Sep 17 00:00:00 2001 From: root Date: Sat, 15 Mar 2025 23:33:46 -0700 Subject: [PATCH 110/110] ready for release 0.2.0 --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dea3779..cb24b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ license = "MIT OR Apache-2.0" keywords = ["self-referential", "data-structures", "memory", "ownership", "no_std"] categories = ["memory-management", "rust-patterns", "no-std", "data-structures"] include = ["/src/", "/Cargo.toml", "/README.md", "/CHANGELOG.md", "/LICENSE-*"] -publish = false # ON_RELEASE: Remove publish = false # # # # # # # # # # # # # # # # # # # # # #