-
Notifications
You must be signed in to change notification settings - Fork 13
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
Safe Transmute RFC #5
Conversation
This comment has been minimized.
This comment has been minimized.
typo fixes from @jonas-schievink Co-authored-by: Jonas Schievink <[email protected]>
#### Stability & Transmutation | ||
A `Src` type is *stably* transmutable into a `Dst` type *only if* `<Src as PromiseTransmutableInto>::Archetype` is transmutable, stability notwithstanding, into `<Dst as PromiseTransmutableFrom>::Archetype`; formally: | ||
```rust | ||
unsafe impl<Src, Dst> TransmuteFrom<Src> for Dst |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... you mention TransmuteFrom here which you've hinted at above but never formally introduce, and you never explicitly state that this will be implemented by the compiler on behalf of the user automatically.
rfcs/0000-safe-transmute.md
Outdated
|
||
The type `<Src as PromiseTransmutableInto>::Archetype` exemplifies the furthest extreme of non-breaking changes that could be made to the layout of `Src` that could affect its use as a source type in transmutations. Conversely, `<Dst as PromiseTransmutableFrom>::Archetype` exemplifies the furthest extreme of non-breaking changes that could be made to the layout of `Dst` that could affect its use as a destination type in transmutations. If a transmutation between these extremities is valid, then so is `Src: TransmuteInto<Dst>`. | ||
|
||
#### Common Use-Case: Promising a Fixed Layout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've mention this before but I would really love a short-hand for types that implement PromiseTransmutableFrom<Archetype=Self>
and PromiseTransmutableInto<Archetype=Self>
- something like FixedLayout
.
This would also allow us to introduce FixedLayout
first if we wanted to let that use case bake before we allowed flexibly declaring stability guarantees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rylev I'm unsure how useful this shorthand would be.
For end-users, I anticipate that PromiseTransmutableFrom
and PromiseTransmutableInto
will virtually never appear outside of the context of implementing those traits. In writing the examples for this RFC, I haven't yet encountered an instance where I needed to use either of those traits in a where
bound. So, I don't think this shorthand would be useful outside of the context of implementing the traits.
And, within the context of implementing those traits, nearly all users will be using the shorthand #[derive(PromiseTransmutableFrom, PromiseTransmutableInto)]
. So here, #[derive(FixedLayout)]
would be a shorthand for a shorthand.
Is there a use-case you have in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short-hand for short-hand is not necessarily a bad thing 😄 . I believe a vast majority of the time that users will want #[derive(PromiseTransmutableFrom, PromiseTransmutableInto)]
. Given the long names and the frequency of its use #[derive(FixedLayout)]
strikes me as an ergonomic choice. But I realize that it's not super elegant to introduce a new trait just as a short-hand for deriving.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that's a better trait name for sure. People will understand it more when looking at docs and source.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rylev Defining a trait is rather tricky (since those derives are a little more nuanced than just Archetype = Self
to account for field stability declarations), but it's very technically feasible to provide a derive(PromiseTransmutableFromAndInto)
macro that would be equivalent to derive(PromiseTransmutableFrom, PromiseTransmutableInto)
— without a PromiseTransmutableFromAndInto
trait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defining a trait is rather tricky (since those derives are a little more nuanced than just Archetype = Self to account for field stability declarations)
To clarify a little more about why having a FixedLayout
shorthand/trait is tricky, it's because the concept of "fixed layout" is a little tricky.
First, there's the name: PromiseTransmutableFrom
isn't really making a promise about layout, it's making a promise about future transmutability. Transmutability involves factors beyond layout, like constructability. If we wanted to create a derive
shorthand for derive(PromiseTransmutableFrom, PromiseTransmutableInto)
, the name PromiseFixedLayout
(or PromiseStableLayout
, etc...) misrepresents what's actually being promised.
Second, there's the semantics: what does promising a fixed layout mean? You're probably mean to promise that you won't make changes to the type's transmutability, but what about others? What if your type's fields come from other crates? What if your type's fields are generic?
The behavior of #[derive(PromiseTransmutableFrom, PromiseTransmutableInto)]
reflects this inherent complexity: it doesn't mean your type has totally-assured-transmutability, it means your type has as-assured-as-possible-transmutability. Concretely: When you write #[derive(PromiseTransmutable{From,Into})]
, your type inherits the instability of its fields. If its fields have strong stability promises, so too will your type; if its fields have weak stability promises, so too will your type.
To conclude, I don't think there's a good mapping of a mem-markers-style FixedLayout
abstraction onto PromiseTransmutableFrom
and PromiseTransmutableInto
. While it might seem simpler, at first glance, to just present one trait (FixedLayout
) instead of two (PromiseTransmutableFrom
and PromiseTransmutableInto
), the concept of a FixedLayout
isn't well-defined, and the complexities of PromiseTransmutable{From,Into}
reflect the complexity inherent to the problem of transmutation stability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I'm arguing here is that there is a use case for "POD" that will be quite common. By "POD" in this case I mean types where the in memory-representation of that type is intrinsic to its functionality and changing that would be a breaking change. This is common in the parsing scenario for instance. Having some sort of short hand for saying this type's layout/alignment/etc cannot change would be a simple "happy path" for many use cases. I'm not arguing this as a replacement to PromiseTransmutable{From,Into}
but as a compliment. This doesn't have to be a derive
either. I can imagine also #[repr(C, fixed)]
for instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per a conversation with @rylev, ed172fe proposes a #[derive(PromiseTransmutable)]
ergonomic extension. I'm fairly convinced that adding such a shorthand is the right thing to do and I refer to it in the Guide-Level Explanation, but it's formally defined as an Extension because it's technically non-central to the RFC and poses a few unusual design quirks.
|
||
The question is, then: *how can the author of a type reason about transmutations they did not write, from-or-to types they did not write?* We address this problem by introducing two traits which both allow an author to opt-in to stability guarantees for their types, and allow third-parties to reason at compile-time about what guarantees are provided for such types. | ||
|
||
#### `PromiseTransmutableFrom` and `PromiseTransmutableInto` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you feel about these names? Open to bike shedding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fond of these names because Promise
reflects that the user implements these traits to make a promise, and Transmutable{From,Into}
corresponds exactly to what they're promising. I'm quite open to bikeshedding, though!
First, thank you @jswrenn. This is an amazing document, and I think you're really on to something here. In general, I'm a bit concerned that the flexibility brings a lot of the complexity forward when the user might often need to think about the complexity. That's what I suggested the Beyond this, I'm wondering if validity currently captures two distinct ideas and whether it's worth it to treat these as separate. As far as I can tell validity captures both the idea of in memory values being valid representations of a type (e.g., 3u8 is not a valid representation of bool) as well as whether the value is a valid instance of the type given the current context. For instance, given a pointer wrapper type |
@rylev I think the distinction you're referring to is captured by the distinction the RFC draws between soundness and safety. I actually like this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per #5 (comment)
Kelly -> Kelley
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix #5 (comment)
rust-lang#5 (comment) Co-authored-by: Florian Gilcher <[email protected]>
Just curious, why is the versioning baked into the type system? Is this not solved by Cargo and version locking? I think this was one of the largest complexities I found while reading the proposal |
@JulianKnodt This is a great question! Why do we need stability?We need a stability system for at least two reasons:
Why is stability so complicated?I go into a ton of depth exploring the implications and design rationale behind stability, but I think it might be one of the simpler parts of the RFC. Whereas ensuring soundness and safety requires non-trivial compiler support, stability doesn't—it's just two normal traits and an pub trait PromiseTransmutableFrom
{
type Archetype
: TransmuteInto<Self, NeglectStability>
+ PromiseTransmutableFrom;
}
pub trait PromiseTransmutableInto
{
type Archetype
: TransmuteFrom<Self, NeglectStability>
+ PromiseTransmutableInto;
}
unsafe impl<Src, Dst> TransmuteFrom<Src, ()> for Dst
where
Src: PromiseTransmutableInto,
Dst: PromiseTransmutableFrom,
<Dst as PromiseTransmutableFrom>::Archetype:
TransmuteFrom<
<Src as PromiseTransmutableInto>::Archetype,
NeglectStability>
{} That's the entire stability system! In short: I think the depth I go into when documenting stability might make it seem more complicated than it is. I'm increasingly thinking that removing the in-depth explanations of stability from the RFC might be a good idea. UPDATE: I've incorporated this answer into the RFC. I've also removed the 'Dissecting Stability' and 'Uncommon Use-Case: Weak Stability Guarantees' sections. If anyone has objections, please let me know. |
This improves the rendering of rustdoc, and leaves us free to remove that bound if need be without violating stability.
The RFC is ready to formally submit! I'm going to merge this PR. I'll file a PR against rust-lang/rfcs either tonight or tomorrow morning. |
Continuation of rust-lang/project-safe-transmute#5
// | ^^^^^^^^^^^^^^ the trait `TransmuteFrom<foo::Foo, _>` is not implemented for `u32` | ||
// | | ||
// = note: required because of the requirements on the impl of `TransmuteInto<u32, _>` for `foo::Foo` | ||
// = note: byte 8 of the source type may be uninitialized; byte 8 of the destination type cannot be uninitialized. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this say "bit 8" instead of "byte 8" ?
Rendered
For a (quicker) summary of the proposed API surface, see this rustdoc.