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

RFC: Coercible and HasPrefix for Zero Cost Coercions #91

Closed
wants to merge 3 commits into from

Conversation

gereeter
Copy link

This is largely based on (with lots of copied text) @glaebhoerl's proposal in rust-lang/rust#9912, but with a few changes:

  • I formalized the idea of "contexts" by using GHC's roles.
  • I used this role system to include user-defined pointers and data structures.
  • I dropped contravariance for simplicity. This may have been a bad idea, but it's unclear how useful contravariance is.

rendered draft

vtable, and is considered a built-in "kind" alongside `Copy`, `Send`, etc.

Where single inheritance and subtyping conflate many different ideas, among them transparenta
ccess to superstruct fields, zero-cost conversion from sub- to supertypes, and these conversions
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo ("transparenta ccess").


The trait is wired-in to the compiler, and user-defined impls of it are highly restricted as
described in the implementation section talking about roles. `coerce()` would coerce between
any two types where the target type "is a proper subtype of" the input type. Note that `coerce`
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does it need to be a proper subtype? This would prevent coercing between two types that are considered equivalent. For example, between a newtype and the underlying uint (which is to say, you could define the coercion in one direction, but not the other).

Copy link
Author

Choose a reason for hiding this comment

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

Good point - I'm not sure why I kept the word proper in there. I'll remove it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think my original intent here with "proper subtype" was just to emphasize the distinction from merely "has prefix". For "strictly fewer inhabitants" I would have said "strict subtype".

@glaebhoerl
Copy link
Contributor

Huge thanks for taking this up. I'll try to leave a few comments.

## Existing `transmute`s

There are various calls to `transmute` scattered throughout the Rust codebase, and a
number of them are perfectly safe. It would be beneficial to write these in a way that
Copy link
Contributor

Choose a reason for hiding this comment

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

I would hope that all of them are perfectly safe, because otherwise it's a bug! I think you just meant to say that the could compiler could verify their safety under this proposal. :)

Copy link
Author

Choose a reason for hiding this comment

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

They aren't all safe, and it is a bug: rust-lang/rust#13933. I think there are others, but those are the most blatant.

@emberian
Copy link
Member

emberian commented Jun 4, 2014

@glaebhoerl
Copy link
Contributor

FWIW if I were writing this proposal today, I would go with this naming scheme:

  • unsafe fn transmute<T, U>(T) -> U -> unsafe fn unsafe_transmute<T, U>(T) -> U
  • trait Coercible<T> -> trait Transmute<T>
  • fn coerce<U, T: Coercible<U>>(T) -> U -> fn transmute<U, T: Transmute<U>>(T) -> U

In other words, rename the existing transmute to unsafe_transmute and use Transmute and transmute instead of Coercible and coerce.

This is more consistent with Rust's existing terminology which uses "transmute" to mean the thing we are doing here, and "coerce" to mean something different (not-necessarily-free compiler-inserted casts). It also happens to directly mirror GHC's (quite logical) scheme of unsafeCoerce, Coercible, and coerce, except using the word "transmute" instead of "coerce".

I still don't have a better idea for what to call HasPrefix.

@brson
Copy link
Contributor

brson commented Jun 25, 2014

Discussed at https://github.com/rust-lang/meeting-minutes/blob/master/weekly-meetings/2014-06-24.md#rfc-pr-91-coerciblehasprefix-httpsgithubcomrust-langrfcspull91-

Ergonomics in general is an area we want to focus on leading up to 1.0, and coercions probably have a role. We're not ready to make any decisions on the subject yet. Closing, but tagging with 'postponed' so it can be referenced later.

@brson brson closed this Jun 25, 2014
@glaebhoerl
Copy link
Contributor

Ergonomics in general is an area we want to focus on leading up to 1.0, and coercions probably have a role.

This is much more about expressiveness, safety and performance than about ergonomics. The name "coercible" was misleading in this context, because Rust uses it to mean something different (see previous comment).

I agree that implementing this is not urgent, just wanted to clarify.

@glaebhoerl
Copy link
Contributor

So... I thought I made a discovery, inspired by an email from Edward Kmett, and then I re-read the RFC, and realized that it already does things exactly the way I was going to suggest, following my "discovery". The discovery was that we can completely avoid the need to have explicit role/variance annotations on type parameters (e.g. struct Foo<covariant T>) by just writing out the equivalent generic Transmute impls instead:

  • Instead of struct Foo<bivariant T>: ("bivariant" is another name for "phantom")

    // `Foo` doesn't actually contain a `T`, so we can `transmute` the `T` to any other type
    impl<T, U> Transmute<Foo<U>> for Foo<T> { }
    
  • Instead of struct Foo<covariant T>:

    // (Here `Foo` contains `T` only in "positive" positions.)
    // We can `transmute` `Foo<T>` to `Foo<U>` iff we can transmute `T` to `U`, 
    // i.e. if `U` is a "subtype" of `T`.
    impl<U, T: Transmute<U>> Transmute<Foo<U>> for Foo<T> { }
    
  • Instead of struct Foo<contravariant T>:

    // `Foo` uses `T` only in "negative" positions, e.g. function parameters,
    //  so this is the reverse of the above.
    impl<U, T: Transmute<U>> Transmute<Foo<T>> for Foo<U> { }
    
  • Instead of struct Foo<invariant T>: ("invariant" is another name for "representational")

    // `Foo` uses `T` in both positive and negative positions, 
    // so we can only `transmute` between `Foo<T>` and `Foo<U>` if they're "subtypes"
    // of each other in *both* directions, e.g. if one is a newtype of the other.
    impl<T, U> where T: Transmute<U>, U: Transmute<T> Transmute<Foo<U>> for Foo<T> { }
    
  • Instead of struct Foo<T>: (a completely fixed type parameter is also referred to as "nominal")

    // (nothing)
    // This is the case for types which not only use `T`, but also depend
    // in some way on its precise *identity*, as opposed to merely its representation. 
    // A good example is the `K` in `HashMap<K, V>`: you can't allow transmuting
    // it to any other type, because that other type might have a different impl of `Hash`,
    // which would break the internal invariants of the `HashMap`.
    

But, as I mentioned above, the RFC already specifies exactly this:

To declare the roles of each parameter, users must write impls of Coercible that follow the above patterns - each variable must be independent and only bounded by either HasPrefix or Coercible. By default, every parameter is nominal.

So I commend @gereeter's brilliance for realizing this before anyone else.

I think I was mislead by the earlier statement that:

To deal with data structures and pointers, we need to introduce the concept of roles,

which I unthinkingly assumed implied explicit nominal, phantom, and/or representational annotations along the same lines as GHC. Whereas by just writing the equivalent generic Transmute impls instead, I would say that we are avoiding the need to "introduce the concept of roles".

(Which is a very good thing!)

@jsgf
Copy link

jsgf commented Jun 8, 2017

How does this RFC look through the eyes of 2017 Rust?

@Centril Centril added the A-coercions Proposals relating to coercions. label Nov 26, 2018
wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019
wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019
…experimental-hooks

Promote buildInstrumentation to public API
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-coercions Proposals relating to coercions. postponed RFCs that have been postponed and may be revisited at a later time.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants