From 90babcddc16cd96ac3a9ccc43982e8cdf0d13a67 Mon Sep 17 00:00:00 2001 From: josh11b Date: Mon, 15 Aug 2022 15:00:00 -0700 Subject: [PATCH] Revise associated types and interface parameters in Generics terminology (#1972) Previous text was not clear, see https://discord.com/channels/655572317891461132/941071822756143115/1004607504396861540 . Co-authored-by: Thomas Heller Co-authored-by: Jon Ross-Perkins --- docs/design/generics/terminology.md | 99 ++++++++++++++--------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 2ce2691d5421..d1fcf1be7daf 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -637,59 +637,54 @@ conditions on the type argument. For example: `Array(T)` might implement ## Interface type parameters and associated types -Imagine an interface defining a container. Different containers will contain -different types of values, and the container API will have to refer to that -"element type" when defining the signature of methods like "insert" or "find". -If that element type is a parameter (input) to the interface type, we say it is -an _interface type parameter_; if it is an output, we say it is an _associated -type_. An associated type is a kind of [associated entity](#associated-entity). +_Interface type parameters_ and _associated types_ are both ways of allowing the +types in function signatures in an interface to vary. For example, different +[stacks]() will have +different element types. That element type would be used as the parameter type +of the `Push` function and the return type of the `Pop` function. As +[in Rust](https://rust-lang.github.io/rfcs/0195-associated-items.html#clearer-trait-matching), +we can distinguish these by whether they are input parameters or output +parameters: -Interface type parameter example: +- An interface type parameter is a parameter or input to the interface type. + That means they must be specified before an implementation of the interface + may be determined. +- In contrast, associated types are outputs. This means that they are + determined by the implementation, and need not be specified in a type + constraint. + +Functions using an interface as a constraint need not specify the value of its +associated types. An associated type is a kind of +[associated entity](#associated-entity). ``` -interface StackTP(ElementType:! Type) - fn Push[addr me: Self*](value: ElementType); - fn Pop[addr me: Self*]() -> ElementType; -} -``` - -Associated type example: - -``` -interface StackAT { +// Stack using associated types +interface Stack { let ElementType:! Type; fn Push[addr me: Self*](value: ElementType); fn Pop[addr me: Self*]() -> ElementType; } + +// Works on any type implementing `Stack`. Return type +// is determined by the type's implementation of `Stack`. +fn PeekAtTopOfStack[T: Stack](s: T*) -> T.ElementType { + let ret: T.ElementType = s->Pop(); + s->Push(ret); + return ret; +} + +class Fruit; +class FruitStack { + // Implement `Stack` for `FruitStack` + // with `ElementType` set to `Fruit`. + impl as Stack where .ElementType == Fruit { ... } +} ``` -Associated types are particularly called for when the implementation controls -the type, not the caller. For example, the iterator type for a container is -specific to the container and not something you would expect a user of the -interface to specify. - -``` -interface Iterator { ... } -interface Container { - // This does not make sense as an parameter to the container interface, - // since this type is determined from the container type. - let IteratorType:! Iterator; - ... - fn Insert[addr me: Self*](position: IteratorType, value: ElementType); -} -class ListIterator(ElementType:! Type) { - ... - impl as Iterator; -} -class List(ElementType:! Type) { - // Iterator type is determined by the container type. - impl as Container where .IteratorType = ListIterator(ElementType) { - fn Insert[addr me: Self*](position: IteratorType, value: ElementType) { - ... - } - } -} -``` +Associated types are particularly called for when the implementation of the +interface determines the type, not the caller. For example, the iterator type +for a container is specific to the container and not something you would expect +a user of the interface to specify. If you have an interface with type parameters, a type can have multiple impls for different combinations of type parameters. As a result, type parameters may @@ -701,7 +696,7 @@ For example, we might have an interface that says how to perform addition with another type: ``` -interface Addable(T:! Type) { +interface AddWith(T:! Type) { let ResultType:! Type; fn Add[me: Self](rhs: T) -> ResultType; } @@ -710,23 +705,23 @@ interface Addable(T:! Type) { An `i32` value might support addition with `i32`, `u16`, and `f64` values. ``` -impl i32 as Addable(i32) where .ResultType = i32 { ... } -impl i32 as Addable(u16) where .ResultType = i32 { ... } -impl i32 as Addable(f64) where .ResultType = f64 { ... } +impl i32 as AddWith(i32) where .ResultType = i32 { ... } +impl i32 as AddWith(u16) where .ResultType = i32 { ... } +impl i32 as AddWith(f64) where .ResultType = f64 { ... } ``` -To write a generic function requiring a parameter to be `Addable`, there needs +To write a generic function requiring a parameter to be `AddWith`, there needs to be some way to determine the type to add to: ``` // ✅ This is allowed, since the value of `T` is determined by the // `y` parameter. -fn DoAdd[T:! Type, U:! Addable(T)](x: U, y: T) -> U.ResultType { +fn DoAdd[T:! Type, U:! AddWith(T)](x: U, y: T) -> U.ResultType { return x.Add(y); } // ❌ This is forbidden, can't uniquely determine `T`. -fn CompileError[T:! Type, U:! Addable(T)](x: U) -> T; +fn CompileError[T:! Type, U:! AddWith(T)](x: U) -> T; ``` Once the interface parameter can be determined, that determines the values for @@ -734,7 +729,7 @@ associated types, such as `ResultType` in the example. As always, calls with types for which no implementation exists will be rejected at the call site: ``` -// ❌ This is forbidden, no implementation of `Addable(Orange)` +// ❌ This is forbidden, no implementation of `AddWith(Orange)` // for `Apple`. DoAdd(apple, orange); ```