Skip to content

Commit

Permalink
0.2.3
Browse files Browse the repository at this point in the history
  • Loading branch information
radekvit committed Nov 17, 2023
1 parent b4b9287 commit 266d73f
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 172 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.2.3
- Factor out the vtable to be shared between the `sync` and `prc` modules, as the type definition is identical
- Fix tests that should fail and check that we're failing for the right reason on nightly
- Remove lifetime bounds from `Parc` and `Prc` signatures where the default lifetime bounds are otherwise identical
- Improve internal naming of structures and their fields
- Increase test coverage to cover as much as possible

## 0.2.2
- Switch `TypeErasedArc` and `TypeErasedRc` to use `ManuallyDrop` when converting the raw pointer into a concrete `Arc<T>`, `Rc<T>` or `Weak<T>` to avoid incorrect behavior in case any method we call panics. Previously, the temporary would be dropped even if it shouldn't have, causing potential UB (use after free).
- Add `Prc::try_from_rc`, `Prc::try_project`, `Parc::try_from_arc`, and `Parc::try_project`. These are fallible versions of the same methods without `try_`, where the `FnOnce` that's passed to these functions returns an `Option`. This allows for using `Prc` and `Parc` where the projected reference might be unavailable.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 4 additions & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
[package]
name = "pared"
version = "0.2.2"
version = "0.2.3"
authors = ["Radek Vít <[email protected]>"]
edition = "2021"
rust-version = "1.56"
description = "Projected reference counted pointers"
repository = "https://github.com/radekvit/pared"
license = "MIT OR Apache-2.0"
keywords = [
"arc",
"rc",
"projected",
"aliasing",
"shared_ptr",
]
categories = [
"data-structures",
"memory-management",
"no-std",
"rust-patterns",
]
keywords = ["arc", "rc", "projected", "aliasing", "shared_ptr"]
categories = ["data-structures", "memory-management", "no-std", "rust-patterns"]

[features]
default = ["std"]
Expand All @@ -32,4 +21,4 @@ doc-comment = "0.3.3"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg=docsrs"]
rustdoc-args = ["--cfg=docsrs"]
30 changes: 22 additions & 8 deletions src/erased_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ use core::{
mem::{size_of, MaybeUninit},
};

/// A type-erased, potentially fat pointer to anything.
///
/// This type will only work with the assumption that all pointers are at most 2 pointers.
#[derive(Clone, Copy)]
#[repr(transparent)]
pub(crate) struct TypeErasedPtr(MaybeUninit<[*const (); 2]>);

impl Clone for TypeErasedPtr {
fn clone(&self) -> Self {
Self(self.0)
}
}

impl Copy for TypeErasedPtr {}

impl core::fmt::Debug for TypeErasedPtr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("TypeErasedPtr").field(&self.0).finish()
Expand Down Expand Up @@ -60,6 +56,7 @@ mod tests {
use alloc::{format, string::String, vec};

#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn sized() {
let s = String::from("Hello!");
let ptr = TypeErasedPtr::new(&s);
Expand All @@ -70,6 +67,7 @@ mod tests {
}

#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn unsized_slice() {
let boxed_slice = vec![1u8, 2, 3, 4, 5].into_boxed_slice();
let ptr = TypeErasedPtr::new(&*boxed_slice as *const [u8]);
Expand All @@ -80,6 +78,7 @@ mod tests {
}

#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn dyn_ptr() {
// We want to check that the pointers actually ARE compatible
#![allow(clippy::vtable_address_comparisons)]
Expand All @@ -91,4 +90,19 @@ mod tests {
assert_eq!(r as *const _, debug as *const dyn core::fmt::Debug);
assert_eq!(format!("{:?}", r), "\"Hello!\"");
}

#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn clone() {
let ptr = TypeErasedPtr::new(&1);
#[allow(clippy::clone_on_copy)]
let _ = ptr.clone();
}

#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
fn debug() {
let ptr = TypeErasedPtr::new(&1);
format!("{:?}", ptr);
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#![deny(missing_docs)]
#![deny(clippy::std_instead_of_core)]
#![deny(clippy::std_instead_of_alloc)]
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]

extern crate alloc;
extern crate core;
Expand All @@ -49,3 +50,4 @@ pub mod prc;
pub mod sync;

mod erased_ptr;
mod vtable;
26 changes: 13 additions & 13 deletions src/prc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
//! //! # Soundness
//! None of the following should compile:
//!
//! ```compile_fail
//! ```compile_fail,E0597
//! use pared::prc::Prc;
//! use std::rc::Rc;
//!
Expand All @@ -34,14 +34,14 @@
//! {
//! let s = "Hello World!".to_string();
//! let s_ref: &str = &s;
//! let y: Prc<&str> = Prc::from_rc(|_| &s_ref);
//! let y: Prc<&str> = Prc::from_rc(&x, |_| &s_ref);
//! z = y.project(|s: &&str| *s);
//! // s deallocated here
//! }
//! println!("{}", &*z); // printing garbage, accessing `s` after it’s freed
//! ```
//!
//! ```compile_fail
//! ```compile_fail,E0597
//! use pared::prc::Prc;
//!
//! let x: Prc<()> = Prc::new(());
Expand All @@ -56,7 +56,7 @@
//! println!("{}", &*z); // printing garbage, accessing `s` after it’s freed
//! ```
//!
//! ```compile_fail
//! ```compile_fail,E0597
//! use pared::prc::Prc;
//! use std::sync::Arc;
//!
Expand Down Expand Up @@ -152,19 +152,19 @@ impl<T: ?Sized> Prc<T> {
/// ```
///
/// Note that references to local variables cannot be returned from the `project` function:
/// ```compile_fail
/// ```compile_fail,E0597
/// # use std::rc::Rc;
/// use pared::prc::Prc;
/// let rc = Rc::new((5u64,));
/// let local = 5;
/// let prc = Prc::from_rc(&rc, |tuple| &local);
/// let prc = Prc::from_rc(&rc, |_| &local);
/// ```
#[inline]
pub fn from_rc<U, F>(rc: &Rc<U>, project: F) -> Self
where
U: ?Sized,
T: 'static,
F: for<'x> FnOnce(&'x U) -> &'x T,
F: FnOnce(&U) -> &T,
{
let projected = project(rc);
// SAFETY: fn shouldn't be able to capture any local references
Expand Down Expand Up @@ -206,7 +206,7 @@ impl<T: ?Sized> Prc<T> {
where
U: ?Sized,
T: 'static,
F: for<'x> FnOnce(&'x U) -> Option<&'x T>,
F: FnOnce(&U) -> Option<&T>,
{
let projected = project(rc)?;
// SAFETY: fn shouldn't be able to capture any local references
Expand All @@ -231,17 +231,17 @@ impl<T: ?Sized> Prc<T> {
/// ```
///
/// Note that references to local variables cannot be returned from the `project` function:
/// ```compile_fail
/// ```compile_fail,E0597
/// use pared::prc::Prc;
/// let prc = Prc::new((5u64,));
/// let local = 5;
/// let projected = prc.project(|tuple| &local);
/// let projected = prc.project(|_| &local);
/// ```
#[inline]
pub fn project<U, F>(&self, project: F) -> Prc<U>
where
U: ?Sized + 'static,
F: for<'x> FnOnce(&'x T) -> &'x U,
F: FnOnce(&T) -> &U,
{
let projected = project(self);
// SAFETY: fn shouldn't be able to capture any local references
Expand Down Expand Up @@ -281,7 +281,7 @@ impl<T: ?Sized> Prc<T> {
pub fn try_project<U, F>(&self, project: F) -> Option<Prc<U>>
where
U: ?Sized + 'static,
F: for<'x> FnOnce(&'x T) -> Option<&'x U>,
F: FnOnce(&T) -> Option<&U>,
{
let projected = project(self)?;
// SAFETY: fn shouldn't be able to capture any local references
Expand Down Expand Up @@ -672,6 +672,6 @@ impl<T: ?Sized> Clone for Weak<T> {

impl<T: ?Sized> core::fmt::Debug for Weak<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "(Peak)")
write!(f, "(Weak)")
}
}
Loading

0 comments on commit 266d73f

Please sign in to comment.