From e038eca0d02bf4f7db36d42ba70ed863936740f0 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 30 Apr 2016 16:15:10 -0600 Subject: [PATCH] Amend #1268 with a more feasible proposal post-specialization --- text/0000-specialization-marker-traits.md | 198 ++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 text/0000-specialization-marker-traits.md diff --git a/text/0000-specialization-marker-traits.md b/text/0000-specialization-marker-traits.md new file mode 100644 index 00000000000..9dda6ba6ffd --- /dev/null +++ b/text/0000-specialization-marker-traits.md @@ -0,0 +1,198 @@ +- Feature Name: Specialization on impls with no items +- Start Date: 2016-04-30 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Assume that any trait impl which provides no items specializes any other impl +which could apply to the same type, regardless of overlap. + +# Motivation +[motivation]: #motivation + +This RFC is intended to replace [RFC #1268][1268] +entirely. Since its acceptance, little progress has been made on that RFC due to +implementation concerns. With the advent of specialization, there are potential +alternatives which are potentially easier to implement, and also addresses the +drawbacks brought by that RFC. + +The motivations for the feature in general are the same as the original RFC, +improving the ergonomics of implementing marker traits. + +Some examples include: + +- the coercible trait design presents at [RFC #91][91]; +- the `ExnSafe` trait proposed in [RFC #1236][1236]. + +# Detailed design +[design]: #detailed-design + +Much of the concern around [RFC #1268][1268] was related to difficulty of +implementation. From the "Unresolved questions" section of the original RFC: + +> Today, we prefer to break down an obligation like +> `Foo: MarkerTrait` into component obligations (e.g., `Foo: Send`). Due to +> coherence, there is always one best way to do this. That is, there is a best +> impl to choose. But under this proposal, there would not be. + +> similar concerns arise with the proposals around specialization, so it may be +> that progress on that front will answer the questions raised here + +Specialization appears to have made some progress on this front. However, +specialization is primarily focused on details of the trait impl, not on the +trait itself. For that reason, it is proposed that we amend [RFC #1268][1268] to +a definition which is more compatible with specialization. + +**Any trait impl which provides no items is assumed to specialize any other +trait impl which could apply to the same type, regardless of overlap**. At the +time of writing, the definition of a trait item is an associated const, type, or +method. The exact meaning of an item may change in the future as language +features are added, but the intention is that an impl providing no items means +that the body of the trait impl is empty. + +[RFC #1210][1210] states the following constraint around allowing overlap: + +> There has to be a way to decide which of two overlapping impls to actually use +> for a given set of input types. + +This continues to hold with the proposed change, as an impl with no body is +interchangeable with the less specific impl. This can be demonstrated with +specialization as it exists today: + +```rust +trait SayHello { + fn hi(); +} + +trait Foo {} +trait Bar {} + +impl SayHello for T { + default fn hi() { + println!("Hello there"); + } +} + +impl SayHello for T {} +``` + +This change effectively has the same meaning as the original RFC, as an empty +trait body would be rejected for a trait that requires adding items. However, +there is an important difference in this meaning compared to the original. + +Overlap would become allowed between impls on traits with items, as long as +neither overlapping trait provides any items. Expanding on the previous example, +the following would hold: + +```rust +trait SayHello { + fn hi(); +} + +trait Foo {} +trait Bar {} +trait Baz {} + +impl SayHello for T { + default fn hi() { + println!("Hello there"); + } +} + +impl SayHello for T {} +impl SayHello for T {} +``` + +This means that the only drawback from [RFC #1268](1268) is removed. Adding a +defaulted item to a marker trait is no longer a breaking change. Adding items +without a default is already considered to be a breaking change today. + +Other than overlap, the rules for what impls are accepted remain the same, +however. Any impl which is not a `default impl` must provide an implementation +for all required items unless they are provided by a less specific impl. That +means that the following code would not compile: + +```rust +trait SayHello { + fn hi(); +} + +trait Foo {} +trait Bar {} + +impl SayHello for T { + default fn hi() { + println!("Hello there"); + } +} + +impl SayHello for T {} +``` + +For any type `T` which implements `Bar`, but not `Foo`, the second impl is +incomplete, and hence would be rejected. However, if the item has a default at +the trait level, the code would compile: + +```rust +trait SayHello { + fn hi() { + println!("Hi, there!"); + } +} + +trait Foo {} +trait Bar {} + +impl SayHello for T { + default fn hi() { + println!("Hello there"); + } +} + +impl SayHello for T {} +``` + +Since `T: Bar` provides no items, it is assumed to specialize `T: Foo` if +permitted for the given type when checking for coherence. Typeck would be +satisfied for any type that fulfills `T: Foo`, `T: Bar`, or `T: Foo + Bar`. +During monomorphisation in codegen, if a type satisfies either `Foo` or +`Foo + Bar`, the code will print "Hello there". If a type satisfies `Bar` but +not `Foo + Bar`, it will print "Hi, there!". + +The overall semantics are moderately complex, but become quite simple when you +think of them in the context of the original intention. When a trait does not +require any items, overlap is allowed. + +Due to the semantics of specialization, there is one additional wrinkle, which +is that any `default impl` with no body would be accepted. This is quirky, but +appears to be harmless. In practice there is no reason to write an empty +`default impl` for anything. The effects of providing a `default impl` for a +trait with no items is intentionally left unspecified (and in fact is unclear +from the definition of specialization without this RFC). + +# Drawbacks +[drawbacks]: #drawbacks + +While this proposal is likely easier to implement, and more flexible than +[RFC #1268][1268], it is more complex and has edge cases that did not exist in +the original. + +# Alternatives +[alternatives]: #alternatives + +Continuing with [RFC #1268](1268) as written. + +# Unresolved questions +[unresolved]: #unresolved-questions + +Is this actually easier to implement than the original RFC? This RFC was written +as a result of attempting to implement [#1268](1268), and this proved to be much +more straightforward. However, this needs to be verified by someone more well +versed in the compiler's internals. + +[1210]: https://github.com/rust-lang/rfcs/pull/1210 +[1236]: https://github.com/rust-lang/rfcs/pull/1236 +[1268]: https://github.com/rust-lang/rfcs/pull/1268 +[91]: https://github.com/rust-lang/rfcs/pull/91