Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define raw pointer transmute behavior #1661

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

joshlf
Copy link
Contributor

@joshlf joshlf commented Oct 23, 2024

This is needed in order to support generic fat pointer casts in a const context as described here: google/zerocopy#1967

@traviscross
Copy link
Contributor

cc @rust-lang/opsem

@RalfJung
Copy link
Member

RalfJung commented Oct 23, 2024 via email

@RalfJung
Copy link
Member

RalfJung commented Oct 23, 2024 via email

@traviscross
Copy link
Contributor

@rustbot author

@joshlf: You'll want to revise the proposed language based on @RalfJung's feedback above.

@rustbot rustbot added the S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. label Oct 23, 2024
@joshlf
Copy link
Contributor Author

joshlf commented Oct 23, 2024

@RalfJung Maybe you can give me advice on whether it's possible to phrase this formally. What I'd like to say is: "transmuting is equivalent to as casting so long as both pointers have the same metadata type". In particular this would support:

  • thin pointers
  • slice DST pointers
  • dyn pointers (where you don't change the trait - ie, you don't perform an upcast)

The reason that phrasing it in generic terms rather than explicitly enumerating the cases is that it permits generic unsafe code to be well-defined. In particular, we are working on a trait that looks something like:

/// # Safety
///
/// If `U: CastFrom<T>`, then given `t: *mut T`, `transmute::<_, *mut U>(t)` is sound and produces a pointer which
/// addresses the same number of bytes as `t`.
unsafe trait CastFrom<T: ?Sized> {}

This then allows us to write the following:

const fn cast<T: ?Sized, U: ?Sized + CastFrom<T>>(t: *mut T) -> *mut U {
    // NOTE: This can't be an `as` cast because Rust doesn't know that the vtable kinds match.
    unsafe { core::mem::transmute(t) }
}

We'd like to be able to implement CastFrom for container types over all possible T, e.g.:

unsafe impl<T: ?Sized> CastFrom<T> for ManuallyDrop<T> {}

This impl is only sound if we're able to add generic text to the effect of "transmuting is equivalent to as casting so long as both pointers have the same metadata type", but is not sound if we are only able to enumerate the cases specifically (since there's no way to prove that, for generic T: ?Sized, *mut T and *mut ManuallyDrop<T> satisfy one of the cases).

So my question is: Is there nomenclature already defined whose meaning matches what I'm aiming for here?

@CAD97
Copy link

CAD97 commented Oct 23, 2024

Is there nomenclature already defined whose meaning matches what I'm aiming for here?

The language to address each unsize kind separately exists (we differentiate between pointer casts that adjust the vtable and ones that don't), but I don't think there's established language to fully generally refer to compatible metadata. While I don't have a reference for official usage of the term, I've seen and used "unsize kind" plenty to generalize "vtable kind" to any pointee metadata.

What could potentially be said is that transmuting raw pointers will transmute the associated Pointee::Metadata, and leave it to DynMetadata to define when it's acceptable to transmute from DynMetadata<Trait + Bounds + A> to DynMetadata<Trait + Bounds + B>. More restrictive and I think sufficient for what you want is requiring identical metadata type, forbidding e.g. *mut dyn Trait + Send as *mut dyn Trait.

@scottmcm
Copy link
Member

My instinct here (with my lang hat on but not speaking for the team) is that saying this for every fat pointer is premature, because we don't know what all potential fat pointers look like going forward.

Do you really need everything? Would slices be enough? Do you need dyn too?

One way forward that I think there's appetite for doing (see https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/Can.20we.20stabilize.20the.20layout.20of.20.26.5BT.5D.20and.20.26str.3F/near/395760337) is defining the layout for references- and pointers-to-slices, which would be another way to start allowing things like this.

But also, why start from asking about defining transmutes for this rather than asking for APIs that can do it, if the goal is just to go from *T to *U (as opposed to doing that in something nested)?

@joshlf
Copy link
Contributor Author

joshlf commented Oct 24, 2024

Do you really need everything? Would slices be enough? Do you need dyn too?

The problem with not supporting everything is that then we can't support generic conversions (reproducing my example from #1661 (comment)):

/// # Safety
///
/// If `U: CastFrom<T>`, then given `t: *mut T`, `transmute::<_, *mut U>(t)` is sound and produces
/// a pointer which addresses the same number of bytes as `t`.
unsafe trait CastFrom<T: ?Sized> {}

const fn cast<T: ?Sized, U: ?Sized + CastFrom<T>>(t: *mut T) -> *mut U {
    // NOTE: This can't be an `as` cast because Rust doesn't know that the vtable kinds match.
    unsafe { core::mem::transmute(t) }
}

unsafe impl<T: ?Sized> CastFrom<T> for ManuallyDrop<T> {}

In particular, the author of the unsafe impl block needs to know that transmuting *mut T into *mut ManuallyDrop<T> is sound for all T: ?Sized. Rust already permits this via as cast; the following compiles:

fn cast_to_manually_drop<T: ?Sized>(t: *mut T) -> *mut ManuallyDrop<T> {
    t as *mut _
}

So I'm not proposing to stabilize any new conversion behavior; I'm only proposing to stabilize that transmute is equivalent to as for such conversions.

But also, why start from asking about defining transmutes for this rather than asking for APIs that can do it, if the goal is just to go from *T to *U (as opposed to doing that in something nested)?

The problem is that I need to support an API like cast above, which needs to be a const fn. Note that cast takes two unrelated T and U - it is able to rely on T and U having compatible metadata thanks to U: CastFrom<T>, not based on anything that Rust has visibility into - in other words, t as *mut U would not compile. (I'm happy to go into more detail about why this is something we need if folks are curious, but I'll leave it at that for now.)

We can already support this today if we're willing to not support const fn; we can write something like:

unsafe trait CastFrom<T: ?Sized> {
    fn cast_from(t: *mut T) -> *mut Self;
}

The problem is that you can't call <U as CastFrom<T>>::cast_from in a const fn.

@RalfJung
Copy link
Member

The current docs we have on this say that *T to *V is allowed when
"T and V are compatible unsized types, e.g., both slices, both the same trait object".

That seems to be outdated? We allow dyn trait upcasting and discarding auto traits, don't we?
Cc @rust-lang/types

@traviscross
Copy link
Contributor

traviscross commented Oct 25, 2024

That seems to be outdated? We allow dyn trait upcasting and discarding auto traits, don't we?

We do allow dropping auto traits, and in Rust 1.84, we'll allow dropping the principal:

On dyn trait upcasting, we still need to restabilize that after having landed, in Rust 1.81:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-lang-nominated S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants