Skip to content

Introduce a PinSafePointer trait that generalizes PinCoerceUnsized#156935

Open
Darksonn wants to merge 1 commit into
rust-lang:mainfrom
Darksonn:pin-safe-pointer
Open

Introduce a PinSafePointer trait that generalizes PinCoerceUnsized#156935
Darksonn wants to merge 1 commit into
rust-lang:mainfrom
Darksonn:pin-safe-pointer

Conversation

@Darksonn
Copy link
Copy Markdown
Member

@Darksonn Darksonn commented May 25, 2026

This PR renames PinCoerceUnsized to PinSafePointer and adds several new safety requirements about the implementations of various safe traits.

Closes: #152667
Closes: #147794

With this merged, the only remaining soundness issues with Pin are:

For your convenience here are the docs for the new trait:

PinSafePointer

Trait that indicates that this is a pointer that does not misbehave when combined with Pin.

Note that for backwards compatibility reasons, it is possible to create a Pin<P> for pointer types P that do not implement this trait. However, this can only be done safely if <P as Deref>::Target implements Unpin, which means that pinning has no effect.

Safety

Types that implement this trait must not provide "malicious" implementations of any safe traits used by Pin.

The pointer must always reference the same object

Calls to deref/deref_mut on the same Pin<P> instance must always refer to the same object. That is, the address returned by these methods must not change. This applies even if the pointer type is moved.

Furthermore, if the pointer type can participate in unsizing coercions or dynamic dispatch, then these coercions must also not change the underlying concrete type. Here, 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.

As an example, after unsizing coercing a pinned pointer, deref_mut must not return a #[repr(transparent)] wrapper around the value it referenced before being unsized, even if the address is unchanged.

The pointer must not move its pointee

The deref_mut method and the pointer type's destructor are called with a &mut self receiver, but they must behave as-if it was a self: Pin<&mut Self> receiver. That is, they must not move out of the underlying value.

As an example, deref_mut must not invoke swap on the inner value.

Shared access to the pointer

If this pointer type uses &P references as evidence that this value is not pinned, then it must not treat the &self argument passed to Clone or the formatting traits (fmt::Debug, fmt::Display, fmt::Pointer) as such evidence.

As an example, given a Pin<Arc<T>> there is no way to obtain an &Arc<T> (note that Deref just gives a &T). Because of this, the Arc type can assume that an &Arc<T> value can only exist if the T is not pinned, which justifies the soundness of the Arc::get_mut method.

Cloning pinned pointers

When a Pin<P> is cloned, the P pointer value returned by clone is passed to Pin::new_unchecked. The implementation of Clone must return a value such that this is sound.

For example, when a Pin<&T> is cloned, the resulting &T points at the same value. The value is known to be pinned since a Pin<&T> to it exists, so it is safe to wrap the &T returned by clone in Pin.

r? lcnr

@Darksonn Darksonn added T-lang Relevant to the language team T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. A-pin Area: Pin labels May 25, 2026
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 25, 2026
// safely as `Box::pin((*p).clone())`.
#[unstable(feature = "pin_coerce_unsized_trait", issue = "150112")]
unsafe impl<T: ?Sized, A: Allocator> PinCoerceUnsized for Box<T, A> {}
unsafe impl<T: ?Sized, A: Allocator + 'static> PinSafePointer for Box<T, A> {}
Copy link
Copy Markdown
Member Author

@Darksonn Darksonn May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added A: 'static bounds to these impls since you generally cannot pin values in memory from a non-static allocator.

View changes since the review

Comment thread library/core/src/pin.rs
/// [`clone`]: Clone::clone
/// [`Arc`]: ../../std/sync/struct.Arc.html "Arc"
/// [`Arc::get_mut`]: ../../std/sync/struct.Arc.html#method.get_mut "Arc::get_mut"
pub unsafe trait PinSafePointer: Deref + Sized {}
Copy link
Copy Markdown
Member Author

@Darksonn Darksonn May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a Sized bound here due to #156920 (comment). This doesn't actually affect any use-cases since Pin<P> already requires P: Sized, but seemed like a good idea nonetheless.

View changes since the review

@Darksonn Darksonn force-pushed the pin-safe-pointer branch from 83c7d24 to 23a34ef Compare May 25, 2026 21:56
Comment thread library/core/src/pin.rs
Comment on lines +1850 to +1852
/// Calls to [`deref`]/[`deref_mut`] on the same `Pin<P>` instance must always
/// refer to the same object. That is, the address returned by these methods
/// must not change. This applies even if the pointer type is moved.
Copy link
Copy Markdown
Member

@RalfJung RalfJung May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is still doing the thing where it talks about a DerefMut impl but DerefMut is not a supertrait of this trait. What are the valid ways in which an unsafe impl PinSafePointer can satisfy this requirement?

  • There's a DerefMut for the same set of types in the same crate: that's easy, we can just check the condition.
  • There's a negative DerefMut for the same set of types in the same crate: that's easy, no impl can exist so the condition is trivially satisfied.
  • Neither of these hold true. Now we... uh, look at the orphan rule and hope for the best? I am not sure that's a good idea, this is exactly what has bitten us in the past...

The same concern applies to the requirements on Clone, the formatting traits, and any others that I missed. (IMO the docs should call out more explicitly: If this type also implements Foo, then blah. If it also implements Bar, then blah. etc)

View changes since the review

Copy link
Copy Markdown
Member Author

@Darksonn Darksonn May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's best to avoid this kind of safety requirement, but I think we have no choice. Luckily we do not need to worry about dyn PinSafePointer.

This PR is adding comments to every impl of PinSafePointer explaining why the various trait implementations are compatible or cannot exist for the particular type in question. Please see the comments for various ways we can make these arguments.

Copy link
Copy Markdown
Member

@RalfJung RalfJung May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah in std we can do that because we have negative impls. But our users can't really do that... and the moment one wants to impl PinSafePointer for MyType<T> where MyType<T>: DerefMut for some but not all T, I don't see any way to make this work.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative impls are only required for fundamental types, so I'm not too concerned about that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With regards to types where MyType<T>: DerefMut for some but not all T, we do in fact have one instance of exactly that. Please see #145608 for the trick to make that sound.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have seen that PR and ran away screaming.^^

I really hope the reasoning is sound, but I do not have a coherent proof in my head -- it relies too much on orphan rule details.

Comment thread library/alloc/src/rc.rs
// with `Rc::get_mut`), this means that this type treats `&Rc<T>` as evidence
// that the `T` is not pinned. The implementations of various traits are written
// accordingly. Since this type is not fundamental, downstream crates cannot
// provide malicious implementations of any of the traits relevant for `Pin`.
Copy link
Copy Markdown
Member

@RalfJung RalfJung May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this relies on something about the orphan rule, right? That makes me uneasy.^^
An explicit impl !DerefMut for Rc would be a lot better IMO.

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, by the orphan rule downstream crates cannot implement DerefMut for Rc<_> under any scenario.

Copy link
Copy Markdown
Member

@RalfJung RalfJung May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are the current orphan rules. How do we know they will never be relaxed?

The only promise the orphan rules are making is "there will never be more than one impl for the same trait + type". I don't think we should rely on any property that goes beyond this, and I don't see how the argument here follows from that property.

Copy link
Copy Markdown
Member Author

@Darksonn Darksonn May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add a negative impl to simplify the reasoning for Rc, but I think there is no way out for the #145608 case unless someone comes up with an entirely different solution. It's simply unsound to add untrusted code to the coherence domain of core because it could implement DerefMut for Pin<LocalType> maliciously.

Comment thread library/core/src/pin.rs
Comment on lines +1875 to +1883
/// If this pointer type uses `&P` references as evidence that this value is not
/// pinned, then it must not treat the `&self` argument passed to [`Clone`] or
/// the formatting traits ([`fmt::Debug`], [`fmt::Display`], [`fmt::Pointer`])
/// as such evidence.
///
/// As an example, given a `Pin<Arc<T>>` there is no way to obtain an `&Arc<T>`
/// (note that `Deref` just gives a `&T`). Because of this, the [`Arc`] type can
/// assume that an `&Arc<T>` value can only exist if the `T` is not pinned,
/// which justifies the soundness of the [`Arc::get_mut`] method.
Copy link
Copy Markdown
Member

@RalfJung RalfJung May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize get_mut becomes even more subtle when pinning gets involved. Impressive.

View changes since the review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-pin Area: Pin S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-lang Relevant to the language team T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Projects

None yet

4 participants