diff --git a/text/3621-derive-smart-pointer.md b/text/3621-derive-smart-pointer.md new file mode 100644 index 00000000000..d6a28ff1d63 --- /dev/null +++ b/text/3621-derive-smart-pointer.md @@ -0,0 +1,817 @@ +- Feature Name: `derive_smart_pointer` +- Start Date: 2024-05-01 +- RFC PR: [rust-lang/rfcs#3621](https://github.com/rust-lang/rfcs/pull/3621) +- Rust Issue: [rust-lang/rust#123430](https://github.com/rust-lang/rust/issues/123430) + +# Summary +[summary]: #summary + +Make it possible to define custom smart pointers that work with trait objects. +For now, it will only be possible to do this using a derive macro, as we do not +stabilize the underlying traits. + +This RFC builds on top of the [arbitrary self types v2 RFC][ast]. All +references to the `Receiver` trait are references to the version defined by +that RFC, which is different from the `Receiver` trait in nightly at the time +of writing. + +# Motivation +[motivation]: #motivation + +Currently, the standard library types `Rc` and `Arc` are special. It's not +possible for third-party libraries to define custom smart pointers that work +with trait objects. + +It is generally desireable to make std less special, but this particular RFC is +motived by use-cases in the Linux Kernel. In the Linux Kernel, we need +reference counted objects often, but we are not able to use the standard +library `Arc`. There are several reasons for this: + +1. The standard Rust `Arc` will call `abort` on overflow. This is not + acceptable in the kernel; instead we want to saturate the count when it hits + `isize::MAX`. This effectively leaks the `Arc`. +2. Using Rust atomics raises various issues with the memory model. We are using + the LKMM (Linux Kernel Memory Model) rather than the usual C++ model. This + means that all atomic operations should be implemented with an `asm!` block + or similar that matches what kernel C does, rather than an LLVM intrinsic + like we do today. + +The Linux Kernel also needs another custom smart pointer called `ListArc`, +which is needed to provide a safe API for the linked list that the kernel uses. +The kernel needs these linked lists to avoid allocating memory during critical +regions on spinlocks. + +For more detailed explanations of these use-cases, please refer to: + +* [Arc in the Linux Kernel](https://rust-for-linux.com/arc-in-the-linux-kernel). + * This document was discussed during [the 2024-03-06 meeting with t-lang](https://hackmd.io/OCz8EfzrRXeogXEDcOrL2w). +* The kernel's custom linked list: [Mailing list](https://lore.kernel.org/all/20240402-linked-list-v1-0-b1c59ba7ae3b@google.com/), [GitHub](https://github.com/Darksonn/linux/commits/b4/linked-list/). +* [Discussion on the memory model issue with t-opsem](https://rust-lang.zulipchat.com/#narrow/stream/136281-t-opsem/topic/.E2.9C.94.20Rust.20and.20the.20Linux.20Kernel.20Memory.20Model/near/422047516) + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The derive macro `SmartPointer` allows you to use custom smart pointers with +trait objects. This means that you will be able to coerce from +`SmartPointer` to `SmartPointer` when `MyStruct` +implements `MyTrait`. Additionally, the derive macro allows you to use `self: +SmartPointer` in traits without making them non-object-safe. + +It is not possible to use this feature without the derive macro, as we are not +stabilizing its expansion. + +## Coercions to trait objects + +By using the macro, the following example will compile: +```rust +#[derive(SmartPointer)] +#[repr(transparent)] +struct MySmartPointer(Box); + +impl Deref for MySmartPointer { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + +trait MyTrait {} + +impl MyTrait for i32 {} + +fn main() { + let ptr: MySmartPointer = MySmartPointer(Box::new(4)); + + // This coercion would be an error without the derive. + let ptr: MySmartPointer = ptr; +} +``` +Without the `#[derive(SmartPointer)]` macro, this example would fail with the +following error: +``` +error[E0308]: mismatched types + --> src/main.rs:11:44 + | +11 | let ptr: MySmartPointer = ptr; + | --------------------------- ^^^ expected `MySmartPointer`, found `MySmartPointer` + | | + | expected due to this + | + = note: expected struct `MySmartPointer` + found struct `MySmartPointer` + = help: `i32` implements `MyTrait` so you could box the found value and coerce it to the trait object `Box`, you will have to change the expected type as well +``` + +## Object safety + +Consider the following trait: +```rust +trait MyTrait { + // Arbitrary self types is enough for this. + fn func(self: MySmartPointer); +} + +// But this requires #[derive(SmartPointer)]. +fn call_func(value: MySmartPointer) { + value.func(); +} +``` +You do not need `#[derive(SmartPointer)]` to declare this trait ([arbitrary +self types][ast] is enough), but the trait will not be object safe unless you +annotate `MySmartPointer` with `#[derive(SmartPointer)]`. If you don't, then +the use of `dyn MyTrait` triggers the following error: +``` +error[E0038]: the trait `MyTrait` cannot be made into an object + --> src/lib.rs:11:36 + | +8 | fn func(self: MySmartPointer); + | -------------------- help: consider changing method `func`'s `self` parameter to be `&self`: `&Self` +... +11 | fn call_func(value: MySmartPointer) { + | ^^^^^^^^^^^ `MyTrait` cannot be made into an object + | +note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit + --> src/lib.rs:8:19 + | +7 | trait MyTrait { + | ------- this trait cannot be made into an object... +8 | fn func(self: MySmartPointer); + | ^^^^^^^^^^^^^^^^^^^^ ...because method `func`'s `self` parameter cannot be dispatched on +``` +Note that using the `self: MySmartPointer` syntax requires that you +implement `Receiver` (or `Deref`), as the derive macro does not emit an +implementation of `Receiver`. + +## Requirements for using the macro + +Whenever a `self: MySmartPointer` method is called on a trait object, the +compiler will convert from `MySmartPointer` to +`MySmartPointer` using something similar to a transmute. Because of +this, there are strict requirements on the layout of `MySmartPointer`. It is +required that `MySmartPointer` is a `#[repr(transparent)]` struct, and the type +of its non-zero-sized field must either be a standard library pointer type +(reference, raw pointer, NonNull, Box, Arc, etc.) or another user-defined type +also using this derive macro. +```rust +#[derive(SmartPointer)] +#[repr(transparent)] +struct MySmartPointer { + ptr: Box, + _phantom: PhantomData, +} +``` + +### Multiple type parameters + +If the type has multiple type parameters, then you must explicitly specify +which one should be used for dynamic dispatch. For example: +```rust +#[derive(SmartPointer)] +#[repr(transparent)] +struct MySmartPointer<#[pointee] T: ?Sized, U> { + ptr: Box, + _phantom: PhantomData, +} +``` +Specifying `#[pointee]` when the struct has only one type parameter is allowed, +but not required. + +## Pinned pointers + +The `#[derive(SmartPointer)]` macro is not sufficient to coerce the smart +pointer when it is wrapped in `Pin`. That is, even if `MySmartPointer` +coerces to `MySmartPointer`, you will not be able to coerce +`Pin>` to `Pin>`. +Similarly, traits with self types of `Pin>` are not object +safe. + +If you implement the unstable unsafe trait called `PinCoerceUnsized` for +`MySmartPointer`, then the smart pointer will gain the ability to be coerced +when wrapped in `Pin`. The trait is not being stabilized by this RFC. + +## Example of a custom Rc +[custom-rc]: #example-of-a-custom-rc + +The macro makes it possible to implement custom smart pointers. For example, +you could implement your own `Rc` type like this: + +```rust +#[derive(SmartPointer)] +#[repr(transparent)] +pub struct Rc { + inner: NonNull>, +} + +struct RcInner { + refcount: usize, + value: T, +} + +impl Deref for Rc { + type Target = T; + fn deref(&self) -> &T { + let ptr = self.inner.as_ptr(); + unsafe { &*ptr.value } + } +} + +impl Rc { + pub fn new(value: T) -> Self { + let inner = Box::new(RcInner { + refcount: 1, + value, + }); + Self { + inner: NonNull::from(Box::leak(inner)), + } + } +} + +impl Clone for Rc { + fn clone(&self) -> Self { + unsafe { (*self.inner.as_ptr()).refcount += 1 }; + Self { inner: self.inner } + } +} + +impl Drop for Rc { + fn drop(&mut self) { + let ptr = self.inner.as_ptr(); + unsafe { (*ptr).refcount -= 1 }; + if unsafe { (*ptr).refcount } == 0 { + drop(unsafe { Box::from_raw(ptr) }); + } + } +} +``` +In this example, `#[derive(SmartPointer)]` makes it possible to use `Rc`. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The derive macro will expand into two trait implementations, +[`core::ops::CoerceUnsized`] to enable unsizing coercions and +[`core::ops::DispatchFromDyn`] for dynamic dispatch. This expansion will be +adapted in the future if the underlying mechanisms for unsizing coercions and +dynamically dispatched receivers changes. + +As mentioned in the [rationale][why-only-macro] section, this RFC only proposes +to stabilize the derive macro. The underlying traits used by its expansion will +remain unstable for now. + +## Input Requirements +[input-requirements]: #input-requirements + +The macro sets the following requirements on its input: + +1. The definition must be a struct. +2. The struct must have at least one type parameter. If multiple type + parameters are present, exactly one of them has to be annotated with the + `#[pointee]` derive helper attribute. +3. The struct must be `#[repr(transparent)]`. +4. The struct must have at least one field. +5. Assume that `T` is a type that can be unsized to `U`, and let `FT` and `FU` + be the type of the struct's field when the pointee is equal to `T` and `U` + respectively. If the struct's trait bounds are satisfied for both `T` and + `U`, then it must be possible to convert `FT` to `FU` using an unsizing + coercion. + +(Adapted from the docs for [`DispatchFromDyn`].) + +Points 1, 2 and 3 are verified syntactically by the derive macro. Points 4 and 5 +are verified semantically by the compiler when checking the generated +[`DispatchFromDyn`] implementation as it does today. + +The `#[pointee]` attribute may also be written as `#[smart_pointer::pointee]`. + +## Expansion + +The macro will expand to two implementations, one for +[`core::ops::CoerceUnsized`] and one for [`core::ops::DispatchFromDyn`]. This +is enough for a type to participate in unsizing coercions and dynamic dispatch. + +The derive macro will implement both traits for the type according to the +following procedure: + +- Copy all generic parameters from the struct definition into the impl. +- Add an additional type parameter `U`. +- For every trait bound declared on the trait, add it twice to the trait + implementation. Once exactly as written, and once with every instance of the + `#[pointee]` parameter replaced with `U`. +- Add an additional `Unsize` bound to the `#[pointee]` type parameter. +- The generic parameter of the trait being implemented will be `Self`, except + that the `#[pointee]` type parameter is replaced with `U`. + +Given the following example code: +```rust +#[derive(SmartPointer)] +#[repr(transparent)] +struct MySmartPointer<'a, #[pointee] T, A> +where + T: ?Sized + SomeTrait, +{ + ptr: &'a T, + phantom: PhantomData, +} +``` + +we'll get the following expansion: + +```rust +#[automatically_derived] +impl<'a, T, A, U> ::core::ops::CoerceUnsized> for MySmartPointer<'a, T, A> +where + T: ?Sized + SomeTrait, + U: ?Sized + SomeTrait, + T: ::core::marker::Unsize, +{} + +#[automatically_derived] +impl<'a, T, A, U> ::core::ops::DispatchFromDyn> for MySmartPointer<'a, T, A> +where + T: ?Sized + SomeTrait, + U: ?Sized + SomeTrait, + T: ::core::marker::Unsize, +{} +``` + +## `Receiver` and `Deref` implementations + +The macro does not emit a [`Receiver`][ast] implementation. Types that do not +implement `Receiver` can still use `#[derive(SmartPointer)]`, but they can't be +used with dynamic dispatch directly. + +The raw pointer type would be an example of a type that (behaves like it) is +annotated with `#[derive(SmartPointer)]` without an implementation of +`Receiver`. In the case of raw pointers, you can coerce from `*const MyStruct` +to `*const dyn MyTrait`, but you must first convert them to a reference before +you can use them for dynamic dispatch. + +## Vtable requirements + +As seen in the `Rc` example, the macro needs to be usable even if the pointer +is `NonNull>` (as opposed to `NonNull`). + +## `PinCoerceUnsized` + +The standard library defines the following unstable trait: +```rust +/// Trait that indicates that this is a pointer or a wrapper for one, where +/// unsizing can be performed on the pointee when it is pinned. +/// +/// # Safety +/// +/// If this type implements `Deref`, then the concrete type returned by `deref` +/// and `deref_mut` must not change without a modification. The following +/// operations are not considered modifications: +/// +/// * Moving the pointer. +/// * Performing unsizing coercions on the pointer. +/// * Performing dynamic dispatch with the pointer. +/// * Calling `deref` or `deref_mut` on the pointer. +/// +/// The concrete type of a trait object is the type that the vtable corresponds +/// to. The concrete type of a slice is an array of the same element type and +/// the length specified in the metadata. The concrete type of a sized type +/// is the type itself. +pub unsafe trait PinCoerceUnsized: CoerceUnsized {} + +impl CoerceUnsized> for Pin +where + T: PinCoerceUnsized, +{} + +impl DispatchFromDyn> for Pin +where + T: PinCoerceUnsized + DispatchFromDyn, +{} +``` +The trait is implemented for all standard library types that implement +`CoerceUnsized`. + +Although this RFC proposes to add the `PinCoerceUnsized` trait to ensure that +unsizing coercions of pinned pointers cannot be used to cause unsoundness, the +RFC does not propose to stabilize the trait. + +# Drawbacks +[drawbacks]: #drawbacks + +- Stabilizing this macro limits how the underlying traits can be changed in the + future, since we cannot change them in ways that make it impossible to + implement the macro as-is. + +- Stabilizing this macro reduces the incentive to stabilize the underlying + traits, meaning that it may take significantly longer before we do so. This + RFC does not include support for coercing transparent containers like + [`Cell`], so hopefully that will be enough incentive to continue work on the + underlying traits. + +- This would be the first example in the standard library of a derive macro that + does not implement a trait of the same name as the macro. (However, there are + examples of macros that implement multiple traits: `#[derive(PartialEq)]` + also implements `StructuralPartialEq`.) + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## Why only stabilize a macro? +[why-only-macro]: #why-only-stabilize-a-macro + +This RFC proposes to stabilize the `#[derive(SmartPointer)]` macro without +stabilizing what it expands to. This effectively means that the macro is the +only way to use these features for custom types. The rationale for this is that +we currently don't know how to stabilize the traits, and that this is a serious +blocker for making progress on this issue. Stabilizing the macro will unblock +projects that wish to define custom smart pointers, and does not prevent +evolution of the underlying traits. + +See also [the section on prior art][prior-art], which discusses a previous +attempt to stabilize the underlying traits. + +## Receiver and Deref traits + +The vast majority of custom smart pointers will implement `Receiver` (often via +`Deref`, which results in a `Receiver` impl due to the blanket impl). So why +not also emit a `Receiver`/`Deref` impl in the output of the macro. One +advantage of doing so is that this may sufficiently limit the macro so that we +do not need to solve the pin soundness issue discussed in [the unresolved +questions section][unresolved-questions]. + +However, it turns out that there are quite a few different ways we might +implement `Deref`. For example, consider [the custom `Rc` example][custom-rc]: +```rust +#[derive(SmartPointer)] +#[repr(transparent)] +pub struct Rc { + inner: NonNull>, +} + +struct RcInner { + refcount: usize, + value: T, +} + +impl Deref for Rc { + type Target = T; + fn deref(&self) -> &T { + let ptr = self.inner.as_ptr(); + unsafe { &*ptr.value } + } +} +``` +Making the macro general enough to generate `Deref` impls that are _that_ +complex would not be feasible. And it doesn't make sense to stabilize the macro +without support for the custom `Rc` case, as implementing a custom `Arc` in the +Linux Kernel is the primary motivation for this RFC. + +Note that having the macro generate a `Receiver` impl instead doesn't work +either, because that prevents the user from implementing `Deref` at all. (There +is a blanket impl of `Receiver` for all `Deref` types.) + +## Transparent containers + +Smart pointers are not the only use case for implementing the [`CoerceUnsized`] +and [`DispatchFromDyn`] traits. They are also used for "transparent containers" +such as [`Cell`]. That use-case allows coercions such as `Cell>` +to `Cell>`. (Coercions where the `Cell` is inside the `Box` are +already supported on stable Rust.) + +It is not possible to use the derive macro proposed by this RFC for transparent +containers because they require a different set of where bounds when +implementing the traits. To compare: +```rust +// smart pointer example +impl DispatchFromDyn> for Box +where + T: Unsize + ?Sized, + U: ?Sized, +{} + +// transparent container example +impl DispatchFromDyn> for Cell +where + T: DispatchFromDyn, +{} +``` +Attempting to annotate `#[derive(SmartPointer)]` onto a transparent container +will fail to compile because [it violates the rules for implementing +`DispatchFromDyn`][tc-pg]. Supporting custom transparent containers is out of +scope for this RFC. + +[tc-pg]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=c3fe2a11822e4c5e2dae5bfec9d77b9e + +## Why not two derive macros? + +The derive macro generates two different trait implementations: + +- [`CoerceUnsized`] that allows conversions from `SmartPtr` to + `SmartPtr`. +- [`DispatchFromDyn`] that allows conversions from `SmartPtr` to + `SmartPtr`. + +It could be argued that these should be split into two separate derive macros. +We are not proposing this for a few reasons: + +- If there are two derive macros, then we have to support the case where you + only use one of them. There isn't much reason to do that, and the authors are + not aware of any examples where you would prefer to implement one of the + traits without implementing both. + +- Having two different macros means that we lock ourselves into solutions that + involve two traits that split the feature in the way that we split it today. + However, it is easy to imagine situations where we would want to split the + traits in a different way. For example, we might instead want one trait for + smart pointers, and another trait for transparent containers. Or maybe we just + want one trait that does both things. + +- The authors believe that a convenience `#[derive(SmartPointer)]` macro will + continue to make sense, even once the underlying traits are stabilized. It is + significantly easier to use than the expansion. + +- If we want the macro to correspond one-to-one to the underlying traits, then + we would want to use the same names as the underlying traits. However, we + don't know what the traits will be called when we finally figure out how to + stabilize them. (One of the traits have already been renamed once!) + +Even raw-pointer-like types that do not implement `Receiver` still want to +implement `DispatchFromDyn`, since this allows you to use them as the field +type in other structs that use `#[derive(SmartPointer)]`. For example, the +custom `Rc` has a field of type `NonNull`, and this works since `NonNull` is +`DispatchFromDyn`. + +[`Cell`]: https://doc.rust-lang.org/stable/core/cell/struct.Cell.html + +## What about `#[pointee]`? + +This RFC currently proposes to mark the generic parameter used for dynamic +dispatch with `#[pointee]`. For convenience, the RFC proposes that this is only +needed when there are multiple generic parameters. + +There are potential use-cases for smart pointers with additional generic +parameters. Specifically, the `ListArc` type used by the linked lists currently +has an additional const generic parameter to allow you to use the same +refcounted value with multiple lists. People have argued that it would be +better to change this to a generic type instead of a const generic, so it would +be useful to keep the option of having multiple generic types on the struct. + +### Conflicts with third-party derive macros + +The `#[pointee]` attribute could in principle conflict with other derive macros +that also wish to annotate one of the parameters with an attribute called +`#[pointee]`. To disambiguate such cases, we also allow the attribute to be +spelled `#[smart_pointer::pointee]`. + +It is an error to specify both `#[pointee]` and `#[smart_pointer::pointee]`, so +both macros must support this kind of disambiguation. + +Another way to avoid conflicts between `#[derive(SmartPointer)]` and third-party +macros is to always assume that the first generic parameter is the pointee. +This RFC does not propose that solution because: + +* It prevents the pointee from having a default unless it is the only parameter, + because parameters with a default must come last. +* If logic such as "the first parameter" becomes commonplace in macro design, + then it does not really solve the issue with conflicts: you could have two + macros that both assume that the first parameter is special. And this kind of + conflict will be more common than attribute conflicts, because the attribute + will only conflict if both macros use an attribute of the same name. + +The authors are not aware of any macros using a `#[pointee]` attribute today. + +## Derive macro or not? + +Stabilizing this as a derive macro more or less locks us in with the decision +that the compiler will use traits to specify which types are compatible with +trait objects. However, one could imagine other mechanisms. For example, stable +Rust currently has logic saying that any struct where the last field is `?Sized` +will work with unsizing operations. (E.g., if `Wrapper` is such a struct, then +you can convert from `Box>` to `Box>`.) That +mechanism is not specified using a trait. + +However, using traits for this functionality seems to be the most flexible. To +solve the unresolved questions, we most likely need to constrain the +implementations of these traits for `Pin` with stricter trait bounds than what +is specified on the struct. That will get much more complicated if we use a +mechanism other than traits to specify this logic. + +## `PinCoerceUnsized` + +Beyond the addition of the `#[derive(SmartPointer)]` macro, this RFC also +proposes to add a new unstable trait called `PinCoerceUnsized`. This trait is +necessary because the API proposed by this RFC would otherwise by unsound: + +> You could use `Pin::new` to create a `Pin>` and coerce +> that to `Pin>`. Then, if `SmartPtr` has a malicious +> implementation of the `Deref` trait, then `deref` could return a `&mut dyn +> Future` whose concrete type is not `MyUnpinFuture`, but instead some other +> future type that *does* need to be pinned. Since no unsafe code is involved in +> any of these steps, this means that we are able to safely create a pinned +> pointer to a value that has not been pinned. + +Adding the unsafe `PinCoerceUnsized` trait ensures that the user cannot coerce +`Pin>` to `Pin>` without using +unsafe to promise that the concrete type returned when calling `deref` on the +resulting `Pin>` is `MyUnpinFuture`. + +This RFC does not propose to stabilize `PinCoerceUnsized` because of naming +issues. If we do not know whether `CoerceUnsized` will still use that name when +we stabilize it, then we can't stabilize a trait called `PinCoerceUnsized`. +Furthermore, the Linux kernel (which forms the motivation for this RFC) does not +currently need it to be stabilized. + +There are some alternatives to `PinCoerceUnsized`. The primary contender for an +alternative solution is `DerefPure`. However, that solution involves a minor +breaking change, and we can always decide to switch to `DerefPure` later even if +we adopt `PinCoerceUnsized` now. + +### `StableDeref` + +A previous version of this RFC proposed to instead add a trait called +`StableDeref` that pretty much had the same requirements as `PinCoerceUnsized`, +except that it also required the address returned by `deref` to be stable. + +The motivation behind adding a `StableDeref` trait instead of `PinCoerceUnsized` +is that `StableDeref` would also be useful for other things, and that both +traits essentially just say that the `Deref` implementation doesn't do anything +unreasonable. The requirement that the address is stable is not strictly +required to keep the API sound, but semantically it is incoherent to have a +pinned pointer whose address can change, so it is not overly burdensome to +require it. + +However, this suggestion was abandoned due to an inconsistency with the +`StableDeref` trait defined by the ecosystem. That trait requires that raw +pointers to the contents of the pointer stay valid even if the smart pointer is +moved, but this is not satisfied by `Box` or `&mut T` because moving these +pointers asserts that they are unique. This is a problem because whichever trait +we use for pinned unsizing coercions, it *must* be implemented by `Box` and +`&mut T`. + +### `DerefPure` + +In a similar manner to the `StableDeref` option, we can use the existing +`DerefPure` trait. This option is a reasonable way forward, but this RFC does +not propose it because it would be a breaking change. (Note that `StableDeref` +is also a breaking change for the same reason.) + +Basically, the problem is that `Deref` is a supertrait of `DerefPure`, but there +are a few types that can be coerced when pinned that do not implement `Deref`. +For example, this code compiles today: +```rust +trait MyTrait {} +impl MyTrait for String {} + +fn pin_cell_map(p: Pin>>) -> Pin>> { + p +} +``` +The `Cell` type does not implement `Deref`, but the above code still compiles. +Note that since all methods on `Pin` _do_ require `Deref`, such pinned pointers +are useless and impossible to construct. But it is a breaking change +nonetheless. + +If this breakage is considered acceptable, then using `DerefPure` instead of a +new `PinCoerceUnsized` would be a reasonable way forward. + +### Make the derive macro unsafe + +We could just make the macro unsafe in a similar vein to [the unsafe attributes +RFC][unsafe-attribute]. +```rust +// SAFETY: The Deref impl is not malicious. +#[unsafe(derive(SmartPointer))] +#[repr(transparent)] +pub struct Rc { + inner: NonNull>, +} +``` +This would solve the unsoundness, but this RFC does not propose it because it +raises forwards compatibility hazards. We might start out with an unsafe derive +macro, and then in the future we might decide to instead use the +`PinCoerceUnsized` solution. Then, `#[unsafe(derive(SmartPointer))]` would have +to generate an implementation of `PinCoerceUnsized` trait too, because otherwise +`#[unsafe(derive(SmartPointer))] Pin>` would lose the ability to be +unsize coerced, which would be a breaking change. This means that +`#[unsafe(derive(SmartPointer))]` and `#[derive(SmartPointer)]` could end up +expanding to _different_ things. + +### Negative trait bounds + +There are also various solutions that involve negative trait bounds. For +example, you might instead modify `CoerceUnsized` like this: +```rust +// Permit going from `Pin` to` Pin` +impl CoerceUnsized> for Pin

+where + P: CoerceUnsized, + P: Deref, + U: Deref, +{ } + +// Permit going from `Pin` to `Pin` +impl CoerceUnsized> for Pin

+where + P: CoerceUnsized, + P: core::ops::Deref, + U: core::ops::Deref, +{ } +``` +This RFC does not propose it because it is a breaking change and the +`PinCoerceUnsized` or `DerefPure` solutions are simpler. This solution is +discussed in more details in [the pre-RFC for stabilizing the underlying +traits][pre-rfc]. + +# Prior art +[prior-art]: #prior-art + +## Stabilizing subsets of features + +There are several prior examples of unstable features that have been blocked +from stabilization for various reasons, where we have been able to make +progress by reducing the scope and stabilizing a subset. + +- The most recent example of this is [the arbitrary self types RFC][ast], where + [it was proposed to reduce the scope][ast-scope] so that we do not block + progress on the feature. +- Another example of this is [the async fn in traits feature][rpit]. This was + stabilized even though it is not yet advisable to use it for traits in the + public API of crates, due to missing parts of the feature. + +There have already been [previous attempts to stabilize the underlying +traits][pre-rfc], and they did not make much progress. Therefore, this RFC +proposes to reduce the scope and instead stabilize a derive macro. + +[ast-scope]: https://github.com/rust-lang/rfcs/pull/3519#discussion_r1492385549 +[rpit]: https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html + +## Macros whose output is unstable + +The Rust testing framework is considered unstable, and the only stable way to +interact with it is via the `#[test]` attribute macro. The macro's output uses +the unstable internals of the testing framework. This allows the testing +framework to be changed in the future. + +Note also that the `pin!` macro expands to something that uses an unstable +feature, though it does so for a different reason than +`#[derive(SmartPointer)]` and `#[test]`. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +Bikeshedding over the name remains. + +The name `#[derive(SmartPointer)]` leaves some things to be desired, as smart +pointers would generally want to implement some traits that this macro does +*not* expand to. Most prominently, any smart pointer should implement `Deref` or +`Receiver`. Really, the macro just says that this pointer works with unsizing +and dynamic dispatch. + +We will settle on the final name prior to stabilization. + +# Future possibilities +[future-possibilities]: #future-possibilities + +One of the design goals of this RFC is that it should make this feature +available to crates without significantly limiting how the underlying traits +can evolve. The authors hope that we will find a way to stabilize the +underlying traits in the future. + +One of the things that is left out of scope of this RFC is coercions involving +custom transparent containers similar to [`Cell`]. They require you to implement +the traits with different where bounds. Adding support for custom transparent +containers makes sense as a future expansion of the feature. + +There is a reasonable change that we may be able to lift some of [the +restrictions][input-requirements] on the shape of the struct as well. The +current restrictions are just whatever [`DispatchFromDyn`] requires today, and +proposals for relaxing them have been seen before (e.g., in the +[pre-RFC][pre-rfc].) + +One example of a restriction that we could lift is the restriction that there is +only one non-zero-sized field (i.e., that it must be `#[repr(transparent)]`). +This would allow smart pointers to use custom allocators. (Today, types like +`Box` and `Rc` only work with trait objects when using the default zero-sized +allocator.) + +This could also allow implementations of `Rc` and `Arc` that store the value and +refcount in two different allocations, like how the C++ `shared_ptr` works. +```rust +#[derive(SmartPointer)] +pub struct Rc { + refcount: NonNull, + value: NonNull, +} +``` +Implementing this probably requires the `#[derive(SmartPointer)]` macro to know +syntactically which field holds the vtable. One simple way to do that could be +to say that it must be the last field, analogous to the unsized field in structs +that must also be the last field. Another option is to add another attribute +like `#[pointee]` that must be annotated on the field in question. + +[ast]: https://github.com/rust-lang/rfcs/pull/3519 +[pre-rfc]: https://internals.rust-lang.org/t/pre-rfc-flexible-unsize-and-coerceunsize-traits/18789 +[`CoerceUnsized`]: https://doc.rust-lang.org/stable/core/ops/trait.CoerceUnsized.html +[`core::ops::CoerceUnsized`]: https://doc.rust-lang.org/stable/core/ops/trait.CoerceUnsized.html +[`DispatchFromDyn`]: https://doc.rust-lang.org/stable/core/ops/trait.DispatchFromDyn.html +[`core::ops::DispatchFromDyn`]: https://doc.rust-lang.org/stable/core/ops/trait.DispatchFromDyn.html +[unsafe-attribute]: https://github.com/rust-lang/rfcs/pull/3325