From c87a854bcb458783b67e39c87625742debe5d487 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 23 Aug 2018 19:08:55 +0200 Subject: [PATCH 01/25] rfc, assoc-default-groups: initial version." --- text/0000-assoc-default-groups.md | 723 ++++++++++++++++++++++++++++++ 1 file changed, 723 insertions(+) create mode 100644 text/0000-assoc-default-groups.md diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md new file mode 100644 index 00000000000..d3242ca9863 --- /dev/null +++ b/text/0000-assoc-default-groups.md @@ -0,0 +1,723 @@ +- Feature Name: `assoc_default_groups` +- Start Date: 2018-08-23 +- RFC PR: _ +- Rust Issue: _ + +# Summary +[summary]: #summary + +[RFC 192]: https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md#defaults + +1. [Resolve][changes] the design of associated type defaults, + first introduced in [RFC 192], + such that provided methods and other items may not assume type defaults. + This applies equally to `default` with respect to specialization. + +2. [Introduce][default_groups] the concept of `default { .. }` groups + in traits and their implementations which may be used to introduce + atomic units of specialization + (if anything in the group is specialized, everything must be). + +# Motivation +[motivation]: #motivation + +## For associated type defaults + +As discussed in the [background] and mentioned in the [summary], +associated type defaults were introduced in [RFC 192]. +These defaults are valuable for a few reasons: + +1. You can already provide defaults for `const`s and `fn`s. + Allowing `type`s to have defaults adds consistency and uniformity + to the language, thereby reducing surprises for users. + +2. Associated `type` defaults in `trait`s simplify the grammar, + allowing the grammar of `trait`s them to be more in line with + the grammar of `impl`s. In addition, this brings `trait`s more in line + with `type` aliases. + +The following points were also noted in [RFC 192], but we expand upon them here: + +3. Most notably, type defaults allow you to provide more ergonomic APIs. + + [proptest]: https://altsysrq.github.io/rustdoc/proptest/latest/proptest/arbitrary/trait.Arbitrary.html + + For example, we may provide an API (due to [proptest]): + + ```rust + trait Arbitrary: Sized + fmt::Debug { + type Parameters: Default = (); + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy; + + fn arbitrary() -> Self::Strategy { + Self::arbitrary_with(Default::default()) + } + + type Strategy: Strategy; + } + ``` + + Being able to say that the default of `Parameters` is `()` means that users + who are not interested in this further detail may simply ignore specifying + `Parameters`. + + The inability of having defaults results in an inability to provide APIs + that are both a) simple to use, and b) flexible / customizable. + By allowing defaults, we can have our cake and eat it too, + enabling both a) and b) concurrently. + +4. Type defaults also aid in API evolution. + Consider a situation such as `Arbitrary` from above; + The API might have originally been: + + ```rust + trait Arbitrary: Sized + fmt::Debug { + fn arbitrary() -> Self::Strategy; + + type Strategy: Strategy; + } + ``` + + By allowing defaults, we can transition to this more flexible API without + breaking any consumers by simply saying: + + ```rust + trait Arbitrary: Sized + fmt::Debug { + type Parameters: Default = (); + + fn arbitrary() -> Self::Strategy { + Self::arbitrary_with(Default::default()) + } + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + Self::arbitrary() + // This co-recursive definition will blow the stack. + // However; since we can assume that previous implementors + // actually provided a definition for `arbitrary` that + // can't possibly reference `arbitrary_with`, we are OK. + // You would only run into trouble for new implementations; + // but that can be dealt with in documentation. + } + + type Strategy: Strategy; + } + ``` + +## For `default { .. }` groups + +Finally, because we are making [changes] to how associated type defaults work +in this RFC, a new mechanism is required to regain the loss of expressive power +due to these changes. This mechanism is described in the section on +[`default { .. }` groups][default_groups] as alluded to in the [summary]. + +These groups not only retain the expressive power due to [RFC 192] but extend +power such that users get fine grained control over what things may and may not +be overridden together. In addition, these groups allow users to assume the +definition of type defaults in other items in a way that preserves soundness. + +Examples where it is useful for other items to assume the default of an +associated type include: + +[issue#29661]: https://github.com/rust-lang/rust/issues/29661 + +[comment174527854]: https://github.com/rust-lang/rust/issues/29661#issuecomment-174527854 +[comment280944035]:https://github.com/rust-lang/rust/issues/29661#issuecomment-280944035 + +1. [A default method][comment174527854] whose + [return type is an associated type:][comment280944035] + + ```rust + /// "Callbacks" for a push-based parser + trait Sink { + fn handle_foo(&mut self, ...); + + default { + type Output = Self; + + // OK to assume what `Output` really is because any overriding + // must override both `Outout` and `finish`. + fn finish(self) -> Self::Output { self } + } + } + ``` + +2. There are plenty of other examples in [rust-lang/rust#29661][issue#29661]. + +[issue#31844]: https://github.com/rust-lang/rust/issues/31844 + +3. Other examples where `default { .. }` would have been useful can be found + in the [tracking issue][issue#31844] for [specialization]: + + + + + You can see `default { .. }` being used + [here](https://github.com/rust-lang/rust/issues/31844#issuecomment-249355377). + + + + + + + + + + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +## Associated type defaults + +### Background and The status quo +[background]: #background-and-the-status-quo + +Let's consider a simple trait with an associated type and another item (1): + +```rust +trait Foo { + type Bar; + + const QUUX: Self::Bar; + + fn wibble(x: Self::Bar) -> u8; +} +``` + +Ever since [RFC 192], +Rust has been capable of assigning default types to associated types as in (2): + +```rust +#![feature(associated_type_defaults)] + +trait Foo { + type Bar = u8; + + const QUUX: Self::Bar = 42u8; + + fn wibble(x: Self::Bar) -> u8 { x } +} +``` + +However, unlike as specified in [RFC 192], which would permit (2), +the current implementation rejects (2) with the following error messages (3): + +```rust +error[E0308]: mismatched types + --> src/lib.rs:6:29 + | +6 | const QUUX: Self::Bar = 42u8; + | ^^^^ expected associated type, found u8 + | + = note: expected type `::Bar` + found type `u8` + +error[E0308]: mismatched types + --> src/lib.rs:8:37 + | +8 | fn wibble(x: Self::Bar) -> u8 { x } + | -- ^ expected u8, found associated type + | | + | expected `u8` because of return type + | + = note: expected type `u8` + found type `::Bar` +``` + +The compiler rejects snippet (2) to preserve the soundness of the type system. +It must be rejected because a user might write (4): + +```rust +struct Bar { ... } + +impl Foo for Bar { + type Bar = Vec; +} +``` + +Given snippet (4), `Self::Bar` will evaluate to `Vec`, +which is therefore the type of `::QUUX`. +However, we have not given a different value for the constant, +and so it must be `42u8`, which has the type `u8`. +Therefore, we have reached an inconsistency in the type system: +`::QUUX` is of value `42u8`, but of type `Vec`. +So we may accept either `impl Foo for Bar` as defined in (4), +or the definition of `Foo` as in (2), but not *both*. + +[RFC 192] solved this dilemma by rejecting the implementation +and insisting that if you override *one* associated type, +then you must override *all* other defaulted items. +Or stated in its own words: + +> + If a trait implementor overrides any default associated types, +> they must also override all default functions and methods. +> + Otherwise, a trait implementor can selectively override individual +> default methods/functions, as they can today. + +Meanwhile, as we saw in the error message above (3), +the current implementation takes the alternative approach of accepting +`impl Foo for Bar` (4) but not the definition of `Foo` as in (2). + +### Changes in this RFC +[changes]: #changes-in-this-rfc + +In this RFC, we change the approach in [RFC 192] to the currently implemented +approach. Thus, you will continue to receive the error message above +and you will be able to provide associated type defaults. + +[specialization]: https://github.com/rust-lang/rfcs/pull/1210 + +With respect to [specialization], the behaviour is the same. +That is, if you write (5): + +```rust +#![feature(specialization)] + +trait Foo { + type Bar; + + fn quux(x: Self::Bar) -> u8; +} + +struct Wibble; + +impl Foo for Wibble { + default type Bar = u8; + + default fn quux(x: Self::Bar) -> u8 { x } +} +``` + +The compiler will reject this because you are not allowed to assume, +just like before, that `x: u8`. The reason why is much the same as +we have previously discussed in the [background]. + +With these changes, +we consider the design of associated type defaults to be *finalized*. + +## `default` specialization groups +[default_groups]: #default-specialization-groups + +Now, you might be thinking: - *"Well, what if I __do__ need to assume that +my defaulted associated type is what I said in a provided method, +what do I do then?"*. Don't worry; We've got you covered. + +To be able to assume that `Self::Bar` is truly `u8` in snippets (2) and (5), +you may henceforth use `default { .. }` to group items into atomic units of +specialization. This means that if one item in `default { .. }` is overridden +in an implementation, then all all the items must be. An example (6): + +```rust +trait ComputerScientist { + default { + type Bar = u8; + const QUUX: Self::Bar = 42u8; // OK! + fn wibble(x: Self::Bar) -> u8 { x } // OK! + } +} + +struct Alan; +struct Alonzo; +struct Kurt; +struct Per; + +impl ComputerScientist for Alan { + type Bar = Vec; + + // ERROR! You must override QUUX and wibble. +} + +impl ComputerScientist for Alonzo { + const QUUX: u8 = 21; + fn wibble(x: Self::Bar) -> u8 { 4 } + + // ERROR! You must override Bar. +} + +impl ComputerScientist for Kurt { + type Bar = (u8, u8); + const QUUX: Self::Bar = (0, 1); + fn wibble(x: Self::Bar) -> u8 { x.0 } + + // OK! We have overridden all items in the group. +} + +impl ComputerScientist for Per { + // OK! We have not overridden anything in the group. +} +``` + +You may also use `default { .. }` in implementations. +When you do so, everything in the group is automatically overridable. +For any items outside the group, you may assume their signatures, +but not the default definitions given. An example: + +```rust +use std::marker::PhantomData; + +trait Fruit { + type Details; + fn foo(); + fn bar(); + fn baz(); +} + +struct Citrus { species: PhantomData } +struct Orange { variety: PhantomData } +struct Blood; +struct Common; + +impl Fruit for Citrus { + default { + type Details = bool; + fn foo() { + let _: Self::Details = true; // OK! + } + fn bar() { + let _: Self::Details = true; // OK! + } + } + + fn baz() { // Removing this item here causes an error. + let _: Self::Details = true; + // ERROR! You may not assume that `Self::Details == bool` here. + } +} + +impl Fruit for Citrus> { + default { + type Details = u8; + fn foo() { + let _: Self::Details = 42u8; // OK! + } + } + + fn bar() { // Removing this item here causes an error. + let _: Self::Details = true; + // ERROR! You may not assume that `Self::Details == bool` here, + // even tho we specified that in `Fruit for Citrus`. + let _: Self::Details = 22u8; + // ERROR! Can't assume that it's u8 either! + } +} + +impl Fruit for Citrus> { + default { + type Details = f32; + fn foo() { + let _: Self::Details = 1.0f32; // OK! + } + } +} + +impl Fruit for Citrus> { + default { + type Details = f32; + } + + fn foo() { + let _: Self::Details = 1.0f32; + // ERROR! Can't assume it is f32. + } +} +``` + +However, please note that for use of `default { .. }` inside implementations, +you will still need actual support for [specialization]. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## Grammar +[grammar]: #grammar + +The production `trait_item` is changed from: + +``` +trait_item +: trait_const +| trait_type +| trait_method +| maybe_outer_attrs item_macro +; +``` + +to: + +``` +trait_item +: trait_default +| trait_const +| trait_type +| trait_method +| maybe_outer_attrs item_macro +; + +trait_default : DEFAULT '{' (trait_item)* '}' ; +``` + +Associated type defaults are already in the grammar. + +## Semantics and type checking + +### Associated type defaults + +This section supersedes [RFC 192] with respect to associated type defaults. + +Associated types can be assigned a default type in a `trait` definition: + +```rust +trait Foo { + type Bar = $default_type; + + $other_items +} +``` + +Any item in `$other_items`, +which have any provided definitions (henceforth: *"default"*), +may only assume that the type of `Self::Bar` is `Self::Bar`. +They may *not* assume that the underlying type of `Self::Bar` is `$default_type`. +This property is essential for the soundness of the type system. +When an associated type default exists in a `trait` definition, +it need not be specified in the implementations of that `trait`. + +This applies generally to any item inside a `trait`. +You may only assume the signature of an item, but not any default, +in defaults of other items. This also includes `impl` items for that trait. +For example, this means that you may not assume the value of an +associated `const` item in other item with a default. + +### Specialization groups + +Implementations of a `trait` as well as `trait`s themselves may now +contain *"specialization default groups"* (henceforth: *"group"*) as +defined by the [grammar]. + +Such a group is considered an *atomic unit of specialization* and +each item in such a group may be specialized / overridden. +This means that if *one* item is overridden in a group, +*all* items must be overridden in a group. + +Items inside a group may assume the defaults inside the group. +Items outside of that group may not assume the defaults inside of it. + +#### Nesting + +There applies no restriction on the nesting of groups. +This means that you may nest them arbitrarily. +When nesting does occur, the atomicity applies as if the nesting was flattened. +However, with respect to what may be assumed, the rule above applies. +For example, you may write: + +```rust +trait Foo { + default { + type Bar = u8; + fn baz() { + let _: Self::Bar = 1u8; + } + + default { + const SIZE: usize = 3; + fn quux() { + let_: [Self::Bar; Self::SIZE] = [1u8, 2u8, 3u8]; + } + } + } +} + +impl Foo for () { + type Bar = Vec; + fn baz() {} + const SIZE: usize = 5; + fn quux() {} +} +``` + +#### Linting redundant `default`s + +When in source code (but not as a consequence of macro expansion), +the following occurs, a warn-by-default lint (`redundant_default`) will be emitted: + +```rust +default { + ... + + default $item +// ^^^^^^^ warning: Redundant `default` +// hint: remove `default`. + + ... +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + +The main drawbacks of this proposal are that: + +1. `default { .. }` is introduced, adding to the complexity of the language. + + However, it should be noted that token `default` is already accepted for + use by specialization and for `default impl`. + Therefore, the syntax is only partially new. + +2. secondarily, if you have implementations where it is commonly needed + to write `default { .. }` because you need to assume the type of an + associated type default in a provided method, then the solution proposed + in this RFC is less ergonomic. + + However, it is the contention of this RFC that such needs will be less common. + This is discussed below. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## Alternatives + +The main alternative is to retain the behaviour in [RFC 192] such that +you may assume the type of associated type defaults in provided methods. +As noted in the [drawbacks] section, +this would be useful for certain types of APIs. +However, it is more likely than not that associated type defaults will +be used as a mechanism for code reuse than for other constructs. +As such, we consider the approach in this RFC to be the more ergonomic approach. + +Another alternative to the mechanism proposed in this RFC is to somehow +track which methods rely on which associated types as well as constants. +However, we have historically had a strong bias toward being explicit +in signatures about such things, avoiding to infer them. +With respect to semantic versioning, such an approach may also cause +surprises for crate authors and their dependents alike. + +## Consistency with associated `const`s + +Consider the following valid example from stable Rust: + +```rust +trait Foo { + const BAR: usize = 1; + + fn baz() { println!("Hi I'm baz."); } +} + +impl Foo for () { + fn baz() { println!("Hi I'm () baz."); } +} +``` + +As we can see, you are permitted to override `baz` but leave `BAR` defaulted. +This is consistent with the behaviour in this RFC in that it has the same +property: *"you don't need to override all items if you override one"*. + +Consistency and uniformity of any programming language is vital to make +its learning easy and to rid users of surprising corner cases and caveats. +By staying consistent, as shown above, we can reduce the cost to our complexity +budget that associated type defaults incur. + +## Overriding everything is less ergonomic + +We have already discussed this to some extent. +Another point to consider is that Rust code frequently sports traits such as +`Iterator` and `Future` that have many provided methods and few associated types. +While these particular traits may not benefit from associated type defaults, +many other traits, such as `Arbitrary` defined in the [motivation], would. + +## `default { .. }` is syntactically light-weight + +When you actually do need to assume the underlying default of an associated type +in a provided method, `default { .. }` provides a syntax that is comparatively +not *that* heavy weight. + +In addition, when you want to say that multiple items are overridable, +`default { .. }` provides less repetition than specifying `default` on +each item would. Thus, we believe the syntax is ergonomic. + +Finally, `default { .. }` works well and allows the user a good deal of control +over what can and can't be assumed and what must be specialized together. +The grouping mechanism also composes well as seen in +[the section where it is discussed][default_groups]. + +# Prior art +[prior-art]: #prior-art + +## Haskell + +As Rust traits are a form of type classes, +we naturally look for prior art from were they first were introduced. +That language, being Haskell, permits a user to specify associated type defaults. +For example, we may write: + +```haskell +{-# LANGUAGE TypeFamilies #-} + +class Foo x where + type Bar x :: * + -- A default: + type Bar x = Int + + -- Provided method: + baz :: x -> Bar x -> Int + baz _ _ = 0 +``` + +In this case, we are not assuming that `Bar x` unifies with `Int`. +Let's try to assume that now: + +```haskell +{-# LANGUAGE TypeFamilies #-} + +class Foo x where + type Bar x :: * + -- A default: + type Bar x = Int + + -- Provided method: + baz :: x -> Bar x -> Int + baz _ barX = barX +``` + +This snippet results in a type checking error (tested on GHC 8.0.1): + +``` +main.hs:11:16: error: + • Couldn't match expected type ‘Int’ with actual type ‘Bar x’ + • In the expression: barX + In an equation for ‘baz’: baz _ barX = barX + • Relevant bindings include + barX :: Bar x (bound at main.hs:11:9) + baz :: x -> Bar x -> Int (bound at main.hs:11:3) +:3:1: error: +``` + +The thing to pay attention to here is: +> Couldn't match expected type ‘`Int`’ with actual type ‘`Bar x`’ + +We can clearly see that the type checker is now allowing us to assume +that `Int` and `Bar x` are the same type. +This is consistent with the approach this RFC proposes. + +To our knowledge, Haskell does not have any means such as `default { .. }` +to change this behaviour. Presumably, this is the case because Haskell +preserves parametricity and lacks specialization, +wherefore `default { .. }` might not carry its weight. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +1. Should trait objects default to the one specified in the trait if + an associated type is omitted? In other words, given: + + ```rust + trait Foo { + type Bar = usize; + fn baz(&self) -> Self::Bar; + } + + type Quux = Box; + ``` + + Should `Quux` be considered well-formed and equivalent to the following? + + ```rust + type Quux = Box>; + ``` + + This question may be left as future work for another RFC or resolved + during this RFC as the RFC is forward-compatible with such a change. From 56f229b558837f75768e235be813c4c6c98e76f2 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Sun, 26 Aug 2018 05:16:09 +0200 Subject: [PATCH 02/25] rfc, assoc-default-groups: more prior art + better reference. --- text/0000-assoc-default-groups.md | 266 ++++++++++++++++++++++++++++-- 1 file changed, 250 insertions(+), 16 deletions(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index d3242ca9863..ec8965f02d1 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -287,6 +287,29 @@ The compiler will reject this because you are not allowed to assume, just like before, that `x: u8`. The reason why is much the same as we have previously discussed in the [background]. +Once place where this proposal does diverge from what is currently implemented +is with respect to the following example (6): + +```rust +#![feature(associated_type_defaults)] + +trait Foo { + type Bar = usize; + + fn baz(x: Self::Bar) -> usize; +} + +impl Foo for Vec { + fn baz(x: Self::Bar) -> usize { x } +} +``` + +In the current implementation, (6) is rejected because the compiler will not +let you assume that `x` is of type `usize`. But in this proposal, you would be +allowed to assume this. To permit this is not a problem because `Foo for ()` +is not further specializable since `baz` in the implementation has not been +marked as `default`. + With these changes, we consider the design of associated type defaults to be *finalized*. @@ -300,7 +323,7 @@ what do I do then?"*. Don't worry; We've got you covered. To be able to assume that `Self::Bar` is truly `u8` in snippets (2) and (5), you may henceforth use `default { .. }` to group items into atomic units of specialization. This means that if one item in `default { .. }` is overridden -in an implementation, then all all the items must be. An example (6): +in an implementation, then all all the items must be. An example (7): ```rust trait ComputerScientist { @@ -448,7 +471,7 @@ trait_item | maybe_outer_attrs item_macro ; -trait_default : DEFAULT '{' (trait_item)* '}' ; +trait_default : DEFAULT '{' trait_item* '}' ; ``` Associated type defaults are already in the grammar. @@ -469,33 +492,41 @@ trait Foo { } ``` -Any item in `$other_items`, -which have any provided definitions (henceforth: *"default"*), +Any item in `$other_items`, which have any provided definitions, may only assume that the type of `Self::Bar` is `Self::Bar`. They may *not* assume that the underlying type of `Self::Bar` is `$default_type`. This property is essential for the soundness of the type system. + When an associated type default exists in a `trait` definition, it need not be specified in the implementations of that `trait`. +If implementations of that `trait` do not make that associated type +available for specialization, the `$default_type` may be assumed +in other items specified in the implementation. +If an implementation does make the associated type available for +further specialization, then other definitions in the implementation +may not assume the given underlying specified type of the associated type +and may only assume that it is `Self::TheAsociatedType`. This applies generally to any item inside a `trait`. -You may only assume the signature of an item, but not any default, -in defaults of other items. This also includes `impl` items for that trait. +You may only assume the signature of an item, but not any provided definition, +in provided definitions of other items. For example, this means that you may not assume the value of an -associated `const` item in other item with a default. +associated `const` item in other items with provided definition +in a `trait` definition. ### Specialization groups Implementations of a `trait` as well as `trait`s themselves may now -contain *"specialization default groups"* (henceforth: *"group"*) as +contain *"specialization default groups"* (henceforth: *"groups"*) as defined by the [grammar]. -Such a group is considered an *atomic unit of specialization* and -each item in such a group may be specialized / overridden. +Such a group is considered an *atomic unit of specialization* +and each item in such a group may be specialized / overridden. This means that if *one* item is overridden in a group, -*all* items must be overridden in a group. +*all* items must be overridden in that group. -Items inside a group may assume the defaults inside the group. -Items outside of that group may not assume the defaults inside of it. +Items inside a group may assume the definitions inside the group. +Items outside of that group may not assume the definitions inside of it. #### Nesting @@ -639,10 +670,13 @@ The grouping mechanism also composes well as seen in ## Haskell +[associated type defaults]: https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/at-syns.pdf + As Rust traits are a form of type classes, we naturally look for prior art from were they first were introduced. -That language, being Haskell, permits a user to specify associated type defaults. -For example, we may write: +That language, being Haskell, +permits a user to specify [associated type defaults]. +For example, we may write the following legal program: ```haskell {-# LANGUAGE TypeFamilies #-} @@ -655,9 +689,16 @@ class Foo x where -- Provided method: baz :: x -> Bar x -> Int baz _ _ = 0 + +data Quux = Quux + +instance Foo Quux where + baz _ y = y ``` -In this case, we are not assuming that `Bar x` unifies with `Int`. +As in this proposal, we may assume that `y :: Int` in the above snippet. + +In this case, we are not assuming that `Bar x` unifies with `Int` in the `class`. Let's try to assume that now: ```haskell @@ -698,6 +739,199 @@ to change this behaviour. Presumably, this is the case because Haskell preserves parametricity and lacks specialization, wherefore `default { .. }` might not carry its weight. +## Idris + +[idris_interface]: http://docs.idris-lang.org/en/latest/tutorial/interfaces.html + +Idris has a concept it calls [`interface`s][idris_interface]. +These resemble type classes in Haskell, and by extension traits in Rust. +However, unlike Haskell and Rust, these `interface`s are incoherent and will +permit multiple implementations of the same interface. + +Since Idris is language with full spectrum dependent types, +it does not distinguish between terms and types, instead, types are terms. +Therefore, there is really not a distinct concept called "associated type". +However, an `interface` may require certain definitions to be provided +and this includes types. For example, we may write: + +```idris +interface Iterator self where + item : Type + next : self -> Maybe (self, item) + +implementation Iterator (List a) where + item = a + next [] = Nothing + next (x :: xs) = Just (xs, x) +``` + +Like in Haskell, in Idris, a function or value in an interface may be given a +default definition. For example, the following is a valid program: + +```idris +interface Foo x where + bar : Type + bar = Bool + + baz : x -> bar + +implementation Foo Int where + baz x = x == 0 +``` + +However, if we provide a default for `baz` in the `interface` which assumes +the default value `Bool` of `bar`, as with the following example: + +```idris +interface Foo x where + bar : Type + bar = Bool + + baz : x -> bar + baz _ = True +``` + +then we run into an error: + +``` +Type checking .\foo.idr +foo.idr:6:13-16: + | +6 | baz _ = True + | ~~~~ +When checking right hand side of Main.default#baz with expected type + bar x _ + +Type mismatch between + Bool (Type of True) +and + bar x _ (Expected type) +``` + +The behaviour here is exactly as in Haskell and as proposed in this RFC. + +## C++ + +In C++, it is possible to provide associated types and specialize them as well. +This is shown in the following example: + +```cpp +#include +#include + +template struct wrap {}; + +template struct foo { // Unspecialized. + typedef int bar; + + bar make_a_bar() { return 0; }; +}; + +template struct foo> { // Partial specialization. + typedef std::string bar; + + bar make_a_bar() { return std::string("hello world"); }; +}; + +int main() { + foo a_foo; + std::cout << a_foo.make_a_bar() << std::endl; + + foo> b_foo; + std::cout << b_foo.make_a_bar() << std::endl; +} +``` + +One thing to note here is that C++ allows us to assume in both the unspecialized +variant of `foo` as well as the specialized version that `bar` is the underlying +type we said it was in the `typedef`. This is unlike the default in this RFC +but the same as when we use `default { .. }`. + +Do note however that C++ will allow us to remove `make_a_bar` from the +specialized `foo>` but will then error out with (gcc 4.6.2): + +```cc +main.cpp: In function 'int main()': +main.cpp:21:24: error: 'struct foo >' has no member named 'make_a_bar' + std::cout << b_foo.make_a_bar() << std::endl; + ^~~~~~~~~~ +``` + +This shows that templates in C++ are fundamentally different from the type of +parametric polymorphism (generics) that Rust employs. + +## Swift + +[swift_assoc]: https://docs.swift.org/swift-book/LanguageGuide/Generics.html + +One language which does have [associated types][swift_assoc] and defaults but +which does not have provided definitions for methods is Swift. +As an example, we may write: + +```swift +protocol Foo { + associatedtype Bar = Int + + func append() -> Bar +} + +struct Quux: Foo { + func baz() -> Bar { + return 1 + } +} +``` + +However, we may not write: + +```swift +protocol Foo { + associatedtype Bar = Int + + func append() -> Bar { return 0 } +} +``` + +This would result in: + +``` +main.swift:4:23: error: protocol methods may not have bodies + func baz() -> Bar { return 0 } +``` + +## Scala + +Another language which allows for these kinds of type projections and defaults +for them is Scala. While Scala does not have type classes like Rust and Haskell +does, it does have a concept of `trait` which can be likened to a sort of +incoherent "type class" system. For example, we may write: + +```scala +trait Foo { + type Bar = Int + + def baz(x: Bar): Int = x +} + +class Quux extends Foo { + override type Bar = Int + override def baz(x: Bar): Int = x +} +``` + +There are a few interesting things to note here: + +1. We are allowed to specify a default type `Int` for `Bar`. + +2. A default definition for `baz` may be provided. + +3. This default definition may assume the default given for `Bar`. + +4. However, we *must* explicitly state that we are overriding `baz`. + +5. If we change the definition of of `override type Bar` to `Double`, + the Scala compiler will reject it. + # Unresolved questions [unresolved-questions]: #unresolved-questions From cba554fae11d3a915ec542c07f0beac82852a820 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 00:05:19 +0200 Subject: [PATCH 03/25] rfc, assoc-default-groups: review suggestion fixes. --- text/0000-assoc-default-groups.md | 54 ++++++++++++------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index ec8965f02d1..82756f930b6 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -42,7 +42,7 @@ The following points were also noted in [RFC 192], but we expand upon them here: [proptest]: https://altsysrq.github.io/rustdoc/proptest/latest/proptest/arbitrary/trait.Arbitrary.html - For example, we may provide an API (due to [proptest]): + For example, we could change [proptest]'s API to be: ```rust trait Arbitrary: Sized + fmt::Debug { @@ -287,7 +287,7 @@ The compiler will reject this because you are not allowed to assume, just like before, that `x: u8`. The reason why is much the same as we have previously discussed in the [background]. -Once place where this proposal does diverge from what is currently implemented +One place where this proposal diverges from what is currently implemented is with respect to the following example (6): ```rust @@ -306,7 +306,7 @@ impl Foo for Vec { In the current implementation, (6) is rejected because the compiler will not let you assume that `x` is of type `usize`. But in this proposal, you would be -allowed to assume this. To permit this is not a problem because `Foo for ()` +allowed to assume this. To permit this is not a problem because `Foo for Vec` is not further specializable since `baz` in the implementation has not been marked as `default`. @@ -316,14 +316,16 @@ we consider the design of associated type defaults to be *finalized*. ## `default` specialization groups [default_groups]: #default-specialization-groups +Note: Everything in this section assumes actual support for [specialization]. + Now, you might be thinking: - *"Well, what if I __do__ need to assume that my defaulted associated type is what I said in a provided method, what do I do then?"*. Don't worry; We've got you covered. To be able to assume that `Self::Bar` is truly `u8` in snippets (2) and (5), -you may henceforth use `default { .. }` to group items into atomic units of -specialization. This means that if one item in `default { .. }` is overridden -in an implementation, then all all the items must be. An example (7): +you may henceforth use `default { .. }` to group associated items into atomic +units of specialization. This means that if one item in `default { .. }` is +overridden in an implementation, then all all the items must be. An example (7): ```rust trait ComputerScientist { @@ -440,9 +442,6 @@ impl Fruit for Citrus> { } ``` -However, please note that for use of `default { .. }` inside implementations, -you will still need actual support for [specialization]. - # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -589,10 +588,9 @@ The main drawbacks of this proposal are that: use by specialization and for `default impl`. Therefore, the syntax is only partially new. -2. secondarily, if you have implementations where it is commonly needed - to write `default { .. }` because you need to assume the type of an - associated type default in a provided method, then the solution proposed - in this RFC is less ergonomic. +2. if you have implementations where you commonly need to write `default { .. }` + because you need to assume the type of an associated type default in a + provided method, then the solution proposed in this RFC is less ergonomic. However, it is the contention of this RFC that such needs will be less common. This is discussed below. @@ -742,11 +740,12 @@ wherefore `default { .. }` might not carry its weight. ## Idris [idris_interface]: http://docs.idris-lang.org/en/latest/tutorial/interfaces.html +[coherence]: http://blog.ezyang.com/2014/07/type-classes-confluence-coherence-global-uniqueness/ Idris has a concept it calls [`interface`s][idris_interface]. These resemble type classes in Haskell, and by extension traits in Rust. -However, unlike Haskell and Rust, these `interface`s are incoherent and will -permit multiple implementations of the same interface. +However, unlike Haskell and Rust, these `interface`s do not have the property +of [coherence] and will permit multiple implementations of the same interface. Since Idris is language with full spectrum dependent types, it does not distinguish between terms and types, instead, types are terms. @@ -822,13 +821,13 @@ This is shown in the following example: template struct wrap {}; template struct foo { // Unspecialized. - typedef int bar; + using bar = int; bar make_a_bar() { return 0; }; }; template struct foo> { // Partial specialization. - typedef std::string bar; + using bar = std::string; bar make_a_bar() { return std::string("hello world"); }; }; @@ -842,23 +841,10 @@ int main() { } ``` -One thing to note here is that C++ allows us to assume in both the unspecialized -variant of `foo` as well as the specialized version that `bar` is the underlying -type we said it was in the `typedef`. This is unlike the default in this RFC -but the same as when we use `default { .. }`. - -Do note however that C++ will allow us to remove `make_a_bar` from the -specialized `foo>` but will then error out with (gcc 4.6.2): - -```cc -main.cpp: In function 'int main()': -main.cpp:21:24: error: 'struct foo >' has no member named 'make_a_bar' - std::cout << b_foo.make_a_bar() << std::endl; - ^~~~~~~~~~ -``` - -This shows that templates in C++ are fundamentally different from the type of -parametric polymorphism (generics) that Rust employs. +You will note that C++ allows us to assume in both the base template class, +as well as the specialization, that bar is equal to the underlying type. +This is because one cannot specialize any part of a class without specializing +the whole of it. It's equivalent to one atomic `default { .. }` block. ## Swift From a9ab28bd12946e0fc9555f46c4e4b8f705eb30d3 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 00:58:58 +0200 Subject: [PATCH 04/25] rfc, assoc-default-groups: more modern CS folks. --- text/0000-assoc-default-groups.md | 50 +++++++++++++++++++------------ 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 82756f930b6..e743e46052c 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -328,41 +328,53 @@ units of specialization. This means that if one item in `default { .. }` is overridden in an implementation, then all all the items must be. An example (7): ```rust +struct Country(&'static str); + +struct LangSec { papers: usize } +struct CategoryTheory { papers: usize } + trait ComputerScientist { default { - type Bar = u8; - const QUUX: Self::Bar = 42u8; // OK! - fn wibble(x: Self::Bar) -> u8 { x } // OK! + type Details = Country; + const THE_DETAILS: Self::Details = Country("Scotland"); // OK! + fn papers(details: Self::Details) -> u8 { 19 } // OK! } } -struct Alan; -struct Alonzo; -struct Kurt; -struct Per; +// https://en.wikipedia.org/wiki/Emily_Riehl +struct EmilyRiehl; -impl ComputerScientist for Alan { - type Bar = Vec; +// https://www.cis.upenn.edu/~sweirich/ +struct StephanieWeirich; + +// http://www.cse.chalmers.se/~andrei/ +struct AndreiSabelfeld; + +// https://en.wikipedia.org/wiki/Conor_McBride +struct ConorMcBride; + +impl ComputerScientist for EmilyRiehl { + type Details = CategoryTheory; - // ERROR! You must override QUUX and wibble. + // ERROR! You must override THE_DETAILS and papers. } -impl ComputerScientist for Alonzo { - const QUUX: u8 = 21; - fn wibble(x: Self::Bar) -> u8 { 4 } +impl ComputerScientist for StephanieWeirich { + const THE_DETAILS: Country = Country("USA"); + fn papers(details: Self::Details) -> u8 { 86 } - // ERROR! You must override Bar. + // ERROR! You must override Details. } -impl ComputerScientist for Kurt { - type Bar = (u8, u8); - const QUUX: Self::Bar = (0, 1); - fn wibble(x: Self::Bar) -> u8 { x.0 } +impl ComputerScientist for AndreiSabelfeld { + type Details = LangSec; + const THE_DETAILS: Self::Details = LangSec { papers: 90 }; + fn papers(details: Self::Details) -> u8 { details.papers } // OK! We have overridden all items in the group. } -impl ComputerScientist for Per { +impl ComputerScientist for ConorMcBride { // OK! We have not overridden anything in the group. } ``` From a065be92b813183fc64f3ba2551d0d46d1cdde8b Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 03:40:37 +0200 Subject: [PATCH 05/25] rfc, assoc-default-groups: case study, future work, impl trait interactions. --- text/0000-assoc-default-groups.md | 123 ++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index e743e46052c..49f858ddf06 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -454,6 +454,64 @@ impl Fruit for Citrus> { } ``` +### Case study +[case study]: #case-study + +[RFC 2500]: https://github.com/rust-lang/rfcs/pull/2500 + +One instance where specialization could be useful to provide a more ergonomic +API is to improve upon [RFC 2500]. The RFC proposes the following API: + +```rust +trait Needle: Sized { + type Searcher: Searcher; + fn into_searcher(self) -> Self::Searcher; + + type Consumer: Consumer; + fn into_consumer(self) -> Self::Consumer; +} +``` + +However, it turns out that usually, `Consumer` and `Searcher` are +the same underlying type. Therefore, we would like to save the user +from some unnecessary work by letting them elide parts of the required +definitions in implementations. + +One might imagine that we'd write: + +```rust +trait Needle: Sized { + type Searcher: Searcher; + fn into_searcher(self) -> Self::Searcher; + + default { + type Consumer: Consumer = Self::Searcher; + fn into_consumer(self) -> Self::Consumer { self.into_searcher() } + } +} +``` + +However, the associated type `Searcher` does not necessarily implement +`Consumer`. Therefore, the above definition would not type check. + +However, we can encode the above construct by rewriting it slightly, +using the concept of partial implementations from [RFC 1210]: + +```rust +default impl Needle for T +where Self::Searcher: Consumer { + default { + type Consumer = Self::Searcher; + fn into_consumer(self) -> Self::Consumer { self.into_searcher() } + } +} +``` + +Now we have ensured that `Self::Searcher` is a `Consumer` +and therefore, the above definition will type check. +Having done this, the API has become more ergonomic because we can +let users define instances of `Needle` with half as much requirements. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -525,6 +583,36 @@ For example, this means that you may not assume the value of an associated `const` item in other items with provided definition in a `trait` definition. +#### Interaction with `existential type` + +[RFC 2071]: https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-existential-types.md#reference-existential-types + +[RFC 2071] defines a construct `existential type Foo: Bar;` which is permitted +in associated types and results in an opaque type. This means that the nominal +type identity is hidden from certain contexts and only `Bar` is extensionally +known about the type wherefore only the operations of `Bar` is afforded. +This construct is sometimes written as `type Foo = impl Bar;` in conversation +instead. + +[RFC 1210]: https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#default-impls + +With respect to this RFC, the semantics of `type Assoc = impl Bar;` +inside a trait definition, where `Assoc` is the name of the associated type, +is understood as what it means in terms of `default impl ..` as discussed +in [RFC 1210]. What this entails means in concrete terms is that given: + +```rust +trait Foo { + type Assoc = impl Bar; + + ... +} +``` + +the underlying type of `Assoc` stays the same for all implementations which +do not change the default of `Assoc`. The same applies to specializations. +With respect to type visibility, it is the same as that of `existential type`. + ### Specialization groups Implementations of a `trait` as well as `trait`s themselves may now @@ -953,3 +1041,38 @@ There are a few interesting things to note here: This question may be left as future work for another RFC or resolved during this RFC as the RFC is forward-compatible with such a change. + +# Future work + +## `where` clauses on `default { .. }` groups + +From our [case study], we noticed that we had to depart from our `trait` +definition into a separate `default impl..` to handle the conditionality +of `Self::Searcher: Consumer`. However, one method to regain +the locality provided by having `default { .. }` inside the `trait` definition +is to realize that we could attach an optional `where` clause to the group. +This would allow us to write: + +```rust +trait Needle: Sized { + type Searcher: Searcher; + fn into_searcher(self) -> Self::Searcher; + + default where + Self::Searcher: Consume + { + type Consumer: Consumer = Self::Searcher; + fn into_consumer(self) -> Self::Consumer { self.into_searcher() } + } +} +``` + +The defaults in this snippet would then be equivalent to the `default impl..` +snippet noted in the [case study]. + +This `default where $bounds` construct should be able to +subsume common cases where you only have a single `default impl..` +but provide comparatively better local reasoning. + +However, we do not propose this at this stage because it is unclear how +common `default impl..` will be in practice. From e815e5ccd3789f0b2c42f9729f9756cf8928f703 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 04:52:28 +0200 Subject: [PATCH 06/25] rfc, assoc-default-groups: formatting fix. --- text/0000-assoc-default-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 49f858ddf06..2be6d171368 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -942,7 +942,7 @@ int main() { ``` You will note that C++ allows us to assume in both the base template class, -as well as the specialization, that bar is equal to the underlying type. +as well as the specialization, that `bar` is equal to the underlying type. This is because one cannot specialize any part of a class without specializing the whole of it. It's equivalent to one atomic `default { .. }` block. From a714f7e6f1e7157945c9c6f1286281b2369dfe89 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 06:19:23 +0200 Subject: [PATCH 07/25] rfc, assoc-default-groups: impl Arbitrary for usize. --- text/0000-assoc-default-groups.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 2be6d171368..61bba012120 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -79,6 +79,16 @@ The following points were also noted in [RFC 192], but we expand upon them here: } ``` + with an implementation: + + ```rust + impl Arbitrary for usize { + fn arbitrary() -> Self::Strategy { 0..100 } + + type Strategy = Range; + } + ``` + By allowing defaults, we can transition to this more flexible API without breaking any consumers by simply saying: @@ -104,6 +114,8 @@ The following points were also noted in [RFC 192], but we expand upon them here: } ``` + The implementation `Arbitrary for usize` *remains valid* even after the change. + ## For `default { .. }` groups Finally, because we are making [changes] to how associated type defaults work From 29bdcc26381af60979965f530e8a71c56decc578 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 06:23:05 +0200 Subject: [PATCH 08/25] rfc, assoc-default-groups: playground link. --- text/0000-assoc-default-groups.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 61bba012120..1665322beeb 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -299,8 +299,10 @@ The compiler will reject this because you are not allowed to assume, just like before, that `x: u8`. The reason why is much the same as we have previously discussed in the [background]. +[current_impl_diverge]: https://play.rust-lang.org/?gist=30e01d77f7045359e30c7d3f3144e984&version=nightly&mode=debug&edition=2015 + One place where this proposal diverges from what is currently implemented -is with respect to the following example (6): +is with respect to the [following example][current_impl_diverge] (6): ```rust #![feature(associated_type_defaults)] From 87679d9d279837c1612baa3688633955f48d4388 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 06:25:11 +0200 Subject: [PATCH 09/25] rfc, assoc-default-groups: type visibility -> type opacity. --- text/0000-assoc-default-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 1665322beeb..6779c20efd2 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -625,7 +625,7 @@ trait Foo { the underlying type of `Assoc` stays the same for all implementations which do not change the default of `Assoc`. The same applies to specializations. -With respect to type visibility, it is the same as that of `existential type`. +With respect to type opacity, it is the same as that of `existential type`. ### Specialization groups From 09c122789e27b42e9e4d916151b1cb91094c0647 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 06:26:29 +0200 Subject: [PATCH 10/25] rfc, assoc-default-groups: fix typo. --- text/0000-assoc-default-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 6779c20efd2..0df1f90e906 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -645,7 +645,7 @@ Items outside of that group may not assume the definitions inside of it. There applies no restriction on the nesting of groups. This means that you may nest them arbitrarily. -When nesting does occur, the atomicity applies as if the nesting was flattened. +When nesting does occur, the atomicity applies as if the nesting were flattened. However, with respect to what may be assumed, the rule above applies. For example, you may write: From 19cf7a7bbd75e1e86a7592e2c6d031a0fb358767 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 06:45:55 +0200 Subject: [PATCH 11/25] rfc, assoc-default-groups: add unresolved question about nesting. --- text/0000-assoc-default-groups.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 0df1f90e906..a17182c3263 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -1056,6 +1056,18 @@ There are a few interesting things to note here: This question may be left as future work for another RFC or resolved during this RFC as the RFC is forward-compatible with such a change. +2. Should groups be arbitrarily nestable? + + On the one hand, permitting arbitrary nesting is simpler from a grammatical + point of view and makes the language simpler by having *fewer rules*. + It also allows the user more fine grained control. + + On the other hand, it is not clear to what use such fine grained control + would be. Nested groups may also be less understandable and lead to confusion. + + To resolve this issue, some usage experience may be required. + Thus, it might be a good idea to defer such a choice until after the RFC. + # Future work ## `where` clauses on `default { .. }` groups From 5548ab1481db94edd30fd130e808333d5ce01237 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 18:43:57 +0200 Subject: [PATCH 12/25] rfc, assoc-default-groups: add example without assoc type. --- text/0000-assoc-default-groups.md | 37 ++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index a17182c3263..bf0e7b07d61 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -468,12 +468,44 @@ impl Fruit for Citrus> { } ``` +So far our examples have always included an associated type. +However, this is not a requirement. +We can also group associated `const`s and `fn`s together or just `fn`s. +An example: + +```rust +trait Foo { + default { + const BAR: usize = 3; + + fn baz() -> [u8; Self::BAR] { + [1, 2, 3] + } + } +} + +trait Quux { + default { + fn wibble() { + ... + } + + fn wobble() { + ... + } + + // For whatever reason; The crate author has found it imperative + // that `wibble` and `wobble` always be defined together. + } +} +``` + ### Case study [case study]: #case-study [RFC 2500]: https://github.com/rust-lang/rfcs/pull/2500 -One instance where specialization could be useful to provide a more ergonomic +One instance where default groups could be useful to provide a more ergonomic API is to improve upon [RFC 2500]. The RFC proposes the following API: ```rust @@ -641,6 +673,9 @@ This means that if *one* item is overridden in a group, Items inside a group may assume the definitions inside the group. Items outside of that group may not assume the definitions inside of it. +The parser will accept items inside `default { .. }` without a body. +However, such an item will later be rejected during type checking. + #### Nesting There applies no restriction on the nesting of groups. From 3f18a9167794b48265a68af01f7d6347d19572fd Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 18:45:56 +0200 Subject: [PATCH 13/25] rfc, assoc-default-groups: adjust start date. --- text/0000-assoc-default-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index bf0e7b07d61..efea3852198 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -1,5 +1,5 @@ - Feature Name: `assoc_default_groups` -- Start Date: 2018-08-23 +- Start Date: 2018-08-27 - RFC PR: _ - Rust Issue: _ From 60e5e65621b901e0bbd3d2a043d280807a54285a Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 27 Aug 2018 19:57:49 +0200 Subject: [PATCH 14/25] rfc, assoc-default-groups: fix typo. --- text/0000-assoc-default-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index efea3852198..eb1790fa870 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -877,7 +877,7 @@ main.hs:11:16: error: The thing to pay attention to here is: > Couldn't match expected type ‘`Int`’ with actual type ‘`Bar x`’ -We can clearly see that the type checker is now allowing us to assume +We can clearly see that the type checker is *not* allowing us to assume that `Int` and `Bar x` are the same type. This is consistent with the approach this RFC proposes. From 904ed01f127a2be44eb7d723a076338b6e372cd5 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 18 Sep 2018 23:54:49 +0200 Subject: [PATCH 15/25] rfc, assoc-default-groups: add std::remove_reference example. --- text/0000-assoc-default-groups.md | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index eb1790fa870..53fbf677386 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -171,6 +171,66 @@ associated type include: + + +[`std::remove_reference`]: http://www.cplusplus.com/reference/type_traits/remove_reference/ + +4. Encoding a more powerful [`std::remove_reference`] + + We can encode a more powerful version of C++'s `remove_reference` construct, + which allows you to get the base type of a reference type recursively. + Without default groups, we can get access to the base type like so: + + ```rust + trait RemoveRef { + type WithoutRef; + } + + impl RemoveRef for T { + default type WithoutRef = T; + } + + impl<'a, T: RemoveRef> RemoveRef for &'a T { + type WithoutRef = T::WithoutRef; + } + ``` + + However, we don't have any way to transitively dereference to + `&Self::WithoutRef`. With default groups we can gain that ability with: + + ```rust + trait RemoveRef { + type WithoutRef; + fn single_ref(&self) -> &Self::WithoutRef; + } + + impl RemoveRef for T { + default { + type WithoutRef = T; + + fn single_ref(&self) -> &Self::WithoutRef { + // We can assume that `T == Self::WithoutRef`. + self + } + } + } + + impl<'a, T: RemoveRef> RemoveRef for &'a T { + type WithoutRef = T::WithoutRef; + + fn single_ref(&self) -> &Self::WithoutRef { + // We can assume that `T::WithoutRef == Self::WithoutRef`. + T::single_ref(*self) + } + } + ``` + + We can then proceed to writing things such as: + + ```rust + fn do_stuff(recv: impl RemoveRef) { + recv.single_ref().my_method(); + } + ``` + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 31342f65eb9fa1b9491bb75d5d605030db66eaaf Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 19 Sep 2018 00:13:18 +0200 Subject: [PATCH 16/25] rfc, assoc-default-groups: default $item is syntactic sugar. --- text/0000-assoc-default-groups.md | 36 ++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 53fbf677386..2b150557387 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -616,7 +616,41 @@ where Self::Searcher: Consumer { Now we have ensured that `Self::Searcher` is a `Consumer` and therefore, the above definition will type check. Having done this, the API has become more ergonomic because we can -let users define instances of `Needle` with half as much requirements. +let users define instances of `Needle` with half as many requirements. + +### `default fn foo() { .. }` is syntactic sugar + +In the section of [changes] to associated type defaults, +snippet (5) actually indirectly introduced default groups of a special form, +namely "singleton groups". That is, when we wrote: + +```rust +impl Foo for Wibble { + default type Bar = u8; + + default fn quux(x: Self::Bar) -> u8 { x } +} +``` + +this was actually sugar for: + +```rust +impl Foo for Wibble { + default { + type Bar = u8; + } + + default { + fn quux(x: Self::Bar) -> u8 { x } + } +} +``` + +We can see that these are equivalent since in the [specialization] RFC, +the semantics of `default fn` were that `fn` may be overridden in more +specific implementations. With these singleton groups, you may assume +the body of `Bar` in all other items in the same group; but it just +happens to be the case that there are no other items in the group. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 597706b4c29c1606461764626a4f4a731c772838 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 19 Sep 2018 05:36:53 +0200 Subject: [PATCH 17/25] rfc, assoc-default-groups: change to the tree of cliques idea. --- text/0000-assoc-default-groups.md | 332 +++++++++++++++++++++++++----- 1 file changed, 283 insertions(+), 49 deletions(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 2b150557387..9f0deb953b0 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -17,6 +17,7 @@ in traits and their implementations which may be used to introduce atomic units of specialization (if anything in the group is specialized, everything must be). + These groups may be nested and form a [tree of cliques]. # Motivation [motivation]: #motivation @@ -652,41 +653,244 @@ specific implementations. With these singleton groups, you may assume the body of `Bar` in all other items in the same group; but it just happens to be the case that there are no other items in the group. +### Nesting and a tree of cliques +[tree of cliques]: #nesting-and-a-tree-of-cliques + +In the [summary], we alluded to the notion of groups being nested. +However, thus far we have seen no examples of such nesting. +This RFC does permit you do that. For example, you may write: + +```rust +trait Foo { + default { + type Bar = usize; + + fn alpha() -> Self::Bar { + 0 // OK! In the same group, so we may assume `Self::Bar == usize`. + } + + // OK; we can rely on `Self::Bar == usize`. + default const BETA: Self::Bar = 3; + + default fn gamma() -> [Self::Bar; 4] { + // OK; we can depend on the underlying type of `Self::Bar`. + [9usize, 8, 7, 6] + } + + /// This is rejected: + default fn delta() -> [Self::Bar; Self::BETA] { + // ERROR! we may not rely on not on `Self::BETA`'s value because + // `Self::BETA` is a sibling of `Self::gamma` which is not in the + // same group and is not an ancestor either. + [9usize, 8, 7] + } + + // But this is accepted: + default fn delta() -> [Self::Bar; 3] { + // OK; we can depend on `Self::Bar == usize`. + [9, 8, 7] + } + + default { + // OK; we can still depend on `Self::Bar == usize`. + const EPSILON: Self::Bar = 2; + + fn zeta() -> [Self::Bar; Self::Epsilon] { + // OK; We can assume the value of `Self::EPSILON` because it + // is a sibling in the same group. We may also assume that + // `Self::Bar == usize` because it is an ancestor. + [42usize, 24] + } + } + } +} + +struct Eta; +struct Theta; +struct Iota; + +impl Foo for Eta { + // We can override `gamma` without overriding anything else because + // `gamma` is the sole member of its sub-group. Note in particular + // that we don't have to override `alpha`. + fn gamma() -> [Self::Bar; 4] { + [43, 42, 41, 40] + } +} + +impl Bar for Theta { + // Since `EPSILON` and `zeta` are in the same group; we must override + // them together. However, we still don't have to override anything + // in ancestral groups. + const EPSILON: Self::Bar = 0; + + fn zeta() -> [Self::Bar; Self::Epsilon] { + [] + } +} + +impl Bar for Iota { + // We have overridden `Bar` which is in the root group. + // Since all other items are decendants of the same group as `Bar` is in, + // they are allowed to depend on what `Bar` is. + type Bar = u8; + + ... // Definitions for all the other items elided for brevity. +} +``` + +[clique]: https://en.wikipedia.org/wiki/Clique_(graph_theory) + +In graph theory, a set of a vertices, in a graph, for which each distinct pair +of vertices is connected by a unique edge is said to form a [clique]. +What the snippet above encodes is a tree of such cliques. In other words, +we can visualize the snippet as: + +``` + ┏━━━━━━━━━━━━━━━━━┓ + ┃ + type Bar ┃ + ┏━━━━━━━━━━━━━┃ + fn alpha ┃━━━━━━━━━━━━━━┓ + ┃ ┗━━━━━━━━━━━━━━━━━┛ ┃ + ┃ ┃ ┃ ┃ + ┃ ┃ ┃ ┃ + ▼ ▼ ▼ ▼ +┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━┓ +┃ + const Beta ┃ ┃ + fn gamma ┃ ┃ + fn delta ┃ ┃ + const EPSILON ┃ +┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┃ + fn zeta ┃ + ┗━━━━━━━━━━━━━━━━━┛ +``` + +Please pay extra attention to the fact that items in the same group may +depend on each other's definitions as well as definitions of items that +are ancestors (up the tree). The inverse implication holds for what you +must override: if you override one item in a group, you must override +all items in that groups and all items in sub-groups (recursively). +As before, these limitations exist to preserve the soundness of the type system. + +Nested groups are intended primarily expected to be used when there is one +associated type, for which you want to define a default, coupled with a bunch +of functions which need to rely on the definition of the associated type. +This is a good mechanism for API evolution in the sense that you can introduce +a new associated type, rely on it in provided methods, but still perform +no breaking change. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation ## Grammar [grammar]: #grammar -The production `trait_item` is changed from: +Productions in this section which are not defined here are taken from +[parser-lalr.y](https://github.com/rust-lang/rust/blob/master/src/grammar/parser-lalr.y). + +Given: ``` -trait_item +trait_item : maybe_outer_attrs trait_item_leaf ; + +trait_item_leaf : trait_const | trait_type | trait_method -| maybe_outer_attrs item_macro +| item_macro +; + +trait_const +: CONST ident maybe_ty_ascription maybe_const_default ';' +; + +trait_type : TYPE ty_param ';' ; + +trait_method : method_prefix method_common ';' | method_prefix method_provided ; +method_prefix : maybe_unsafe | CONST maybe_unsafe | maybe_unsafe EXTERN maybe_abi ; +method_provided : method_common inner_attrs_and_block ; +method_common +: FN ident generic_params fn_decl_with_self_allow_anon_params maybe_where_clause ; ``` -to: +The production `trait_item` is changed into: ``` -trait_item -: trait_default +trait_item : maybe_outer_attrs trait_item_def ; + +trait_item_def +: trait_default_group +| trait_default_singleton | trait_const | trait_type | trait_method -| maybe_outer_attrs item_macro +| item_macro +; + +trait_default_singleton : DEFAULT trait_item ; +trait_default_group : DEFAULT '{' trait_item* '}' ; + +trait_type : TYPE ty_param ('=' ty_sum)? ';' ; +``` + +Given: + +``` +impl_item : attrs_and_vis impl_item_leaf ; +impl_item_leaf +: item_macro +| maybe_default impl_method +| maybe_default impl_const +| maybe_default impl_type ; -trait_default : DEFAULT '{' trait_item* '}' ; +impl_const : item_const ; +impl_type : TYPE ident generic_params '=' ty_sum ';' ; +impl_method : method_prefix method_common ; + +method_common +: FN ident generic_params fn_decl_with_self maybe_where_clause inner_attrs_and_block +; ``` -Associated type defaults are already in the grammar. +The production `impl_item` is changed into: + +``` +impl_item : attrs_and_vis impl_item_def ; +impl_item_def +: impl_default_singleton +| impl_default_group +| item_macro +| impl_method +| impl_const +| impl_type +; + +impl_default_singleton : DEFAULT impl_item ; +impl_default_group : DEFAULT '{' impl_item* '}' ; +``` + +Note that associated type defaults are already in the grammar due to [RFC 192] +but we have specified them in the grammar here nonetheless. + +Note also that `default default fn ..` as well as `default default { .. }` are +intentionally recognized by the grammar to make life easier for macro authors +even though writing `default default ..` should never be written directly. + +## Desugaring + +After macro expansion, wherever the production `trait_default_singleton` occurs, +it is treated in all respects as, except for error reporting -- which is left up +to implementations of Rust, and is desugared to `DEFAULT '{' trait_item '}'`. +The same applies to `impl_default_singleton`. +In other words: `default fn f() {}` is desugared to `default { fn f() {} }`. ## Semantics and type checking +### Semantic restrictions on the syntax + +According to the [grammar], the parser will accept items inside `default { .. }` +without a body. However, such an item will later be rejected during type checking. +The parser will also accept visibility modifiers on `default { .. }` +(e.g. `pub default { .. }`). However, such a visibility modifier will also be +rejected by the type checker. + ### Associated type defaults This section supersedes [RFC 192] with respect to associated type defaults. @@ -756,26 +960,33 @@ With respect to type opacity, it is the same as that of `existential type`. ### Specialization groups Implementations of a `trait` as well as `trait`s themselves may now -contain *"specialization default groups"* (henceforth: *"groups"*) as -defined by the [grammar]. +contain *"specialization default groups"* (henceforth: *"group(s)"*) +as defined by the [grammar]. + +A group forms a [clique] and is considered an *atomic unit of specialization* +wherein each item can be specialized / overridden. + +Groups may contain other groups - such groups are referred to as +*"nested groups"* and may be nested arbitrarily deeply. +Items which are not in any group are referred to as *`0`-deep*. +A group which occurs at the top level of a `trait` or an `impl` +definition is referred to as a *`1`-deep* group. +A group which is contained in a *`1`-deep* group is *`2`-deep*. +If a group is nested `k` times it is *`k`-deep*. + +A group and its sub-groups form a *tree of cliques*. +Given a group `$g` with items `$x_1, .. $x_n`, an item `$x_j` in `$g` +can assume the definitions of `$x_i, ∀ i ∈ { 1..n }` as well as any +definitions of items in `$f` where `$f` is an ancestor of `$g` (up the tree). +Conversely, items in `$g` may not assume the definitions of items in +descendant groups `$h_i` of `$g` as well as items which are grouped at all +or which are in groups which are not ancestors of `$g`. + +If an `impl` block overrides one item `$x_j` in `$g`, +it also has to override all `$x_i` in `$g` where `i ≠ j` as well as +all items in groups `$h_i` which are descendants of `$g` (down the tree). +Otherwise, items do not need to be overridden. -Such a group is considered an *atomic unit of specialization* -and each item in such a group may be specialized / overridden. -This means that if *one* item is overridden in a group, -*all* items must be overridden in that group. - -Items inside a group may assume the definitions inside the group. -Items outside of that group may not assume the definitions inside of it. - -The parser will accept items inside `default { .. }` without a body. -However, such an item will later be rejected during type checking. - -#### Nesting - -There applies no restriction on the nesting of groups. -This means that you may nest them arbitrarily. -When nesting does occur, the atomicity applies as if the nesting were flattened. -However, with respect to what may be assumed, the rule above applies. For example, you may write: ```rust @@ -803,21 +1014,32 @@ impl Foo for () { } ``` -#### Linting redundant `default`s +## Linting redundant `default`s When in source code (but not as a consequence of macro expansion), -the following occurs, a warn-by-default lint (`redundant_default`) will be emitted: +any of the following occurs, a warn-by-default lint (`redundant_default`) +will be emitted: ```rust -default { - ... + default default $item +// ^^^^^^^ warning: Redundant `default` +// hint: remove `default`. - default $item + default default { // ^^^^^^^ warning: Redundant `default` // hint: remove `default`. + ... + } - ... -} + default { + ... + + default $item +// ^^^^^^^ warning: Redundant `default` +// hint: remove `default`. + + ... + } ``` # Drawbacks @@ -835,7 +1057,8 @@ The main drawbacks of this proposal are that: because you need to assume the type of an associated type default in a provided method, then the solution proposed in this RFC is less ergonomic. - However, it is the contention of this RFC that such needs will be less common. + However, it is the contention of this RFC that such needs will be less common + and the nesting mechanism is sufficiently ergonomic for such cases. This is discussed below. # Rationale and alternatives @@ -856,7 +1079,14 @@ track which methods rely on which associated types as well as constants. However, we have historically had a strong bias toward being explicit in signatures about such things, avoiding to infer them. With respect to semantic versioning, such an approach may also cause -surprises for crate authors and their dependents alike. +surprises for crate authors and their dependents alike because it may +be difficult at glance to decide what the dependencies are. +This in turn reduces the maintainability and readability of code. + +One may also consider mechanisms such as `default(Bar, BAZ) { .. }` to give +more freedom as to which dependency graphs may be encoded. +However, in practice, we believe that the *tree of cliques* approach proposed +in this RFC should be more than enough for practical applications. ## Consistency with associated `const`s @@ -906,6 +1136,22 @@ over what can and can't be assumed and what must be specialized together. The grouping mechanism also composes well as seen in [the section where it is discussed][default_groups]. +## Tree of cliques is familiar + +The *"can depend on"* rule is similar to the rule used to determine whether a +non-`pub` item in a module tree is accessible or not. +Familiarity is a good tool to limit complexity costs. + +## Non-special treatment for methods + +In this RFC we haven't given methods any special treatment. +We could do so by allowing methods to assume the underlying type +of an associated type and still be overridable without having to override +the type. However, this might lead to *semantic breakage* in the sense that +the details of an `fn` may be tied to the definition of an associated type. +When those details change, it may also be prudent to change the associated type. +Default groups give users a mechanism to enforce such decisions. + # Prior art [prior-art]: #prior-art @@ -1185,18 +1431,6 @@ There are a few interesting things to note here: This question may be left as future work for another RFC or resolved during this RFC as the RFC is forward-compatible with such a change. -2. Should groups be arbitrarily nestable? - - On the one hand, permitting arbitrary nesting is simpler from a grammatical - point of view and makes the language simpler by having *fewer rules*. - It also allows the user more fine grained control. - - On the other hand, it is not clear to what use such fine grained control - would be. Nested groups may also be less understandable and lead to confusion. - - To resolve this issue, some usage experience may be required. - Thus, it might be a good idea to defer such a choice until after the RFC. - # Future work ## `where` clauses on `default { .. }` groups From e1c6278e439ae36f70e9ef97b3a0c858f651f2f2 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 19 Sep 2018 05:44:32 +0200 Subject: [PATCH 18/25] rfc, assoc-default-groups: fix formulation re. depth. --- text/0000-assoc-default-groups.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 9f0deb953b0..fcfd62af592 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -969,10 +969,10 @@ wherein each item can be specialized / overridden. Groups may contain other groups - such groups are referred to as *"nested groups"* and may be nested arbitrarily deeply. Items which are not in any group are referred to as *`0`-deep*. -A group which occurs at the top level of a `trait` or an `impl` -definition is referred to as a *`1`-deep* group. -A group which is contained in a *`1`-deep* group is *`2`-deep*. -If a group is nested `k` times it is *`k`-deep*. +An item directly defined in a group which occurs at the top level of a +`trait` or an `impl` definition is referred to as being *`1`-deep*. +An item in a group which is contained in a *`1`-deep* group is *`2`-deep*. +If an item is nested in `k` groups it is *`k`-deep*. A group and its sub-groups form a *tree of cliques*. Given a group `$g` with items `$x_1, .. $x_n`, an item `$x_j` in `$g` From 6d27c630f195fc5ba31189c3bb95f5df7ac7f599 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 19 Sep 2018 06:28:11 +0200 Subject: [PATCH 19/25] rfc, assoc-default-groups: fix note about specialization. --- text/0000-assoc-default-groups.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index fcfd62af592..1c7a3019784 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -391,7 +391,8 @@ we consider the design of associated type defaults to be *finalized*. ## `default` specialization groups [default_groups]: #default-specialization-groups -Note: Everything in this section assumes actual support for [specialization]. +Note: Overlapping implementations, where one is more specific than the other, +requires actual support for [specialization]. Now, you might be thinking: - *"Well, what if I __do__ need to assume that my defaulted associated type is what I said in a provided method, From 26291df7de9fe8e2f189a1410d9ae46e3fe815f8 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 18 Jan 2019 13:10:13 +0100 Subject: [PATCH 20/25] rfc, associated-default-groups: drop the groups part + resolve dyn Trait issue. --- text/0000-assoc-default-groups.md | 1972 ++++++++++++++++------------- 1 file changed, 1068 insertions(+), 904 deletions(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 1c7a3019784..d888368b946 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -1,4 +1,4 @@ -- Feature Name: `assoc_default_groups` +- Feature Name: `associated_type_defaults2` - Start Date: 2018-08-27 - RFC PR: _ - Rust Issue: _ @@ -8,22 +8,15 @@ [RFC 192]: https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md#defaults -1. [Resolve][changes] the design of associated type defaults, - first introduced in [RFC 192], - such that provided methods and other items may not assume type defaults. - This applies equally to `default` with respect to specialization. - -2. [Introduce][default_groups] the concept of `default { .. }` groups - in traits and their implementations which may be used to introduce - atomic units of specialization - (if anything in the group is specialized, everything must be). - These groups may be nested and form a [tree of cliques]. +[Resolve][changes] the design of associated type defaults, +first introduced in [RFC 192], +such that provided methods and other items may not assume type defaults. +This applies equally to `default` with respect to specialization. +Finally, `dyn Trait` will assume provided defaults and allow those to be elided. # Motivation [motivation]: #motivation -## For associated type defaults - As discussed in the [background] and mentioned in the [summary], associated type defaults were introduced in [RFC 192]. These defaults are valuable for a few reasons: @@ -59,8 +52,8 @@ The following points were also noted in [RFC 192], but we expand upon them here: } ``` - Being able to say that the default of `Parameters` is `()` means that users - who are not interested in this further detail may simply ignore specifying + Being able to say that the default of `Parameters` is `()` means that users, + who are not interested in this further detail, may simply ignore specifying `Parameters`. The inability of having defaults results in an inability to provide APIs @@ -117,127 +110,10 @@ The following points were also noted in [RFC 192], but we expand upon them here: The implementation `Arbitrary for usize` *remains valid* even after the change. -## For `default { .. }` groups - -Finally, because we are making [changes] to how associated type defaults work -in this RFC, a new mechanism is required to regain the loss of expressive power -due to these changes. This mechanism is described in the section on -[`default { .. }` groups][default_groups] as alluded to in the [summary]. - -These groups not only retain the expressive power due to [RFC 192] but extend -power such that users get fine grained control over what things may and may not -be overridden together. In addition, these groups allow users to assume the -definition of type defaults in other items in a way that preserves soundness. - -Examples where it is useful for other items to assume the default of an -associated type include: - -[issue#29661]: https://github.com/rust-lang/rust/issues/29661 - -[comment174527854]: https://github.com/rust-lang/rust/issues/29661#issuecomment-174527854 -[comment280944035]:https://github.com/rust-lang/rust/issues/29661#issuecomment-280944035 - -1. [A default method][comment174527854] whose - [return type is an associated type:][comment280944035] - - ```rust - /// "Callbacks" for a push-based parser - trait Sink { - fn handle_foo(&mut self, ...); - - default { - type Output = Self; - - // OK to assume what `Output` really is because any overriding - // must override both `Outout` and `finish`. - fn finish(self) -> Self::Output { self } - } - } - ``` - -2. There are plenty of other examples in [rust-lang/rust#29661][issue#29661]. - -[issue#31844]: https://github.com/rust-lang/rust/issues/31844 - -3. Other examples where `default { .. }` would have been useful can be found - in the [tracking issue][issue#31844] for [specialization]: - - + - - You can see `default { .. }` being used - [here](https://github.com/rust-lang/rust/issues/31844#issuecomment-249355377). - - + - + - + - + - -[`std::remove_reference`]: http://www.cplusplus.com/reference/type_traits/remove_reference/ - -4. Encoding a more powerful [`std::remove_reference`] - - We can encode a more powerful version of C++'s `remove_reference` construct, - which allows you to get the base type of a reference type recursively. - Without default groups, we can get access to the base type like so: - - ```rust - trait RemoveRef { - type WithoutRef; - } - - impl RemoveRef for T { - default type WithoutRef = T; - } - - impl<'a, T: RemoveRef> RemoveRef for &'a T { - type WithoutRef = T::WithoutRef; - } - ``` - - However, we don't have any way to transitively dereference to - `&Self::WithoutRef`. With default groups we can gain that ability with: - - ```rust - trait RemoveRef { - type WithoutRef; - fn single_ref(&self) -> &Self::WithoutRef; - } - - impl RemoveRef for T { - default { - type WithoutRef = T; - - fn single_ref(&self) -> &Self::WithoutRef { - // We can assume that `T == Self::WithoutRef`. - self - } - } - } - - impl<'a, T: RemoveRef> RemoveRef for &'a T { - type WithoutRef = T::WithoutRef; - - fn single_ref(&self) -> &Self::WithoutRef { - // We can assume that `T::WithoutRef == Self::WithoutRef`. - T::single_ref(*self) - } - } - ``` - - We can then proceed to writing things such as: - - ```rust - fn do_stuff(recv: impl RemoveRef) { - recv.single_ref().my_method(); - } - ``` - # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -## Associated type defaults - -### Background and The status quo +## Background and The status quo [background]: #background-and-the-status-quo Let's consider a simple trait with an associated type and another item (1): @@ -326,7 +202,7 @@ Meanwhile, as we saw in the error message above (3), the current implementation takes the alternative approach of accepting `impl Foo for Bar` (4) but not the definition of `Foo` as in (2). -### Changes in this RFC +## Changes in this RFC [changes]: #changes-in-this-rfc In this RFC, we change the approach in [RFC 192] to the currently implemented @@ -385,1056 +261,1344 @@ allowed to assume this. To permit this is not a problem because `Foo for Vec` is not further specializable since `baz` in the implementation has not been marked as `default`. -With these changes, -we consider the design of associated type defaults to be *finalized*. +### Trait objects -## `default` specialization groups -[default_groups]: #default-specialization-groups +Another divergence in this RFC as compared to the current implementation is +with respect to trait objects. Currently, if you write (7): -Note: Overlapping implementations, where one is more specific than the other, -requires actual support for [specialization]. +```rust +trait Foo { + type Bar = u8; + fn method(&self) -> Self::Bar; +} -Now, you might be thinking: - *"Well, what if I __do__ need to assume that -my defaulted associated type is what I said in a provided method, -what do I do then?"*. Don't worry; We've got you covered. +type Alpha = Box; +``` -To be able to assume that `Self::Bar` is truly `u8` in snippets (2) and (5), -you may henceforth use `default { .. }` to group associated items into atomic -units of specialization. This means that if one item in `default { .. }` is -overridden in an implementation, then all all the items must be. An example (7): +the compiler will reject it with (8): ```rust -struct Country(&'static str); +error[E0191]: the value of the associated type `Bar` (from the trait `Foo`) must be specified + --> src/lib.rs:8:17 + | +4 | type Bar = u8; + | -------------- `Bar` defined here +... +8 | type Alpha = Box; + | ^^^^^^^ associated type `Bar` must be specified +``` -struct LangSec { papers: usize } -struct CategoryTheory { papers: usize } +With this RFC however, the error in (8) will disappear and (7) will be *accepted*. +That is, `Box` is taken as equivalent as `Box>`. -trait ComputerScientist { - default { - type Details = Country; - const THE_DETAILS: Self::Details = Country("Scotland"); // OK! - fn papers(details: Self::Details) -> u8 { 19 } // OK! - } +If we complicate the situation slightly and introduce another associated `type` +`Baz` which refers to `Bar` in its default, the compiler will still let us +elide specifying the defaults (9): + +```rust +trait Foo { + type Bar = u8; + type Baz = Vec; + + fn method(&self) -> (Self::Bar, Self::Baz); } -// https://en.wikipedia.org/wiki/Emily_Riehl -struct EmilyRiehl; +type Alpha = Box; +// ------- +// Same as: `dyn Foo>`. -// https://www.cis.upenn.edu/~sweirich/ -struct StephanieWeirich; +type Beta = Box>; +// ------------------ +// Same as: `dyn Foo>`. +``` -// http://www.cse.chalmers.se/~andrei/ -struct AndreiSabelfeld; +Note that in `Beta`, `Bar` was specified but `Baz` was not. +The compiler can infer that `Baz` is `Vec` since `Self::Bar = u16` and +`Baz = Vec`. -// https://en.wikipedia.org/wiki/Conor_McBride -struct ConorMcBride; +With these changes, +we consider the design of associated type defaults to be *finalized*. -impl ComputerScientist for EmilyRiehl { - type Details = CategoryTheory; +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation - // ERROR! You must override THE_DETAILS and papers. -} +The proposal makes no changes to the dynamic semantics and the grammar of Rust. -impl ComputerScientist for StephanieWeirich { - const THE_DETAILS: Country = Country("USA"); - fn papers(details: Self::Details) -> u8 { 86 } +## Static semantics - // ERROR! You must override Details. -} +This section supersedes [RFC 192] with respect to associated type defaults. -impl ComputerScientist for AndreiSabelfeld { - type Details = LangSec; - const THE_DETAILS: Self::Details = LangSec { papers: 90 }; - fn papers(details: Self::Details) -> u8 { details.papers } +Associated types can be assigned a default type in a `trait` definition: - // OK! We have overridden all items in the group. -} +```rust +trait Foo { + type Bar = $default_type; -impl ComputerScientist for ConorMcBride { - // OK! We have not overridden anything in the group. + $other_items } ``` -You may also use `default { .. }` in implementations. -When you do so, everything in the group is automatically overridable. -For any items outside the group, you may assume their signatures, -but not the default definitions given. An example: +Any item in `$other_items`, which have any provided definitions, +may only assume that the type of `Self::Bar` is `Self::Bar`. +They may *not* assume that the underlying type of `Self::Bar` is `$default_type`. +This property is essential for the soundness of the type system. -```rust -use std::marker::PhantomData; +When an associated type default exists in a `trait` definition, +it need not be specified in the implementations of that `trait`. +If implementations of that `trait` do not make that associated type +available for specialization, the `$default_type` may be assumed +in other items specified in the implementation. +If an implementation does make the associated type available for +further specialization, then other definitions in the implementation +may not assume the given underlying specified type of the associated type +and may only assume that it is `Self::TheAsociatedType`. -trait Fruit { - type Details; - fn foo(); - fn bar(); - fn baz(); -} +This applies generally to any item inside a `trait`. +You may only assume the signature of an item, but not any provided definition, +in provided definitions of other items. +For example, this means that you may not assume the value of an +associated `const` item in other items with provided definition +in a `trait` definition. -struct Citrus { species: PhantomData } -struct Orange { variety: PhantomData } -struct Blood; -struct Common; +### Interaction with `dyn Trait<...>` -impl Fruit for Citrus { - default { - type Details = bool; - fn foo() { - let _: Self::Details = true; // OK! - } - fn bar() { - let _: Self::Details = true; // OK! - } - } ++ Let `σ` denote a well-formed type. ++ Let `L` denote a well-formed lifetime. ++ Let `X` refer to an object safe `trait`. + + Let `k` denote the number of lifetime parameters in `X`. + + Let `l` denote the number of type parameters in `X`. + + Let `m` where `0 ≤ m ≤ l` denote the number of type parameters + in `X` without specified defaults. + + Let `A` denote the set of associated types in `X`. + + Let `o = |A|`. + + Let `D` where `D ⊆ A` denote set of associated types in `X` with defaults. + + Let `E = A \ D`. - fn baz() { // Removing this item here causes an error. - let _: Self::Details = true; - // ERROR! You may not assume that `Self::Details == bool` here. - } -} +Then, in a type of form (where `m ≤ n ≤ l`): -impl Fruit for Citrus> { - default { - type Details = u8; - fn foo() { - let _: Self::Details = 42u8; // OK! - } - } +```rust +dyn X< + L0, .., Lk, + σ0, .. σn, + A0 = σ_{n + 1}, .., Ao = σ_{n + o} +> +``` - fn bar() { // Removing this item here causes an error. - let _: Self::Details = true; - // ERROR! You may not assume that `Self::Details == bool` here, - // even tho we specified that in `Fruit for Citrus`. - let _: Self::Details = 22u8; - // ERROR! Can't assume that it's u8 either! - } +the associated types in `E` must be bound in `A0, .., Ao` +whereas those in `D` may be omitted selectively (i.e. omit zero, some, or all). + +When inferring the types of the omitted projections in `D`, +projections in the assigned defaults of types in `D` will use the types in +`A0, .., Ao` instead of the defaults specified in `D`. For example, if given: + +```rust +trait X { + type A0 = u8; + type A1 = Vec; } +``` -impl Fruit for Citrus> { - default { - type Details = f32; - fn foo() { - let _: Self::Details = 1.0f32; // OK! - } - } -} +then the type `dyn X` is inferred to `dyn X>` +as opposed to `dyn X>`. -impl Fruit for Citrus> { - default { - type Details = f32; - } +### Interaction with `existential type` - fn foo() { - let _: Self::Details = 1.0f32; - // ERROR! Can't assume it is f32. - } -} -``` +[RFC 2071]: https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-existential-types.md#reference-existential-types -So far our examples have always included an associated type. -However, this is not a requirement. -We can also group associated `const`s and `fn`s together or just `fn`s. -An example: +[RFC 2071] defines a construct `existential type Foo: Bar;` which is permitted +in associated types and results in an opaque type. This means that the nominal +type identity is hidden from certain contexts and only `Bar` is extensionally +known about the type wherefore only the operations of `Bar` is afforded. +This construct is sometimes written as `type Foo = impl Bar;` in conversation +instead. + +[RFC 1210]: https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#default-impls + +With respect to this RFC, the semantics of `type Assoc = impl Bar;` +inside a trait definition, where `Assoc` is the name of the associated type, +is understood as what it means in terms of `default impl ..` as discussed +in [RFC 1210]. What this entails means in concrete terms is that given: ```rust trait Foo { - default { - const BAR: usize = 3; + type Assoc = impl Bar; - fn baz() -> [u8; Self::BAR] { - [1, 2, 3] - } - } + ... } +``` -trait Quux { - default { - fn wibble() { - ... - } +the underlying type of `Assoc` stays the same for all implementations which +do not change the default of `Assoc`. The same applies to specializations. +With respect to type opacity, it is the same as that of `existential type`. - fn wobble() { - ... - } +# Drawbacks +[drawbacks]: #drawbacks - // For whatever reason; The crate author has found it imperative - // that `wibble` and `wobble` always be defined together. - } -} -``` +The main drawbacks of this proposal are that: -### Case study -[case study]: #case-study +1. if you have implementations where you commonly would have needed to + write `default { .. }` because you need to assume the type of an + associated type default in a provided method, then the solution proposed + in this RFC is less ergonomic. -[RFC 2500]: https://github.com/rust-lang/rfcs/pull/2500 + However, it is the contention of this RFC that such needs will be less common + and that the nesting mechanism or other similar ideas will be sufficiently + ergonomic for such cases. This is discussed below. -One instance where default groups could be useful to provide a more ergonomic -API is to improve upon [RFC 2500]. The RFC proposes the following API: +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives -```rust -trait Needle: Sized { - type Searcher: Searcher; - fn into_searcher(self) -> Self::Searcher; +## Alternatives - type Consumer: Consumer; - fn into_consumer(self) -> Self::Consumer; -} -``` +The main alternative is to retain the behaviour in [RFC 192] such that +you may assume the type of associated type defaults in provided methods. +As noted in the [drawbacks] section, +this would be useful for certain types of APIs. +However, it is more likely than not that associated type defaults will +be used as a mechanism for code reuse than for other constructs. +As such, we consider the approach in this RFC to be more ergonomic. -However, it turns out that usually, `Consumer` and `Searcher` are -the same underlying type. Therefore, we would like to save the user -from some unnecessary work by letting them elide parts of the required -definitions in implementations. +Another alternative to the mechanism proposed in this RFC is to somehow +track which methods rely on which associated types as well as constants. +However, we have historically had a strong bias toward being explicit +in signatures about such things, avoiding to infer them. +With respect to semantic versioning, such an approach may also cause +surprises for crate authors and their dependents alike because it may +be difficult at glance to decide what the dependencies are. +This in turn reduces the maintainability and readability of code. -One might imagine that we'd write: +## Consistency with associated `const`s + +Consider the following valid example from stable Rust: ```rust -trait Needle: Sized { - type Searcher: Searcher; - fn into_searcher(self) -> Self::Searcher; +trait Foo { + const BAR: usize = 1; - default { - type Consumer: Consumer = Self::Searcher; - fn into_consumer(self) -> Self::Consumer { self.into_searcher() } - } + fn baz() { println!("Hi I'm baz."); } +} + +impl Foo for () { + fn baz() { println!("Hi I'm () baz."); } } ``` -However, the associated type `Searcher` does not necessarily implement -`Consumer`. Therefore, the above definition would not type check. +As we can see, you are permitted to override `baz` but leave `BAR` defaulted. +This is consistent with the behaviour in this RFC in that it has the same +property: *"you don't need to override all items if you override one"*. -However, we can encode the above construct by rewriting it slightly, -using the concept of partial implementations from [RFC 1210]: +Consistency and uniformity of any programming language is vital to make +its learning easy and to rid users of surprising corner cases and caveats. +By staying consistent, as shown above, we can reduce the cost to our complexity +budget that associated type defaults incur. -```rust -default impl Needle for T -where Self::Searcher: Consumer { - default { - type Consumer = Self::Searcher; - fn into_consumer(self) -> Self::Consumer { self.into_searcher() } - } -} -``` +## Overriding everything is less ergonomic -Now we have ensured that `Self::Searcher` is a `Consumer` -and therefore, the above definition will type check. -Having done this, the API has become more ergonomic because we can -let users define instances of `Needle` with half as many requirements. +We have already discussed this to some extent. +Another point to consider is that Rust code frequently sports traits such as +`Iterator` and `Future` that have many provided methods and few associated types. +While these particular traits may not benefit from associated type defaults, +many other traits, such as `Arbitrary` defined in the [motivation], would. -### `default fn foo() { .. }` is syntactic sugar +## True API evolution by inferring in `dyn Trait` -In the section of [changes] to associated type defaults, -snippet (5) actually indirectly introduced default groups of a special form, -namely "singleton groups". That is, when we wrote: +While `impl Trait` will not take associated type defaults into account, +`dyn trait` will. This may seem inconsistent. However, it is justified by the +inherent difference in semantics between these constructs and by the goal set +out in the [motivation] to facilitate API evolution. + +As an illustration, consider `Iterator`: ```rust -impl Foo for Wibble { - default type Bar = u8; +trait Iterator { + type Item; - default fn quux(x: Self::Bar) -> u8 { x } + ... } ``` -this was actually sugar for: +Currently, you may write: ```rust -impl Foo for Wibble { - default { - type Bar = u8; - } - - default { - fn quux(x: Self::Bar) -> u8 { x } - } -} +fn foo() -> impl Iterator { 0..1 } ``` -We can see that these are equivalent since in the [specialization] RFC, -the semantics of `default fn` were that `fn` may be overridden in more -specific implementations. With these singleton groups, you may assume -the body of `Bar` in all other items in the same group; but it just -happens to be the case that there are no other items in the group. - -### Nesting and a tree of cliques -[tree of cliques]: #nesting-and-a-tree-of-cliques +and when `foo` is called, you will know nothing about `Item`. -In the [summary], we alluded to the notion of groups being nested. -However, thus far we have seen no examples of such nesting. -This RFC does permit you do that. For example, you may write: +However, you cannot write: ```rust -trait Foo { - default { - type Bar = usize; +fn bar() -> Box { Box::new(0..1) } +``` - fn alpha() -> Self::Bar { - 0 // OK! In the same group, so we may assume `Self::Bar == usize`. - } +since the associated type `Item` is not specified. - // OK; we can rely on `Self::Bar == usize`. - default const BETA: Self::Bar = 3; +In `bar`, the type of `Item` is unknown and so the compiler does not know how +to generate the vtable. As a result, an error is emitted: - default fn gamma() -> [Self::Bar; 4] { - // OK; we can depend on the underlying type of `Self::Bar`. - [9usize, 8, 7, 6] - } +```rust +L | fn bar() -> Box { Box::new(0..1) } + | ^^^^^^^^^^^^ missing associated type `Item` value +``` - /// This is rejected: - default fn delta() -> [Self::Bar; Self::BETA] { - // ERROR! we may not rely on not on `Self::BETA`'s value because - // `Self::BETA` is a sibling of `Self::gamma` which is not in the - // same group and is not an ancestor either. - [9usize, 8, 7] - } +If we introduced a default for `Item`: - // But this is accepted: - default fn delta() -> [Self::Bar; 3] { - // OK; we can depend on `Self::Bar == usize`. - [9, 8, 7] - } +```rust + type Item = (); +``` - default { - // OK; we can still depend on `Self::Bar == usize`. - const EPSILON: Self::Bar = 2; +then `bar` would become legal under this RFC and so strictly more code than +today would be accepted. - fn zeta() -> [Self::Bar; Self::Epsilon] { - // OK; We can assume the value of `Self::EPSILON` because it - // is a sibling in the same group. We may also assume that - // `Self::Bar == usize` because it is an ancestor. - [42usize, 24] - } - } - } -} +Meanwhile, if `impl Iterator` meant `impl Iterator`, +this would impose a stronger requirement on existing code where `impl Iterator` +is used and thus it would be a breaking change to the users of `Iterator`. -struct Eta; -struct Theta; -struct Iota; +For `Iterator`, it would not be helpful to introduce a default for `Item`. +However, for the purposes of API evolution, the value is not in assigning +defaults to the existing associated types of a trait. Rather, the value comes +from being able to add associated types without breaking dependent crates. -impl Foo for Eta { - // We can override `gamma` without overriding anything else because - // `gamma` is the sole member of its sub-group. Note in particular - // that we don't have to override `alpha`. - fn gamma() -> [Self::Bar; 4] { - [43, 42, 41, 40] - } -} +Due to the possible breakage of `dyn Trait<..>` when adding an associated type +to `Trait`, to truly achieve API evolution, defaults must be taken into account +and be inferable for `dyn Trait`. The opposite is true for `impl Trait`. +To facilitate API evolution, stronger requirements must not be placed on +`impl Trait` and therefore defaults should not be taken into account. -impl Bar for Theta { - // Since `EPSILON` and `zeta` are in the same group; we must override - // them together. However, we still don't have to override anything - // in ancestral groups. - const EPSILON: Self::Bar = 0; +# Prior art +[prior-art]: #prior-art - fn zeta() -> [Self::Bar; Self::Epsilon] { - [] - } -} +## Haskell -impl Bar for Iota { - // We have overridden `Bar` which is in the root group. - // Since all other items are decendants of the same group as `Bar` is in, - // they are allowed to depend on what `Bar` is. - type Bar = u8; +[associated type defaults]: https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/at-syns.pdf - ... // Definitions for all the other items elided for brevity. -} -``` +As Rust traits are a form of type classes, +we naturally look for prior art from were they first were introduced. +That language, being Haskell, +permits a user to specify [associated type defaults]. +For example, we may write the following legal program: -[clique]: https://en.wikipedia.org/wiki/Clique_(graph_theory) +```haskell +{-# LANGUAGE TypeFamilies #-} -In graph theory, a set of a vertices, in a graph, for which each distinct pair -of vertices is connected by a unique edge is said to form a [clique]. -What the snippet above encodes is a tree of such cliques. In other words, -we can visualize the snippet as: +class Foo x where + type Bar x :: * + -- A default: + type Bar x = Int -``` - ┏━━━━━━━━━━━━━━━━━┓ - ┃ + type Bar ┃ - ┏━━━━━━━━━━━━━┃ + fn alpha ┃━━━━━━━━━━━━━━┓ - ┃ ┗━━━━━━━━━━━━━━━━━┛ ┃ - ┃ ┃ ┃ ┃ - ┃ ┃ ┃ ┃ - ▼ ▼ ▼ ▼ -┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━┓ -┃ + const Beta ┃ ┃ + fn gamma ┃ ┃ + fn delta ┃ ┃ + const EPSILON ┃ -┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┃ + fn zeta ┃ - ┗━━━━━━━━━━━━━━━━━┛ + -- Provided method: + baz :: x -> Bar x -> Int + baz _ _ = 0 + +data Quux = Quux + +instance Foo Quux where + baz _ y = y ``` -Please pay extra attention to the fact that items in the same group may -depend on each other's definitions as well as definitions of items that -are ancestors (up the tree). The inverse implication holds for what you -must override: if you override one item in a group, you must override -all items in that groups and all items in sub-groups (recursively). -As before, these limitations exist to preserve the soundness of the type system. +As in this proposal, we may assume that `y :: Int` in the above snippet. -Nested groups are intended primarily expected to be used when there is one -associated type, for which you want to define a default, coupled with a bunch -of functions which need to rely on the definition of the associated type. -This is a good mechanism for API evolution in the sense that you can introduce -a new associated type, rely on it in provided methods, but still perform -no breaking change. +In this case, we are not assuming that `Bar x` unifies with `Int` in the `class`. +Let's try to assume that now: -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation +```haskell +{-# LANGUAGE TypeFamilies #-} -## Grammar -[grammar]: #grammar +class Foo x where + type Bar x :: * + -- A default: + type Bar x = Int -Productions in this section which are not defined here are taken from -[parser-lalr.y](https://github.com/rust-lang/rust/blob/master/src/grammar/parser-lalr.y). + -- Provided method: + baz :: x -> Bar x -> Int + baz _ barX = barX +``` -Given: +This snippet results in a type checking error (tested on GHC 8.0.1): ``` -trait_item : maybe_outer_attrs trait_item_leaf ; +main.hs:11:16: error: + • Couldn't match expected type ‘Int’ with actual type ‘Bar x’ + • In the expression: barX + In an equation for ‘baz’: baz _ barX = barX + • Relevant bindings include + barX :: Bar x (bound at main.hs:11:9) + baz :: x -> Bar x -> Int (bound at main.hs:11:3) +:3:1: error: +``` -trait_item_leaf -: trait_const -| trait_type -| trait_method -| item_macro -; +The thing to pay attention to here is: +> Couldn't match expected type ‘`Int`’ with actual type ‘`Bar x`’ -trait_const -: CONST ident maybe_ty_ascription maybe_const_default ';' -; +We can clearly see that the type checker is *not* allowing us to assume +that `Int` and `Bar x` are the same type. +This is consistent with the approach this RFC proposes. -trait_type : TYPE ty_param ';' ; +To our knowledge, Haskell does not have any means such as `default { .. }` +to change this behaviour. Presumably, this is the case because Haskell +preserves parametricity thus lacking specialization, +wherefore `default { .. }`, as suggested in the [future possibilities], +might not carry its weight. -trait_method : method_prefix method_common ';' | method_prefix method_provided ; -method_prefix : maybe_unsafe | CONST maybe_unsafe | maybe_unsafe EXTERN maybe_abi ; -method_provided : method_common inner_attrs_and_block ; -method_common -: FN ident generic_params fn_decl_with_self_allow_anon_params maybe_where_clause -; -``` +## Idris -The production `trait_item` is changed into: +[idris_interface]: http://docs.idris-lang.org/en/latest/tutorial/interfaces.html +[coherence]: http://blog.ezyang.com/2014/07/type-classes-confluence-coherence-global-uniqueness/ -``` -trait_item : maybe_outer_attrs trait_item_def ; +Idris has a concept it calls [`interface`s][idris_interface]. +These resemble type classes in Haskell, and by extension traits in Rust. +However, unlike Haskell and Rust, these `interface`s do not have the property +of [coherence] and will permit multiple implementations of the same interface. -trait_item_def -: trait_default_group -| trait_default_singleton -| trait_const -| trait_type -| trait_method -| item_macro -; +Since Idris is language with full spectrum dependent types, +it does not distinguish between terms and types, instead, types are terms. +Therefore, there is really not a distinct concept called "associated type". +However, an `interface` may require certain definitions to be provided +and this includes types. For example, we may write: -trait_default_singleton : DEFAULT trait_item ; -trait_default_group : DEFAULT '{' trait_item* '}' ; +```idris +interface Iterator self where + item : Type + next : self -> Maybe (self, item) -trait_type : TYPE ty_param ('=' ty_sum)? ';' ; +implementation Iterator (List a) where + item = a + next [] = Nothing + next (x :: xs) = Just (xs, x) ``` -Given: +Like in Haskell, in Idris, a function or value in an interface may be given a +default definition. For example, the following is a valid program: -``` -impl_item : attrs_and_vis impl_item_leaf ; -impl_item_leaf -: item_macro -| maybe_default impl_method -| maybe_default impl_const -| maybe_default impl_type -; +```idris +interface Foo x where + bar : Type + bar = Bool -impl_const : item_const ; -impl_type : TYPE ident generic_params '=' ty_sum ';' ; -impl_method : method_prefix method_common ; + baz : x -> bar -method_common -: FN ident generic_params fn_decl_with_self maybe_where_clause inner_attrs_and_block -; +implementation Foo Int where + baz x = x == 0 ``` -The production `impl_item` is changed into: +However, if we provide a default for `baz` in the `interface` which assumes +the default value `Bool` of `bar`, as with the following example: +```idris +interface Foo x where + bar : Type + bar = Bool + + baz : x -> bar + baz _ = True ``` -impl_item : attrs_and_vis impl_item_def ; -impl_item_def -: impl_default_singleton -| impl_default_group -| item_macro -| impl_method -| impl_const -| impl_type -; -impl_default_singleton : DEFAULT impl_item ; -impl_default_group : DEFAULT '{' impl_item* '}' ; +then we run into an error: + ``` +Type checking .\foo.idr +foo.idr:6:13-16: + | +6 | baz _ = True + | ~~~~ +When checking right hand side of Main.default#baz with expected type + bar x _ -Note that associated type defaults are already in the grammar due to [RFC 192] -but we have specified them in the grammar here nonetheless. +Type mismatch between + Bool (Type of True) +and + bar x _ (Expected type) +``` -Note also that `default default fn ..` as well as `default default { .. }` are -intentionally recognized by the grammar to make life easier for macro authors -even though writing `default default ..` should never be written directly. +The behaviour here is exactly as in Haskell and as proposed in this RFC. -## Desugaring +## C++ -After macro expansion, wherever the production `trait_default_singleton` occurs, -it is treated in all respects as, except for error reporting -- which is left up -to implementations of Rust, and is desugared to `DEFAULT '{' trait_item '}'`. -The same applies to `impl_default_singleton`. -In other words: `default fn f() {}` is desugared to `default { fn f() {} }`. +In C++, it is possible to provide associated types and specialize them as well. +This is shown in the following example: -## Semantics and type checking +```cpp +#include +#include -### Semantic restrictions on the syntax +template struct wrap {}; -According to the [grammar], the parser will accept items inside `default { .. }` -without a body. However, such an item will later be rejected during type checking. -The parser will also accept visibility modifiers on `default { .. }` -(e.g. `pub default { .. }`). However, such a visibility modifier will also be -rejected by the type checker. +template struct foo { // Unspecialized. + using bar = int; -### Associated type defaults + bar make_a_bar() { return 0; }; +}; -This section supersedes [RFC 192] with respect to associated type defaults. +template struct foo> { // Partial specialization. + using bar = std::string; -Associated types can be assigned a default type in a `trait` definition: + bar make_a_bar() { return std::string("hello world"); }; +}; -```rust -trait Foo { - type Bar = $default_type; +int main() { + foo a_foo; + std::cout << a_foo.make_a_bar() << std::endl; - $other_items + foo> b_foo; + std::cout << b_foo.make_a_bar() << std::endl; } ``` -Any item in `$other_items`, which have any provided definitions, -may only assume that the type of `Self::Bar` is `Self::Bar`. -They may *not* assume that the underlying type of `Self::Bar` is `$default_type`. -This property is essential for the soundness of the type system. - -When an associated type default exists in a `trait` definition, -it need not be specified in the implementations of that `trait`. -If implementations of that `trait` do not make that associated type -available for specialization, the `$default_type` may be assumed -in other items specified in the implementation. -If an implementation does make the associated type available for -further specialization, then other definitions in the implementation -may not assume the given underlying specified type of the associated type -and may only assume that it is `Self::TheAsociatedType`. +You will note that C++ allows us to assume in both the base template class, +as well as the specialization, that `bar` is equal to the underlying type. +This is because one cannot specialize any part of a class without specializing +the whole of it. It's equivalent to one atomic `default { .. }` block. -This applies generally to any item inside a `trait`. -You may only assume the signature of an item, but not any provided definition, -in provided definitions of other items. -For example, this means that you may not assume the value of an -associated `const` item in other items with provided definition -in a `trait` definition. +## Swift -#### Interaction with `existential type` +[swift_assoc]: https://docs.swift.org/swift-book/LanguageGuide/Generics.html -[RFC 2071]: https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-existential-types.md#reference-existential-types +One language which does have [associated types][swift_assoc] and defaults but +which does not have provided definitions for methods is Swift. +As an example, we may write: -[RFC 2071] defines a construct `existential type Foo: Bar;` which is permitted -in associated types and results in an opaque type. This means that the nominal -type identity is hidden from certain contexts and only `Bar` is extensionally -known about the type wherefore only the operations of `Bar` is afforded. -This construct is sometimes written as `type Foo = impl Bar;` in conversation -instead. - -[RFC 1210]: https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#default-impls - -With respect to this RFC, the semantics of `type Assoc = impl Bar;` -inside a trait definition, where `Assoc` is the name of the associated type, -is understood as what it means in terms of `default impl ..` as discussed -in [RFC 1210]. What this entails means in concrete terms is that given: +```swift +protocol Foo { + associatedtype Bar = Int -```rust -trait Foo { - type Assoc = impl Bar; + func append() -> Bar +} - ... +struct Quux: Foo { + func baz() -> Bar { + return 1 + } } ``` -the underlying type of `Assoc` stays the same for all implementations which -do not change the default of `Assoc`. The same applies to specializations. -With respect to type opacity, it is the same as that of `existential type`. - -### Specialization groups +However, we may not write: -Implementations of a `trait` as well as `trait`s themselves may now -contain *"specialization default groups"* (henceforth: *"group(s)"*) -as defined by the [grammar]. +```swift +protocol Foo { + associatedtype Bar = Int -A group forms a [clique] and is considered an *atomic unit of specialization* -wherein each item can be specialized / overridden. + func append() -> Bar { return 0 } +} +``` -Groups may contain other groups - such groups are referred to as -*"nested groups"* and may be nested arbitrarily deeply. -Items which are not in any group are referred to as *`0`-deep*. -An item directly defined in a group which occurs at the top level of a -`trait` or an `impl` definition is referred to as being *`1`-deep*. -An item in a group which is contained in a *`1`-deep* group is *`2`-deep*. -If an item is nested in `k` groups it is *`k`-deep*. +This would result in: -A group and its sub-groups form a *tree of cliques*. -Given a group `$g` with items `$x_1, .. $x_n`, an item `$x_j` in `$g` -can assume the definitions of `$x_i, ∀ i ∈ { 1..n }` as well as any -definitions of items in `$f` where `$f` is an ancestor of `$g` (up the tree). -Conversely, items in `$g` may not assume the definitions of items in -descendant groups `$h_i` of `$g` as well as items which are grouped at all -or which are in groups which are not ancestors of `$g`. +``` +main.swift:4:23: error: protocol methods may not have bodies + func baz() -> Bar { return 0 } +``` -If an `impl` block overrides one item `$x_j` in `$g`, -it also has to override all `$x_i` in `$g` where `i ≠ j` as well as -all items in groups `$h_i` which are descendants of `$g` (down the tree). -Otherwise, items do not need to be overridden. +## Scala -For example, you may write: +Another language which allows for these kinds of type projections and defaults +for them is Scala. While Scala does not have type classes like Rust and Haskell +does, it does have a concept of `trait` which can be likened to a sort of +incoherent "type class" system. For example, we may write: -```rust +```scala trait Foo { - default { - type Bar = u8; - fn baz() { - let _: Self::Bar = 1u8; - } + type Bar = Int - default { - const SIZE: usize = 3; - fn quux() { - let_: [Self::Bar; Self::SIZE] = [1u8, 2u8, 3u8]; - } - } - } + def baz(x: Bar): Int = x } -impl Foo for () { - type Bar = Vec; - fn baz() {} - const SIZE: usize = 5; - fn quux() {} +class Quux extends Foo { + override type Bar = Int + override def baz(x: Bar): Int = x } ``` -## Linting redundant `default`s - -When in source code (but not as a consequence of macro expansion), -any of the following occurs, a warn-by-default lint (`redundant_default`) -will be emitted: +There are a few interesting things to note here: -```rust - default default $item -// ^^^^^^^ warning: Redundant `default` -// hint: remove `default`. +1. We are allowed to specify a default type `Int` for `Bar`. - default default { -// ^^^^^^^ warning: Redundant `default` -// hint: remove `default`. - ... - } +2. A default definition for `baz` may be provided. - default { - ... +3. This default definition may assume the default given for `Bar`. - default $item -// ^^^^^^^ warning: Redundant `default` -// hint: remove `default`. +4. However, we *must* explicitly state that we are overriding `baz`. - ... - } -``` +5. If we change the definition of of `override type Bar` to `Double`, + the Scala compiler will reject it. -# Drawbacks -[drawbacks]: #drawbacks +# Unresolved questions +[unresolved-questions]: #unresolved-questions -The main drawbacks of this proposal are that: +There are none. -1. `default { .. }` is introduced, adding to the complexity of the language. +# Future possibilities +[future-possibilities]: #future-possibilities - However, it should be noted that token `default` is already accepted for - use by specialization and for `default impl`. - Therefore, the syntax is only partially new. +This section in the RFC used to be part of the proposal. To provide context +for considerations made in the proposal, it is recorded here. -2. if you have implementations where you commonly need to write `default { .. }` - because you need to assume the type of an associated type default in a - provided method, then the solution proposed in this RFC is less ergonomic. +## Summary - However, it is the contention of this RFC that such needs will be less common - and the nesting mechanism is sufficiently ergonomic for such cases. - This is discussed below. +[Introduce][default_groups] the concept of `default { .. }` groups in traits +and their implementations which may be used to introduce atomic units of +specialization (if anything in the group is specialized, everything must be). +These groups may be nested and form a [tree of cliques]. -# Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives +## Motivation -## Alternatives +### For `default { .. }` groups -The main alternative is to retain the behaviour in [RFC 192] such that -you may assume the type of associated type defaults in provided methods. -As noted in the [drawbacks] section, -this would be useful for certain types of APIs. -However, it is more likely than not that associated type defaults will -be used as a mechanism for code reuse than for other constructs. -As such, we consider the approach in this RFC to be the more ergonomic approach. +Finally, because we are making [changes] to how associated type defaults work +in this RFC, a new mechanism is required to regain the loss of expressive power +due to these changes. This mechanism is described in the section on +[`default { .. }` groups][default_groups] as alluded to in the summary. -Another alternative to the mechanism proposed in this RFC is to somehow -track which methods rely on which associated types as well as constants. -However, we have historically had a strong bias toward being explicit -in signatures about such things, avoiding to infer them. -With respect to semantic versioning, such an approach may also cause -surprises for crate authors and their dependents alike because it may -be difficult at glance to decide what the dependencies are. -This in turn reduces the maintainability and readability of code. +These groups not only retain the expressive power due to [RFC 192] but extend +power such that users get fine grained control over what things may and may not +be overridden together. In addition, these groups allow users to assume the +definition of type defaults in other items in a way that preserves soundness. -One may also consider mechanisms such as `default(Bar, BAZ) { .. }` to give -more freedom as to which dependency graphs may be encoded. -However, in practice, we believe that the *tree of cliques* approach proposed -in this RFC should be more than enough for practical applications. +Examples where it is useful for other items to assume the default of an +associated type include: -## Consistency with associated `const`s +[issue#29661]: https://github.com/rust-lang/rust/issues/29661 -Consider the following valid example from stable Rust: +[comment174527854]: https://github.com/rust-lang/rust/issues/29661#issuecomment-174527854 +[comment280944035]:https://github.com/rust-lang/rust/issues/29661#issuecomment-280944035 -```rust -trait Foo { - const BAR: usize = 1; +1. [A default method][comment174527854] whose + [return type is an associated type:][comment280944035] - fn baz() { println!("Hi I'm baz."); } -} + ```rust + /// "Callbacks" for a push-based parser + trait Sink { + fn handle_foo(&mut self, ...); + + default { + type Output = Self; + + // OK to assume what `Output` really is because any overriding + // must override both `Outout` and `finish`. + fn finish(self) -> Self::Output { self } + } + } + ``` -impl Foo for () { - fn baz() { println!("Hi I'm () baz."); } -} -``` +2. There are plenty of other examples in [rust-lang/rust#29661][issue#29661]. -As we can see, you are permitted to override `baz` but leave `BAR` defaulted. -This is consistent with the behaviour in this RFC in that it has the same -property: *"you don't need to override all items if you override one"*. +[issue#31844]: https://github.com/rust-lang/rust/issues/31844 -Consistency and uniformity of any programming language is vital to make -its learning easy and to rid users of surprising corner cases and caveats. -By staying consistent, as shown above, we can reduce the cost to our complexity -budget that associated type defaults incur. +3. Other examples where `default { .. }` would have been useful can be found + in the [tracking issue][issue#31844] for [specialization]: -## Overriding everything is less ergonomic + + -We have already discussed this to some extent. -Another point to consider is that Rust code frequently sports traits such as -`Iterator` and `Future` that have many provided methods and few associated types. -While these particular traits may not benefit from associated type defaults, -many other traits, such as `Arbitrary` defined in the [motivation], would. + You can see `default { .. }` being used + [here](https://github.com/rust-lang/rust/issues/31844#issuecomment-249355377). -## `default { .. }` is syntactically light-weight + + + + + + + + -When you actually do need to assume the underlying default of an associated type -in a provided method, `default { .. }` provides a syntax that is comparatively -not *that* heavy weight. +[`std::remove_reference`]: http://www.cplusplus.com/reference/type_traits/remove_reference/ -In addition, when you want to say that multiple items are overridable, -`default { .. }` provides less repetition than specifying `default` on -each item would. Thus, we believe the syntax is ergonomic. +4. Encoding a more powerful [`std::remove_reference`] -Finally, `default { .. }` works well and allows the user a good deal of control -over what can and can't be assumed and what must be specialized together. -The grouping mechanism also composes well as seen in -[the section where it is discussed][default_groups]. + We can encode a more powerful version of C++'s `remove_reference` construct, + which allows you to get the base type of a reference type recursively. + Without default groups, we can get access to the base type like so: -## Tree of cliques is familiar + ```rust + trait RemoveRef { + type WithoutRef; + } -The *"can depend on"* rule is similar to the rule used to determine whether a -non-`pub` item in a module tree is accessible or not. -Familiarity is a good tool to limit complexity costs. + impl RemoveRef for T { + default type WithoutRef = T; + } -## Non-special treatment for methods + impl<'a, T: RemoveRef> RemoveRef for &'a T { + type WithoutRef = T::WithoutRef; + } + ``` -In this RFC we haven't given methods any special treatment. -We could do so by allowing methods to assume the underlying type -of an associated type and still be overridable without having to override -the type. However, this might lead to *semantic breakage* in the sense that -the details of an `fn` may be tied to the definition of an associated type. -When those details change, it may also be prudent to change the associated type. -Default groups give users a mechanism to enforce such decisions. + However, we don't have any way to transitively dereference to + `&Self::WithoutRef`. With default groups we can gain that ability with: -# Prior art -[prior-art]: #prior-art + ```rust + trait RemoveRef { + type WithoutRef; + fn single_ref(&self) -> &Self::WithoutRef; + } -## Haskell + impl RemoveRef for T { + default { + type WithoutRef = T; -[associated type defaults]: https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/at-syns.pdf + fn single_ref(&self) -> &Self::WithoutRef { + // We can assume that `T == Self::WithoutRef`. + self + } + } + } -As Rust traits are a form of type classes, -we naturally look for prior art from were they first were introduced. -That language, being Haskell, -permits a user to specify [associated type defaults]. -For example, we may write the following legal program: + impl<'a, T: RemoveRef> RemoveRef for &'a T { + type WithoutRef = T::WithoutRef; -```haskell -{-# LANGUAGE TypeFamilies #-} + fn single_ref(&self) -> &Self::WithoutRef { + // We can assume that `T::WithoutRef == Self::WithoutRef`. + T::single_ref(*self) + } + } + ``` -class Foo x where - type Bar x :: * - -- A default: - type Bar x = Int + We can then proceed to writing things such as: - -- Provided method: - baz :: x -> Bar x -> Int - baz _ _ = 0 + ```rust + fn do_stuff(recv: impl RemoveRef) { + recv.single_ref().my_method(); + } + ``` -data Quux = Quux +## Guide-level explanation -instance Foo Quux where - baz _ y = y +### `default` specialization groups +[default_groups]: #default-specialization-groups + +Note: Overlapping implementations, where one is more specific than the other, +requires actual support for [specialization]. + +Now, you might be thinking: - *"Well, what if I __do__ need to assume that +my defaulted associated type is what I said in a provided method, +what do I do then?"*. Don't worry; We've got you covered. + +To be able to assume that `Self::Bar` is truly `u8` in snippets (2) and (5), +you may henceforth use `default { .. }` to group associated items into atomic +units of specialization. This means that if one item in `default { .. }` is +overridden in an implementation, then all all the items must be. An example (7): + +```rust +struct Country(&'static str); + +struct LangSec { papers: usize } +struct CategoryTheory { papers: usize } + +trait ComputerScientist { + default { + type Details = Country; + const THE_DETAILS: Self::Details = Country("Scotland"); // OK! + fn papers(details: Self::Details) -> u8 { 19 } // OK! + } +} + +// https://en.wikipedia.org/wiki/Emily_Riehl +struct EmilyRiehl; + +// https://www.cis.upenn.edu/~sweirich/ +struct StephanieWeirich; + +// http://www.cse.chalmers.se/~andrei/ +struct AndreiSabelfeld; + +// https://en.wikipedia.org/wiki/Conor_McBride +struct ConorMcBride; + +impl ComputerScientist for EmilyRiehl { + type Details = CategoryTheory; + + // ERROR! You must override THE_DETAILS and papers. +} + +impl ComputerScientist for StephanieWeirich { + const THE_DETAILS: Country = Country("USA"); + fn papers(details: Self::Details) -> u8 { 86 } + + // ERROR! You must override Details. +} + +impl ComputerScientist for AndreiSabelfeld { + type Details = LangSec; + const THE_DETAILS: Self::Details = LangSec { papers: 90 }; + fn papers(details: Self::Details) -> u8 { details.papers } + + // OK! We have overridden all items in the group. +} + +impl ComputerScientist for ConorMcBride { + // OK! We have not overridden anything in the group. +} ``` -As in this proposal, we may assume that `y :: Int` in the above snippet. +You may also use `default { .. }` in implementations. +When you do so, everything in the group is automatically overridable. +For any items outside the group, you may assume their signatures, +but not the default definitions given. An example: -In this case, we are not assuming that `Bar x` unifies with `Int` in the `class`. -Let's try to assume that now: +```rust +use std::marker::PhantomData; -```haskell -{-# LANGUAGE TypeFamilies #-} +trait Fruit { + type Details; + fn foo(); + fn bar(); + fn baz(); +} -class Foo x where - type Bar x :: * - -- A default: - type Bar x = Int +struct Citrus { species: PhantomData } +struct Orange { variety: PhantomData } +struct Blood; +struct Common; - -- Provided method: - baz :: x -> Bar x -> Int - baz _ barX = barX +impl Fruit for Citrus { + default { + type Details = bool; + fn foo() { + let _: Self::Details = true; // OK! + } + fn bar() { + let _: Self::Details = true; // OK! + } + } + + fn baz() { // Removing this item here causes an error. + let _: Self::Details = true; + // ERROR! You may not assume that `Self::Details == bool` here. + } +} + +impl Fruit for Citrus> { + default { + type Details = u8; + fn foo() { + let _: Self::Details = 42u8; // OK! + } + } + + fn bar() { // Removing this item here causes an error. + let _: Self::Details = true; + // ERROR! You may not assume that `Self::Details == bool` here, + // even tho we specified that in `Fruit for Citrus`. + let _: Self::Details = 22u8; + // ERROR! Can't assume that it's u8 either! + } +} + +impl Fruit for Citrus> { + default { + type Details = f32; + fn foo() { + let _: Self::Details = 1.0f32; // OK! + } + } +} + +impl Fruit for Citrus> { + default { + type Details = f32; + } + + fn foo() { + let _: Self::Details = 1.0f32; + // ERROR! Can't assume it is f32. + } +} ``` -This snippet results in a type checking error (tested on GHC 8.0.1): +So far our examples have always included an associated type. +However, this is not a requirement. +We can also group associated `const`s and `fn`s together or just `fn`s. +An example: + +```rust +trait Foo { + default { + const BAR: usize = 3; + + fn baz() -> [u8; Self::BAR] { + [1, 2, 3] + } + } +} + +trait Quux { + default { + fn wibble() { + ... + } + fn wobble() { + ... + } + + // For whatever reason; The crate author has found it imperative + // that `wibble` and `wobble` always be defined together. + } +} ``` -main.hs:11:16: error: - • Couldn't match expected type ‘Int’ with actual type ‘Bar x’ - • In the expression: barX - In an equation for ‘baz’: baz _ barX = barX - • Relevant bindings include - barX :: Bar x (bound at main.hs:11:9) - baz :: x -> Bar x -> Int (bound at main.hs:11:3) -:3:1: error: + +#### Case study +[case study]: #case-study + +[RFC 2500]: https://github.com/rust-lang/rfcs/pull/2500 + +One instance where default groups could be useful to provide a more ergonomic +API is to improve upon [RFC 2500]. The RFC proposes the following API: + +```rust +trait Needle: Sized { + type Searcher: Searcher; + fn into_searcher(self) -> Self::Searcher; + + type Consumer: Consumer; + fn into_consumer(self) -> Self::Consumer; +} ``` -The thing to pay attention to here is: -> Couldn't match expected type ‘`Int`’ with actual type ‘`Bar x`’ +However, it turns out that usually, `Consumer` and `Searcher` are +the same underlying type. Therefore, we would like to save the user +from some unnecessary work by letting them elide parts of the required +definitions in implementations. -We can clearly see that the type checker is *not* allowing us to assume -that `Int` and `Bar x` are the same type. -This is consistent with the approach this RFC proposes. +One might imagine that we'd write: -To our knowledge, Haskell does not have any means such as `default { .. }` -to change this behaviour. Presumably, this is the case because Haskell -preserves parametricity and lacks specialization, -wherefore `default { .. }` might not carry its weight. +```rust +trait Needle: Sized { + type Searcher: Searcher; + fn into_searcher(self) -> Self::Searcher; -## Idris + default { + type Consumer: Consumer = Self::Searcher; + fn into_consumer(self) -> Self::Consumer { self.into_searcher() } + } +} +``` -[idris_interface]: http://docs.idris-lang.org/en/latest/tutorial/interfaces.html -[coherence]: http://blog.ezyang.com/2014/07/type-classes-confluence-coherence-global-uniqueness/ +However, the associated type `Searcher` does not necessarily implement +`Consumer`. Therefore, the above definition would not type check. -Idris has a concept it calls [`interface`s][idris_interface]. -These resemble type classes in Haskell, and by extension traits in Rust. -However, unlike Haskell and Rust, these `interface`s do not have the property -of [coherence] and will permit multiple implementations of the same interface. +However, we can encode the above construct by rewriting it slightly, +using the concept of partial implementations from [RFC 1210]: -Since Idris is language with full spectrum dependent types, -it does not distinguish between terms and types, instead, types are terms. -Therefore, there is really not a distinct concept called "associated type". -However, an `interface` may require certain definitions to be provided -and this includes types. For example, we may write: +```rust +default impl Needle for T +where Self::Searcher: Consumer { + default { + type Consumer = Self::Searcher; + fn into_consumer(self) -> Self::Consumer { self.into_searcher() } + } +} +``` -```idris -interface Iterator self where - item : Type - next : self -> Maybe (self, item) +Now we have ensured that `Self::Searcher` is a `Consumer` +and therefore, the above definition will type check. +Having done this, the API has become more ergonomic because we can +let users define instances of `Needle` with half as many requirements. -implementation Iterator (List a) where - item = a - next [] = Nothing - next (x :: xs) = Just (xs, x) +#### `default fn foo() { .. }` is syntactic sugar + +In the section of [changes] to associated type defaults, +snippet (5) actually indirectly introduced default groups of a special form, +namely "singleton groups". That is, when we wrote: + +```rust +impl Foo for Wibble { + default type Bar = u8; + + default fn quux(x: Self::Bar) -> u8 { x } +} ``` -Like in Haskell, in Idris, a function or value in an interface may be given a -default definition. For example, the following is a valid program: +this was actually sugar for: -```idris -interface Foo x where - bar : Type - bar = Bool +```rust +impl Foo for Wibble { + default { + type Bar = u8; + } + + default { + fn quux(x: Self::Bar) -> u8 { x } + } +} +``` + +We can see that these are equivalent since in the [specialization] RFC, +the semantics of `default fn` were that `fn` may be overridden in more +specific implementations. With these singleton groups, you may assume +the body of `Bar` in all other items in the same group; but it just +happens to be the case that there are no other items in the group. + +#### Nesting and a tree of cliques +[tree of cliques]: #nesting-and-a-tree-of-cliques + +In the summary, we alluded to the notion of groups being nested. +However, thus far we have seen no examples of such nesting. +This RFC does permit you do that. For example, you may write: + +```rust +trait Foo { + default { + type Bar = usize; + + fn alpha() -> Self::Bar { + 0 // OK! In the same group, so we may assume `Self::Bar == usize`. + } + + // OK; we can rely on `Self::Bar == usize`. + default const BETA: Self::Bar = 3; + + default fn gamma() -> [Self::Bar; 4] { + // OK; we can depend on the underlying type of `Self::Bar`. + [9usize, 8, 7, 6] + } + + /// This is rejected: + default fn delta() -> [Self::Bar; Self::BETA] { + // ERROR! we may not rely on not on `Self::BETA`'s value because + // `Self::BETA` is a sibling of `Self::gamma` which is not in the + // same group and is not an ancestor either. + [9usize, 8, 7] + } + + // But this is accepted: + default fn delta() -> [Self::Bar; 3] { + // OK; we can depend on `Self::Bar == usize`. + [9, 8, 7] + } + + default { + // OK; we can still depend on `Self::Bar == usize`. + const EPSILON: Self::Bar = 2; + + fn zeta() -> [Self::Bar; Self::Epsilon] { + // OK; We can assume the value of `Self::EPSILON` because it + // is a sibling in the same group. We may also assume that + // `Self::Bar == usize` because it is an ancestor. + [42usize, 24] + } + } + } +} + +struct Eta; +struct Theta; +struct Iota; + +impl Foo for Eta { + // We can override `gamma` without overriding anything else because + // `gamma` is the sole member of its sub-group. Note in particular + // that we don't have to override `alpha`. + fn gamma() -> [Self::Bar; 4] { + [43, 42, 41, 40] + } +} + +impl Bar for Theta { + // Since `EPSILON` and `zeta` are in the same group; we must override + // them together. However, we still don't have to override anything + // in ancestral groups. + const EPSILON: Self::Bar = 0; + + fn zeta() -> [Self::Bar; Self::Epsilon] { + [] + } +} + +impl Bar for Iota { + // We have overridden `Bar` which is in the root group. + // Since all other items are decendants of the same group as `Bar` is in, + // they are allowed to depend on what `Bar` is. + type Bar = u8; + + ... // Definitions for all the other items elided for brevity. +} +``` + +[clique]: https://en.wikipedia.org/wiki/Clique_(graph_theory) + +In graph theory, a set of a vertices, in a graph, for which each distinct pair +of vertices is connected by a unique edge is said to form a [clique]. +What the snippet above encodes is a tree of such cliques. In other words, +we can visualize the snippet as: + +``` + ┏━━━━━━━━━━━━━━━━━┓ + ┃ + type Bar ┃ + ┏━━━━━━━━━━━━━┃ + fn alpha ┃━━━━━━━━━━━━━━┓ + ┃ ┗━━━━━━━━━━━━━━━━━┛ ┃ + ┃ ┃ ┃ ┃ + ┃ ┃ ┃ ┃ + ▼ ▼ ▼ ▼ +┏━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━┓ +┃ + const Beta ┃ ┃ + fn gamma ┃ ┃ + fn delta ┃ ┃ + const EPSILON ┃ +┗━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┃ + fn zeta ┃ + ┗━━━━━━━━━━━━━━━━━┛ +``` + +Please pay extra attention to the fact that items in the same group may +depend on each other's definitions as well as definitions of items that +are ancestors (up the tree). The inverse implication holds for what you +must override: if you override one item in a group, you must override +all items in that groups and all items in sub-groups (recursively). +As before, these limitations exist to preserve the soundness of the type system. + +Nested groups are intended primarily expected to be used when there is one +associated type, for which you want to define a default, coupled with a bunch +of functions which need to rely on the definition of the associated type. +This is a good mechanism for API evolution in the sense that you can introduce +a new associated type, rely on it in provided methods, but still perform +no breaking change. + +## Reference-level explanation + +### Grammar +[grammar]: #grammar + +Productions in this section which are not defined here are taken from +[parser-lalr.y](https://github.com/rust-lang/rust/blob/master/src/grammar/parser-lalr.y). + +Given: + +``` +trait_item : maybe_outer_attrs trait_item_leaf ; + +trait_item_leaf +: trait_const +| trait_type +| trait_method +| item_macro +; + +trait_const +: CONST ident maybe_ty_ascription maybe_const_default ';' +; + +trait_type : TYPE ty_param ';' ; + +trait_method : method_prefix method_common ';' | method_prefix method_provided ; +method_prefix : maybe_unsafe | CONST maybe_unsafe | maybe_unsafe EXTERN maybe_abi ; +method_provided : method_common inner_attrs_and_block ; +method_common +: FN ident generic_params fn_decl_with_self_allow_anon_params maybe_where_clause +; +``` - baz : x -> bar +The production `trait_item` is changed into: -implementation Foo Int where - baz x = x == 0 ``` +trait_item : maybe_outer_attrs trait_item_def ; -However, if we provide a default for `baz` in the `interface` which assumes -the default value `Bool` of `bar`, as with the following example: +trait_item_def +: trait_default_group +| trait_default_singleton +| trait_const +| trait_type +| trait_method +| item_macro +; -```idris -interface Foo x where - bar : Type - bar = Bool +trait_default_singleton : DEFAULT trait_item ; +trait_default_group : DEFAULT '{' trait_item* '}' ; - baz : x -> bar - baz _ = True +trait_type : TYPE ty_param ('=' ty_sum)? ';' ; ``` -then we run into an error: +Given: ``` -Type checking .\foo.idr -foo.idr:6:13-16: - | -6 | baz _ = True - | ~~~~ -When checking right hand side of Main.default#baz with expected type - bar x _ +impl_item : attrs_and_vis impl_item_leaf ; +impl_item_leaf +: item_macro +| maybe_default impl_method +| maybe_default impl_const +| maybe_default impl_type +; -Type mismatch between - Bool (Type of True) -and - bar x _ (Expected type) +impl_const : item_const ; +impl_type : TYPE ident generic_params '=' ty_sum ';' ; +impl_method : method_prefix method_common ; + +method_common +: FN ident generic_params fn_decl_with_self maybe_where_clause inner_attrs_and_block +; ``` -The behaviour here is exactly as in Haskell and as proposed in this RFC. +The production `impl_item` is changed into: -## C++ +``` +impl_item : attrs_and_vis impl_item_def ; +impl_item_def +: impl_default_singleton +| impl_default_group +| item_macro +| impl_method +| impl_const +| impl_type +; -In C++, it is possible to provide associated types and specialize them as well. -This is shown in the following example: +impl_default_singleton : DEFAULT impl_item ; +impl_default_group : DEFAULT '{' impl_item* '}' ; +``` -```cpp -#include -#include +Note that associated type defaults are already in the grammar due to [RFC 192] +but we have specified them in the grammar here nonetheless. -template struct wrap {}; +Note also that `default default fn ..` as well as `default default { .. }` are +intentionally recognized by the grammar to make life easier for macro authors +even though writing `default default ..` should never be written directly. -template struct foo { // Unspecialized. - using bar = int; +### Desugaring - bar make_a_bar() { return 0; }; -}; +After macro expansion, wherever the production `trait_default_singleton` occurs, +it is treated in all respects as, except for error reporting -- which is left up +to implementations of Rust, and is desugared to `DEFAULT '{' trait_item '}'`. +The same applies to `impl_default_singleton`. +In other words: `default fn f() {}` is desugared to `default { fn f() {} }`. -template struct foo> { // Partial specialization. - using bar = std::string; +### Semantics and type checking - bar make_a_bar() { return std::string("hello world"); }; -}; +#### Semantic restrictions on the syntax -int main() { - foo a_foo; - std::cout << a_foo.make_a_bar() << std::endl; +According to the [grammar], the parser will accept items inside `default { .. }` +without a body. However, such an item will later be rejected during type checking. +The parser will also accept visibility modifiers on `default { .. }` +(e.g. `pub default { .. }`). However, such a visibility modifier will also be +rejected by the type checker. - foo> b_foo; - std::cout << b_foo.make_a_bar() << std::endl; -} -``` +#### Specialization groups -You will note that C++ allows us to assume in both the base template class, -as well as the specialization, that `bar` is equal to the underlying type. -This is because one cannot specialize any part of a class without specializing -the whole of it. It's equivalent to one atomic `default { .. }` block. +Implementations of a `trait` as well as `trait`s themselves may now +contain *"specialization default groups"* (henceforth: *"group(s)"*) +as defined by the [grammar]. -## Swift +A group forms a [clique] and is considered an *atomic unit of specialization* +wherein each item can be specialized / overridden. -[swift_assoc]: https://docs.swift.org/swift-book/LanguageGuide/Generics.html +Groups may contain other groups - such groups are referred to as +*"nested groups"* and may be nested arbitrarily deeply. +Items which are not in any group are referred to as *`0`-deep*. +An item directly defined in a group which occurs at the top level of a +`trait` or an `impl` definition is referred to as being *`1`-deep*. +An item in a group which is contained in a *`1`-deep* group is *`2`-deep*. +If an item is nested in `k` groups it is *`k`-deep*. -One language which does have [associated types][swift_assoc] and defaults but -which does not have provided definitions for methods is Swift. -As an example, we may write: +A group and its sub-groups form a *tree of cliques*. +Given a group `$g` with items `$x_1, .. $x_n`, an item `$x_j` in `$g` +can assume the definitions of `$x_i, ∀ i ∈ { 1..n }` as well as any +definitions of items in `$f` where `$f` is an ancestor of `$g` (up the tree). +Conversely, items in `$g` may not assume the definitions of items in +descendant groups `$h_i` of `$g` as well as items which are grouped at all +or which are in groups which are not ancestors of `$g`. -```swift -protocol Foo { - associatedtype Bar = Int +If an `impl` block overrides one item `$x_j` in `$g`, +it also has to override all `$x_i` in `$g` where `i ≠ j` as well as +all items in groups `$h_i` which are descendants of `$g` (down the tree). +Otherwise, items do not need to be overridden. - func append() -> Bar -} +For example, you may write: -struct Quux: Foo { - func baz() -> Bar { - return 1 +```rust +trait Foo { + default { + type Bar = u8; + fn baz() { + let _: Self::Bar = 1u8; + } + + default { + const SIZE: usize = 3; + fn quux() { + let_: [Self::Bar; Self::SIZE] = [1u8, 2u8, 3u8]; + } + } } } -``` - -However, we may not write: - -```swift -protocol Foo { - associatedtype Bar = Int - func append() -> Bar { return 0 } +impl Foo for () { + type Bar = Vec; + fn baz() {} + const SIZE: usize = 5; + fn quux() {} } ``` -This would result in: +### Linting redundant `default`s -``` -main.swift:4:23: error: protocol methods may not have bodies - func baz() -> Bar { return 0 } -``` +When in source code (but not as a consequence of macro expansion), +any of the following occurs, a warn-by-default lint (`redundant_default`) +will be emitted: -## Scala +```rust + default default $item +// ^^^^^^^ warning: Redundant `default` +// hint: remove `default`. -Another language which allows for these kinds of type projections and defaults -for them is Scala. While Scala does not have type classes like Rust and Haskell -does, it does have a concept of `trait` which can be likened to a sort of -incoherent "type class" system. For example, we may write: + default default { +// ^^^^^^^ warning: Redundant `default` +// hint: remove `default`. + ... + } -```scala -trait Foo { - type Bar = Int + default { + ... - def baz(x: Bar): Int = x -} + default $item +// ^^^^^^^ warning: Redundant `default` +// hint: remove `default`. -class Quux extends Foo { - override type Bar = Int - override def baz(x: Bar): Int = x -} + ... + } ``` -There are a few interesting things to note here: +## Drawbacks -1. We are allowed to specify a default type `Int` for `Bar`. +The main drawbacks of this proposal are that: -2. A default definition for `baz` may be provided. +1. `default { .. }` is introduced, adding to the complexity of the language. -3. This default definition may assume the default given for `Bar`. + However, it should be noted that token `default` is already accepted for + use by specialization and for `default impl`. + Therefore, the syntax is only partially new. -4. However, we *must* explicitly state that we are overriding `baz`. +## Rationale and alternatives -5. If we change the definition of of `override type Bar` to `Double`, - the Scala compiler will reject it. +### Alternatives -# Unresolved questions -[unresolved-questions]: #unresolved-questions +One may consider mechanisms such as `default(Bar, BAZ) { .. }` to give +more freedom as to which dependency graphs may be encoded. +However, in practice, we believe that the *tree of cliques* approach proposed +in this RFC should be more than enough for practical applications. -1. Should trait objects default to the one specified in the trait if - an associated type is omitted? In other words, given: +### `default { .. }` is syntactically light-weight - ```rust - trait Foo { - type Bar = usize; - fn baz(&self) -> Self::Bar; - } +When you actually do need to assume the underlying default of an associated type +in a provided method, `default { .. }` provides a syntax that is comparatively +not *that* heavy weight. - type Quux = Box; - ``` +In addition, when you want to say that multiple items are overridable, +`default { .. }` provides less repetition than specifying `default` on +each item would. Thus, we believe the syntax is ergonomic. + +Finally, `default { .. }` works well and allows the user a good deal of control +over what can and can't be assumed and what must be specialized together. +The grouping mechanism also composes well as seen in +[the section where it is discussed][default_groups]. - Should `Quux` be considered well-formed and equivalent to the following? +### Tree of cliques is familiar - ```rust - type Quux = Box>; - ``` +The *"can depend on"* rule is similar to the rule used to determine whether a +non-`pub` item in a module tree is accessible or not. +Familiarity is a good tool to limit complexity costs. - This question may be left as future work for another RFC or resolved - during this RFC as the RFC is forward-compatible with such a change. +### Non-special treatment for methods + +In this RFC we haven't given methods any special treatment. +We could do so by allowing methods to assume the underlying type +of an associated type and still be overridable without having to override +the type. However, this might lead to *semantic breakage* in the sense that +the details of an `fn` may be tied to the definition of an associated type. +When those details change, it may also be prudent to change the associated type. +Default groups give users a mechanism to enforce such decisions. -# Future work +## Future work -## `where` clauses on `default { .. }` groups +### `where` clauses on `default { .. }` groups From our [case study], we noticed that we had to depart from our `trait` definition into a separate `default impl..` to handle the conditionality From a1c207762c0d3d65f319a70fdaa21a2c8288d83d Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 18 Jan 2019 13:15:12 +0100 Subject: [PATCH 21/25] rfc, associated-default-groups: simplify explanation of groups. --- text/0000-assoc-default-groups.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index d888368b946..5b376fdbd6d 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -1031,8 +1031,6 @@ For any items outside the group, you may assume their signatures, but not the default definitions given. An example: ```rust -use std::marker::PhantomData; - trait Fruit { type Details; fn foo(); @@ -1040,8 +1038,8 @@ trait Fruit { fn baz(); } -struct Citrus { species: PhantomData } -struct Orange { variety: PhantomData } +struct Citrus { species: S } +struct Orange { variety: V } struct Blood; struct Common; From 1209054084fafc745ef4c77d7139fd50e860c03b Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 31 Jan 2019 22:41:33 +0100 Subject: [PATCH 22/25] rfc, associated-default-groups: add unresolved questions. --- text/0000-assoc-default-groups.md | 105 +++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 5b376fdbd6d..7fc13c5ad78 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -824,7 +824,110 @@ There are a few interesting things to note here: # Unresolved questions [unresolved-questions]: #unresolved-questions -There are none. +## 1. When do suitability of defaults need to be proven? + +Consider a trait `Foo` defined as: + +```rust +trait Foo { + type Bar: Clone = Vec; +} +``` + +Let's also assume the following implementation of `Clone`: + +```rust +impl Clone for Vec { ... } +``` + +To prove that `Vec: Clone`, we must prove that `T: Clone`. +However, `Foo` does not say that `T: Clone` so is its definition valid? +If the suitability of `Vec` is checked where `Foo` is defined (1), +then we don't know that `T: Clone` and so the definition must be rejected. +To make the compiler admit `Foo`, we would have to write: + +```rust +trait Foo { + type Bar: Clone = Vec; +} +``` + +Now it is provable that `T: Clone` so `Vec: Clone` which is what was required. + +If instead the suitability of defaults are checked in `impl`ementations (2), +then proving `Vec: Clone` would not be required in `Foo`'s definition and +so then `Foo` would type-check. As a result, it would be admissible to write: + +```rust +#[derive(Copy, Clone)] +struct A; + +struct B; + +impl Foo for B {} +``` + +since `Vec: Clone` holds. + +With condition (2), strictly more programs are accepted than with (1). +It may be that useful programs are rejected if we enforce (1) rather than (2). +However, it would also be the more conservative choice, allowing us to move +towards (2) when necessary. As it is currently unclear what solution is best, +this question is left unresolved. + +## 1. Where are cycles checked? + +[playground]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=e823eea5e7ecba5da78cff225e0adaf9 + +Consider a program *([playground])*: + +```rust +#![feature(associated_type_defaults)] + +trait A { + type B = Self::C; // B defaults to C, + type C = Self::B; // C defaults to B, and we have a cycle! +} + +impl A for () {} + +fn _foo() { + let _x: <() as A>::B; +} + +// Removing this function will make the example compile. +fn main() { + let _x: <() as A>::B; +} +``` + +Currently, this results in a crash. This will need to be fixed. +At the very latest, `impl A for () {}` should have been an error. + +```rust +trait A { + type B = Self::C; + type C = Self::B; +} + +impl A for () {} // This OK but shouldn't be. +``` + +If cycles are checked for in `impl A for ()`, then it would be valid to write: + +```rust +trait A { + type B = Self::C; + type C = Self::B; +} + +impl A for () { + type B = u8; // The cycle is broken! +} +``` + +Alternatively, cycles could be checked for in `A`'s definition. +This is similar to the previous question in (1). # Future possibilities [future-possibilities]: #future-possibilities From a1a0ded95bf25f31ff1ea84ececfec0b2e67ec29 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Sat, 16 Feb 2019 23:10:51 +0100 Subject: [PATCH 23/25] rfc, associated-default-groups: fix typo. --- text/0000-assoc-default-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index 7fc13c5ad78..ff99f069d43 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -417,7 +417,7 @@ instead. With respect to this RFC, the semantics of `type Assoc = impl Bar;` inside a trait definition, where `Assoc` is the name of the associated type, is understood as what it means in terms of `default impl ..` as discussed -in [RFC 1210]. What this entails means in concrete terms is that given: +in [RFC 1210]. What this means in concrete terms is that given: ```rust trait Foo { From 3ed70c49e9dbffc61d8b66cbe3bf1dbdbce80602 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 20 Feb 2019 13:33:43 +0100 Subject: [PATCH 24/25] rfc, associated-default-groups: fix broken link. --- text/0000-assoc-default-groups.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-assoc-default-groups.md b/text/0000-assoc-default-groups.md index ff99f069d43..ca6820178c9 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/0000-assoc-default-groups.md @@ -636,8 +636,8 @@ This is consistent with the approach this RFC proposes. To our knowledge, Haskell does not have any means such as `default { .. }` to change this behaviour. Presumably, this is the case because Haskell -preserves parametricity thus lacking specialization, -wherefore `default { .. }`, as suggested in the [future possibilities], +preserves parametricity thus lacking specialization, wherefore `default { .. }`, +as suggested in the [future possibilities][future-possibilities], might not carry its weight. ## Idris From 2015cf5bbaefb24ecf524a59dc53efe5edfe658e Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 25 Feb 2019 14:04:20 +0100 Subject: [PATCH 25/25] RFC 2532 is associated_type_defaults --- ...c-default-groups.md => 2532-associated-type-defaults.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename text/{0000-assoc-default-groups.md => 2532-associated-type-defaults.md} (99%) diff --git a/text/0000-assoc-default-groups.md b/text/2532-associated-type-defaults.md similarity index 99% rename from text/0000-assoc-default-groups.md rename to text/2532-associated-type-defaults.md index ca6820178c9..26b41294117 100644 --- a/text/0000-assoc-default-groups.md +++ b/text/2532-associated-type-defaults.md @@ -1,7 +1,7 @@ -- Feature Name: `associated_type_defaults2` +- Feature Name: `associated_type_defaults` - Start Date: 2018-08-27 -- RFC PR: _ -- Rust Issue: _ +- RFC PR: [rust-lang/rfcs#2532](https://github.com/rust-lang/rfcs/pull/2532) +- Rust Issue: [rust-lang/rust#29661](https://github.com/rust-lang/rust/issues/29661) # Summary [summary]: #summary