-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
DST custom coercions. #982
Changes from all commits
bf8d81b
4d803c1
97452ca
7468c30
4b99006
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
- Feature Name: dst_coercions | ||
- Start Date: 2015-03-16 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
|
||
Custom coercions allow smart pointers to fully participate in the DST system. | ||
In particular, they allow practical use of `Rc<T>` and `Arc<T>` where `T` is unsized. | ||
|
||
This RFC subsumes part of [RFC 401 coercions](https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md). | ||
|
||
# Motivation | ||
|
||
DST is not really finished without this, in particular there is a need for types | ||
like reference counted trait objects (`Rc<Trait>`) which are not currently well- | ||
supported (without coercions, it is pretty much impossible to create such values | ||
with such a type). | ||
|
||
# Detailed design | ||
|
||
There is an `Unsize` trait and lang item. This trait signals that a type can be | ||
converted using the compiler's coercion machinery from a sized to an unsized | ||
type. All implementations of this trait are implicit and compiler generated. It | ||
is an error to implement this trait. If `&T` can be coerced to `&U` then there | ||
will be an implementation of `Unsize<U>` for `T`. E.g, `[i32; 42]: | ||
Unsize<[i32]>`. Note that the existence of an `Unsize` impl does not signify a | ||
coercion can itself can take place, it represents an internal part of the | ||
coercion mechanism (it corresponds with `coerce_inner` from RFC 401). The trait | ||
is defined as: | ||
|
||
``` | ||
#[lang="unsize"] | ||
trait Unsize<T: ?Sized>: ::std::marker::PhantomFn<Self, T> {} | ||
``` | ||
|
||
There are implementations for any fixed size array to the corresponding unsized | ||
array, for any type to any trait that that type implements, for structs and | ||
tuples where the last field can be unsized, and for any pair of traits where | ||
`Self` is a sub-trait of `T` (see RFC 401 for more details). | ||
|
||
There is a `CoerceUnsized` trait which is implemented by smart pointer types to | ||
opt-in to DST coercions. It is defined as: | ||
|
||
``` | ||
#[lang="coerce_unsized"] | ||
trait CoerceUnsized<Target>: ::std::marker::PhantomFn<Self, Target> + Sized {} | ||
``` | ||
|
||
An example implementation: | ||
|
||
``` | ||
impl<T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<Rc<U>> for Rc<T> {} | ||
impl<T: Zeroable+CoerceUnsized<U>, U: Zeroable> CoerceUnsized<NonZero<U>> for NonZero<T> {} | ||
|
||
// For reference, the definitions of Rc and NonZero: | ||
pub struct Rc<T: ?Sized> { | ||
_ptr: NonZero<*mut RcBox<T>>, | ||
} | ||
pub struct NonZero<T: Zeroable>(T); | ||
``` | ||
|
||
Implementing `CoerceUnsized` indicates that the self type should be able to be | ||
coerced to the `Target` type. E.g., the above implementation means that | ||
`Rc<[i32; 42]>` can be coerced to `Rc<[i32]>`. There will be `CoerceUnsized` impls | ||
for the various pointer kinds available in Rust and which allow coercions, therefore | ||
`CoerceUnsized` when used as a bound indicates coercible types. E.g., | ||
|
||
``` | ||
fn foo<T: CoerceUnsized<U>, U>(x: T) -> U { | ||
x | ||
} | ||
``` | ||
|
||
Built-in pointer impls: | ||
|
||
``` | ||
impl<'a, 'b: 'aT: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b mut T {} | ||
impl<'a, T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<&'a mut U> for &'a mut T {} | ||
impl<'a, T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for &'a mut T {} | ||
impl<'a, T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for &'a mut T {} | ||
|
||
impl<'a, 'b: 'a, T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} | ||
impl<'b, T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for &'b T {} | ||
|
||
impl<T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *mut T {} | ||
impl<T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} | ||
|
||
impl<T: ?Sized+Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *const T {} | ||
``` | ||
|
||
Note that there are some coercions which are not given by `CoerceUnsized`, e.g., | ||
from safe to unsafe function pointers, so it really is a `CoerceUnsized` trait, | ||
not a general `Coerce` trait. | ||
|
||
|
||
## Compiler checking | ||
|
||
### On encountering an implementation of `CoerceUnsized` (type collection phase) | ||
|
||
* If the impl is for a built-in pointer type, we check nothing, otherwise... | ||
* The compiler checks that the `Self` type is a struct or tuple struct and that | ||
the `Target` type is a simple substitution of type parameters from the `Self` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't quite know what "simple substitution of type parameters" means... but I think we want to check that both of them are applications of the same base type constructor. e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant that there exists some |
||
type (i.e., That `Self` is `Foo<Ts>`, `Target` is `Foo<Us>` and that there exist | ||
`Vs` and `Xs` (where `Xs` are all type parameters) such that `Target = [Vs/Xs]Self`. | ||
One day, with HKT, this could be a regular part of type checking, for now | ||
it must be an ad hoc check). We might enforce that this substitution is of the | ||
form `X/Y` where `X` and `Y` are both formal type parameters of the | ||
implementation (I don't think this is necessary, but it makes checking coercions | ||
easier and is satisfied for all smart pointers). | ||
* The compiler checks each field in the `Self` type against the corresponding field | ||
in the `Target` type. Assuming `Fs` is the type of a field in `Self` and `Ft` is | ||
the type of the corresponding field in `Target`, then either `Ft <: Fs` or | ||
`Fs: CoerceUnsized<Ft>` (note that this includes some built-in coercions, coercions | ||
unrelated to unsizing are excluded, these could probably be added later, if needed). | ||
* There must be only one field that is coerced. | ||
* We record for each impl, the index of the field in the `Self` type which is | ||
coerced. | ||
|
||
### On encountering a potential coercion (type checking phase) | ||
|
||
* If we have an expression with type `E` where the type `F` is required during | ||
type checking and `E` is not a subtype of `F`, nor is it coercible using the | ||
built-in coercions, then we search for a bound of `E: CoerceUnsized<F>`. Note | ||
that we may not at this stage find the actual impl, but finding the bound is | ||
good enough for type checking. | ||
|
||
* If we require a coercion in the receiver of a method call or field lookup, we | ||
perform the same search that we currently do, except that where we currently | ||
check for coercions, we check for built-in coercions and then for `CoerceUnsized` | ||
bounds. We must also check for `Unsize` bounds for the case where the receiver | ||
is auto-deref'ed, but not autoref'ed. | ||
|
||
|
||
### On encountering an adjustment (translation phase) | ||
|
||
* In trans (which is post-monomorphisation) we should always be able to find an | ||
impl for any `CoerceUnsized` bound. | ||
* If the impl is for a built-in pointer type, then we use the current coercion | ||
code for the various pointer kinds (`Box<T>` has different behaviour than `&` and | ||
`*` pointers). | ||
* Otherwise, we lookup which field is coerced due to the opt-in coercion, move | ||
the object being coerced and coerce the field in question by recursing (the | ||
built-in pointers are the base cases). | ||
|
||
|
||
### Adjustment types | ||
|
||
We add `AdjustCustom` to the `AutoAdjustment` enum as a placeholder for coercions | ||
due to a `CoerceUnsized` bound. I don't think we need the `UnsizeKind` enum at | ||
all now, since all checking is postponed until trans or relies on traits and impls. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! I've done this in a local branch and now there's only unsizing coercion creation (typeck) and unsizing information generation (trans) that cares about the specific kind of unsizing being performed. // In the case of `&Struct<[T; N]>` -> `&Struct<[T]>`, the types are:
// * leaf_source: `[T; N]`
// * leaf_target: `[T]`
// * root_target: `Struct<[T]>`
pub struct AutoUnsize<'tcx> {
leaf_source: Ty<'tcx>,
leaf_target: Ty<'tcx>,
root_target: Ty<'tcx>
} |
||
|
||
|
||
# Drawbacks | ||
|
||
Not as flexible as the previous proposal. | ||
|
||
# Alternatives | ||
|
||
The original [DST5 proposal](http://smallcultfollowing.com/babysteps/blog/2014/01/05/dst-take-5/) | ||
contains a similar proposal with no opt-in trait, i.e., coercions are completely | ||
automatic and arbitrarily deep. This is a little too magical and unpredicatable. | ||
It violates some 'soft abstraction boundaries' by interefering with the deep | ||
structure of objects, sometimes even automatically (and implicitly) allocating. | ||
|
||
[RFC 401](https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md) | ||
proposed a scheme for proposals where users write their own coercion using | ||
intrinsics. Although more flexible, this allows for implcicit excecution of | ||
arbitrary code. If we need the increased flexibility, I believe we can add a | ||
manual option to the `CoerceUnsized` trait backwards compatibly. | ||
|
||
The proposed design could be tweaked: for example, we could change the | ||
`CoerceUnsized` trait in many ways (we experimented with an associated type to | ||
indicate the field type which is coerced, for example). | ||
|
||
# Unresolved questions | ||
|
||
None |
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 don't think this design can prevent writing functions generic over unsizing coercions - is that even desired?
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.
You should use
T: CoerceUnsized<U>
for a bound and then I think it is possible. UsingUnsize
as a bound only really makes sense in theCoerceUnsized
impls.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.
An
Unsize
bound makes theCoerceUnsized
impls applicable, which enables the coercions.