We (@ekuber and I) would like to propose partial stabilization of "DST Custom Coercions" rust-lang/rfcs#982, specifically the stabilization of the Unsize trait (#![feature(unsize)]). Critically, this would not include the stabilization of CoerceUnsized.
Unsize is marked as not implementable by user code, which would allow for its stabilization without constraining the trait's shape: no breakage would be expected even if the underlying design changes (for example, by adding functions to the trait), as only std/rustc can implement it. Only compiler-guaranteed unsize coercions are allowed by this trait—the trait just encodes what the compiler already guarantees and does. Look at the docs for all details on the compiler guarantees.
Having Unsize as stable would allow for custom type coercions without conversion by providing an API with a helper function, while the CoerceUnsized trait design is still in progress, which would eventually make the helper function unnecessary and its body can become a direct as cast, kept only for potential backwards-compatibility.
impl<T: ?Sized> CustomSmartPointer<T> {
const fn unsize<K: ?Sized>(self) -> CustomSmartPointer<K>
where
T: Unsize<K>,
{
CustomSmartPointer { inner: self.inner }
}
}
fn main() {
let mut x = 255;
let p = CustomSmartPointer { inner: &mut x };
let y = p.unsize::<dyn Foo>();
println!("{}", y);
}
Notably, this approach also allows for this helper to be a const fn. Additionally, you can also support coercions of multiple separate pointers if necessary, which CoerceUnsized doesn't (currently?) support.
On stable Rust there's currently no adequate full alternative. Any currently-stable way of providing this coercion necessarily requires providing access to the raw pointer to the API's consumer/caller, by requiring them to pass in a closure performing the cast. Allowing for this helper function to be const would also require a nightly feature for impl [const] Fn. With Unsize it is possible to do this without closures, and only allow sound operations, all from within the smart pointer's code.
impl<T: ?Sized> CustomSmartPointer<T> {
const unsafe fn unsize<K: ?Sized>(
self,
coerce: impl [const] FnOnce(*mut T) -> *mut K,
) -> CustomSmartPointer<K> {
CustomSmartPointer {
inner: coerce(self.inner),
}
}
}
const fn coerce(x: *mut u8) -> *mut dyn Display {
x as _
}
fn main() {
let mut x = 255;
let p = CustomSmartPointer { inner: &mut x };
let y = unsafe { p.unsize(coerce) };
println!("{}", y);
}
Note that by taking an fn pointer, the user can perform arbitrary operations that might not conform to the safety invariants needed by the pointer, so the API should be marked as unsafe, making for a poorer API:
// ⚠️ can do any casting operation, not just coercion ⚠️
const fn shouldnt_be_allowed(x: *mut dyn Display) -> *mut i8 {
x as _
}
Ferrous Systems had an experiment for CoerceUnsized 2 years ago for encoding more information of the pointer in the traits themselves, but given that Unsize is useful independently of CoerceUnsized, and that the trait is not user-implementable, if those experiments (or any other extensions) are eventually adopted, it wouldn't affect the usage of the Unsize trait on stable being proposed.
We'd like to see if there is appetite of stabilizing just Unsize.