Skip to content

Commit

Permalink
Auto merge of #95249 - HeroicKatora:set-ptr-value, r=dtolnay
Browse files Browse the repository at this point in the history
Refactor set_ptr_value as with_metadata_of

Replaces `set_ptr_value` (#75091) with methods of reversed argument order:

```rust
impl<T: ?Sized> *mut T {
    pub fn with_metadata_of<U: ?Sized>(self, val: *mut U) -> *mut U;
}

impl<T: ?Sized> *const T {
    pub fn with_metadata_of<U: ?Sized>(self, val: *const U) -> *const U;
}
```

By reversing the arguments we achieve several clarifications:

- The function closely resembles `cast` with an argument to
  initialize the metadata. This is easier to teach and answers a long
  outstanding question that had restricted cast to `Sized` pointee
  targets. See multiples reviews of
  <#47631>
- The 'object identity', in the form of provenance, is now preserved
  from the receiver argument to the result. This helps explain the method as
  a builder-style, instead of some kind of setter that would modify
  something in-place. Ensuring that the result has the identity of the
  `self` argument is also beneficial for an intuition of effects.
- An outstanding concern, 'Correct argument type', is avoided by not
  committing to any specific argument type. This is consistent with cast
  which does not require its receiver to be a 'raw address'.

Hopefully the usage examples in `sync/rc.rs` serve as sufficient examples of the style to convince the reader of the readability improvements of this style, when compared to the previous order of arguments.

I want to take the opportunity to motivate inclusion of this method _separate_ from metadata API, separate from `feature(ptr_metadata)`. It does _not_ involve the `Pointee` trait in any form. This may be regarded as a very, very light form that does not commit to any details of the pointee trait, or its associated metadata. There are several use cases for which this is already sufficient and no further inspection of metadata is necessary.

- Storing the coercion of `*mut T` into `*mut dyn Trait` as a way to dynamically cast some an arbitrary instance of the same type to a dyn trait instance. In particular, one can have a field of type `Option<*mut dyn io::Seek>` to memorize if a particular writer is seekable. Then a method `fn(self: &T) -> Option<&dyn Seek>` can be provided, which does _not_ involve the static trait bound `T: Seek`. This makes it possible to create an API that is capable of utilizing seekable streams and non-seekable streams (instead of a possible less efficient manner such as more buffering) through the same entry-point.

- Enabling more generic forms of unsizing for no-`std` smart pointers. Using the stable APIs only few concrete cases are available. One can unsize arrays to `[T]` by `ptr::slice_from_raw_parts` but unsizing a custom smart pointer to, e.g., `dyn Iterator`, `dyn Future`, `dyn Debug`, can't easily be done generically. Exposing `with_metadata_of` would allow smart pointers to offer their own `unsafe` escape hatch with similar parameters where the caller provides the unsized metadata. This is particularly interesting for embedded where `dyn`-trait usage can drastically reduce code size.
  • Loading branch information
bors committed Mar 28, 2022
2 parents ee915c3 + d489ea7 commit c1230e1
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 88 deletions.
6 changes: 3 additions & 3 deletions library/alloc/src/rc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ impl<T: ?Sized> Rc<T> {

// Reverse the offset to find the original RcBox.
let rc_ptr =
unsafe { (ptr as *mut RcBox<T>).set_ptr_value((ptr as *mut u8).offset(-offset)) };
unsafe { (ptr as *mut u8).offset(-offset).with_metadata_of(ptr as *mut RcBox<T>) };

unsafe { Self::from_ptr(rc_ptr) }
}
Expand Down Expand Up @@ -1338,7 +1338,7 @@ impl<T: ?Sized> Rc<T> {
Self::allocate_for_layout(
Layout::for_value(&*ptr),
|layout| Global.allocate(layout),
|mem| (ptr as *mut RcBox<T>).set_ptr_value(mem),
|mem| mem.with_metadata_of(ptr as *mut RcBox<T>),
)
}
}
Expand Down Expand Up @@ -2264,7 +2264,7 @@ impl<T: ?Sized> Weak<T> {
let offset = unsafe { data_offset(ptr) };
// Thus, we reverse the offset to get the whole RcBox.
// SAFETY: the pointer originated from a Weak, so this offset is safe.
unsafe { (ptr as *mut RcBox<T>).set_ptr_value((ptr as *mut u8).offset(-offset)) }
unsafe { (ptr as *mut u8).offset(-offset).with_metadata_of(ptr as *mut RcBox<T>) }
};

// SAFETY: we now have recovered the original Weak pointer, so can create the Weak.
Expand Down
7 changes: 4 additions & 3 deletions library/alloc/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,8 @@ impl<T: ?Sized> Arc<T> {
let offset = data_offset(ptr);

// Reverse the offset to find the original ArcInner.
let arc_ptr = (ptr as *mut ArcInner<T>).set_ptr_value((ptr as *mut u8).offset(-offset));
let arc_ptr =
(ptr as *mut u8).offset(-offset).with_metadata_of(ptr as *mut ArcInner<T>);

Self::from_ptr(arc_ptr)
}
Expand Down Expand Up @@ -1182,7 +1183,7 @@ impl<T: ?Sized> Arc<T> {
Self::allocate_for_layout(
Layout::for_value(&*ptr),
|layout| Global.allocate(layout),
|mem| (ptr as *mut ArcInner<T>).set_ptr_value(mem) as *mut ArcInner<T>,
|mem| mem.with_metadata_of(ptr as *mut ArcInner<T>),
)
}
}
Expand Down Expand Up @@ -1888,7 +1889,7 @@ impl<T: ?Sized> Weak<T> {
let offset = unsafe { data_offset(ptr) };
// Thus, we reverse the offset to get the whole RcBox.
// SAFETY: the pointer originated from a Weak, so this offset is safe.
unsafe { (ptr as *mut ArcInner<T>).set_ptr_value((ptr as *mut u8).offset(-offset)) }
unsafe { (ptr as *mut u8).offset(-offset).with_metadata_of(ptr as *mut ArcInner<T>) }
};

// SAFETY: we now have recovered the original Weak pointer, so can create the Weak.
Expand Down
85 changes: 44 additions & 41 deletions library/core/src/ptr/const_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,50 @@ impl<T: ?Sized> *const T {
self as _
}

/// Use the pointer value in a new pointer of another type.
///
/// In case `val` is a (fat) pointer to an unsized type, this operation
/// will ignore the pointer part, whereas for (thin) pointers to sized
/// types, this has the same effect as a simple cast.
///
/// The resulting pointer will have provenance of `self`, i.e., for a fat
/// pointer, this operation is semantically the same as creating a new
/// fat pointer with the data pointer value of `self` but the metadata of
/// `val`.
///
/// # Examples
///
/// This function is primarily useful for allowing byte-wise pointer
/// arithmetic on potentially fat pointers:
///
/// ```
/// #![feature(set_ptr_value)]
/// # use core::fmt::Debug;
/// let arr: [i32; 3] = [1, 2, 3];
/// let mut ptr = arr.as_ptr() as *const dyn Debug;
/// let thin = ptr as *const u8;
/// unsafe {
/// ptr = thin.add(8).with_metadata_of(ptr);
/// # assert_eq!(*(ptr as *const i32), 3);
/// println!("{:?}", &*ptr); // will print "3"
/// }
/// ```
#[unstable(feature = "set_ptr_value", issue = "75091")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[inline]
pub fn with_metadata_of<U>(self, mut val: *const U) -> *const U
where
U: ?Sized,
{
let target = &mut val as *mut *const U as *mut *const u8;
// SAFETY: In case of a thin pointer, this operations is identical
// to a simple assignment. In case of a fat pointer, with the current
// fat pointer layout implementation, the first field of such a
// pointer is always the data pointer, which is likewise assigned.
unsafe { *target = self as *const u8 };
val
}

/// Changes constness without changing the type.
///
/// This is a bit safer than `as` because it wouldn't silently change the type if the code is
Expand Down Expand Up @@ -764,47 +808,6 @@ impl<T: ?Sized> *const T {
self.wrapping_offset((count as isize).wrapping_neg())
}

/// Sets the pointer value to `ptr`.
///
/// In case `self` is a (fat) pointer to an unsized type, this operation
/// will only affect the pointer part, whereas for (thin) pointers to
/// sized types, this has the same effect as a simple assignment.
///
/// The resulting pointer will have provenance of `val`, i.e., for a fat
/// pointer, this operation is semantically the same as creating a new
/// fat pointer with the data pointer value of `val` but the metadata of
/// `self`.
///
/// # Examples
///
/// This function is primarily useful for allowing byte-wise pointer
/// arithmetic on potentially fat pointers:
///
/// ```
/// #![feature(set_ptr_value)]
/// # use core::fmt::Debug;
/// let arr: [i32; 3] = [1, 2, 3];
/// let mut ptr = arr.as_ptr() as *const dyn Debug;
/// let thin = ptr as *const u8;
/// unsafe {
/// ptr = ptr.set_ptr_value(thin.add(8));
/// # assert_eq!(*(ptr as *const i32), 3);
/// println!("{:?}", &*ptr); // will print "3"
/// }
/// ```
#[unstable(feature = "set_ptr_value", issue = "75091")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[inline]
pub fn set_ptr_value(mut self, val: *const u8) -> Self {
let thin = &mut self as *mut *const T as *mut *const u8;
// SAFETY: In case of a thin pointer, this operations is identical
// to a simple assignment. In case of a fat pointer, with the current
// fat pointer layout implementation, the first field of such a
// pointer is always the data pointer, which is likewise assigned.
unsafe { *thin = val };
self
}

/// Reads the value from `self` without moving it. This leaves the
/// memory in `self` unchanged.
///
Expand Down
85 changes: 44 additions & 41 deletions library/core/src/ptr/mut_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,50 @@ impl<T: ?Sized> *mut T {
self as _
}

/// Use the pointer value in a new pointer of another type.
///
/// In case `val` is a (fat) pointer to an unsized type, this operation
/// will ignore the pointer part, whereas for (thin) pointers to sized
/// types, this has the same effect as a simple cast.
///
/// The resulting pointer will have provenance of `self`, i.e., for a fat
/// pointer, this operation is semantically the same as creating a new
/// fat pointer with the data pointer value of `self` but the metadata of
/// `val`.
///
/// # Examples
///
/// This function is primarily useful for allowing byte-wise pointer
/// arithmetic on potentially fat pointers:
///
/// ```
/// #![feature(set_ptr_value)]
/// # use core::fmt::Debug;
/// let mut arr: [i32; 3] = [1, 2, 3];
/// let mut ptr = arr.as_mut_ptr() as *mut dyn Debug;
/// let thin = ptr as *mut u8;
/// unsafe {
/// ptr = thin.add(8).with_metadata_of(ptr);
/// # assert_eq!(*(ptr as *mut i32), 3);
/// println!("{:?}", &*ptr); // will print "3"
/// }
/// ```
#[unstable(feature = "set_ptr_value", issue = "75091")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[inline]
pub fn with_metadata_of<U>(self, mut val: *mut U) -> *mut U
where
U: ?Sized,
{
let target = &mut val as *mut *mut U as *mut *mut u8;
// SAFETY: In case of a thin pointer, this operations is identical
// to a simple assignment. In case of a fat pointer, with the current
// fat pointer layout implementation, the first field of such a
// pointer is always the data pointer, which is likewise assigned.
unsafe { *target = self as *mut u8 };
val
}

/// Changes constness without changing the type.
///
/// This is a bit safer than `as` because it wouldn't silently change the type if the code is
Expand Down Expand Up @@ -878,47 +922,6 @@ impl<T: ?Sized> *mut T {
self.wrapping_offset((count as isize).wrapping_neg())
}

/// Sets the pointer value to `ptr`.
///
/// In case `self` is a (fat) pointer to an unsized type, this operation
/// will only affect the pointer part, whereas for (thin) pointers to
/// sized types, this has the same effect as a simple assignment.
///
/// The resulting pointer will have provenance of `val`, i.e., for a fat
/// pointer, this operation is semantically the same as creating a new
/// fat pointer with the data pointer value of `val` but the metadata of
/// `self`.
///
/// # Examples
///
/// This function is primarily useful for allowing byte-wise pointer
/// arithmetic on potentially fat pointers:
///
/// ```
/// #![feature(set_ptr_value)]
/// # use core::fmt::Debug;
/// let mut arr: [i32; 3] = [1, 2, 3];
/// let mut ptr = arr.as_mut_ptr() as *mut dyn Debug;
/// let thin = ptr as *mut u8;
/// unsafe {
/// ptr = ptr.set_ptr_value(thin.add(8));
/// # assert_eq!(*(ptr as *mut i32), 3);
/// println!("{:?}", &*ptr); // will print "3"
/// }
/// ```
#[unstable(feature = "set_ptr_value", issue = "75091")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[inline]
pub fn set_ptr_value(mut self, val: *mut u8) -> Self {
let thin = &mut self as *mut *mut T as *mut *mut u8;
// SAFETY: In case of a thin pointer, this operations is identical
// to a simple assignment. In case of a fat pointer, with the current
// fat pointer layout implementation, the first field of such a
// pointer is always the data pointer, which is likewise assigned.
unsafe { *thin = val };
self
}

/// Reads the value from `self` without moving it. This leaves the
/// memory in `self` unchanged.
///
Expand Down

0 comments on commit c1230e1

Please sign in to comment.