From 3b5abbfd05049b38dce501f862bcf37b71ad587e Mon Sep 17 00:00:00 2001 From: Sean Patrick Santos Date: Sun, 15 Feb 2015 16:03:20 -0700 Subject: [PATCH 1/4] Update RFC 195 for RFC 246. --- text/0195-associated-items.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/text/0195-associated-items.md b/text/0195-associated-items.md index 45f97efe0fb..1b48e859247 100644 --- a/text/0195-associated-items.md +++ b/text/0195-associated-items.md @@ -9,6 +9,7 @@ more convenient, scalable, and powerful. In particular, traits will consist of a set of methods, together with: * Associated functions (already present as "static" functions) +* Associated consts * Associated statics * Associated types * Associated lifetimes @@ -173,7 +174,7 @@ provide a distinct `impl` for every member of this family. Associated types, lifetimes, and functions can already be expressed in today's Rust, though it is unwieldy to do so (as argued above). -But associated _statics_ cannot be expressed using today's traits. +But associated _consts_ and _statics_ cannot be expressed using today's traits. For example, today's Rust includes a variety of numeric traits, including `Float`, which must currently expose constants as static functions: @@ -191,19 +192,19 @@ trait Float { ``` Because these functions cannot be used in static initializers, the modules for -float types _also_ export a separate set of constants as statics, not using +float types _also_ export a separate set of constants as consts, not using traits. -Associated constants would allow the statics to live directly on the traits: +Associated constants would allow the consts to live directly on the traits: ```rust trait Float { - static NAN: Self; - static INFINITY: Self; - static NEG_INFINITY: Self; - static NEG_ZERO: Self; - static PI: Self; - static TWO_PI: Self; + const NAN: Self; + const INFINITY: Self; + const NEG_INFINITY: Self; + const NEG_ZERO: Self; + const PI: Self; + const TWO_PI: Self; ... } ``` @@ -282,13 +283,14 @@ distinction" below. ## Trait bodies: defining associated items -Trait bodies are expanded to include three new kinds of items: statics, types, -and lifetimes: +Trait bodies are expanded to include four new kinds of items: consts, statics, +types, and lifetimes: ``` TRAIT = TRAIT_HEADER '{' TRAIT_ITEM* '}' TRAIT_ITEM = ... + | 'const' IDENT ':' TYPE [ '=' CONST_EXP ] ';' | 'static' IDENT ':' TYPE [ '=' CONST_EXP ] ';' | 'type' IDENT [ ':' BOUNDS ] [ WHERE_CLAUSE ] [ '=' TYPE ] ';' | 'lifetime' LIFETIME_IDENT ';' @@ -352,7 +354,7 @@ external to the trait. ### Defaults -Notice that associated statics and types both permit defaults, just as trait +Notice that associated consts, statics, and types permit defaults, just as trait methods and functions can provide defaults. Defaults are useful both as a code reuse mechanism, and as a way to expand the @@ -430,6 +432,7 @@ lifetime items are allowed: ``` IMPL_ITEM = ... + | 'const' IDENT ':' TYPE '=' CONST_EXP ';' | 'static' IDENT ':' TYPE '=' CONST_EXP ';' | 'type' IDENT' '=' 'TYPE' ';' | 'lifetime' LIFETIME_IDENT '=' LIFETIME_REFERENCE ';' @@ -767,7 +770,8 @@ as UFCS-style functions: trait Foo { type AssocType; lifetime 'assoc_lifetime; - static ASSOC_STATIC: uint; + const ASSOC_CONST: uint; + static ASSOC_STATIC: &'static [uint, ..1024]; fn assoc_fn() -> Self; // Note: 'assoc_lifetime and AssocType in scope: @@ -776,6 +780,7 @@ trait Foo { fn default_method(&self) -> uint { // method in scope UFCS-style, assoc_fn in scope let _ = method(self, assoc_fn()); + ASSOC_CONST // in scope ASSOC_STATIC // in scope } } From 4ba33ee94a14cb40411c283f9ad4bff7bf8ec0e1 Mon Sep 17 00:00:00 2001 From: Sean Patrick Santos Date: Tue, 17 Feb 2015 19:57:13 -0700 Subject: [PATCH 2/4] Add historical note to RFC 195. --- text/0195-associated-items.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/0195-associated-items.md b/text/0195-associated-items.md index 1b48e859247..718b63b9fea 100644 --- a/text/0195-associated-items.md +++ b/text/0195-associated-items.md @@ -21,6 +21,11 @@ This RFC also provides a mechanism for *multidispatch* traits, where the `impl` is selected based on multiple types. The connection to associated items will become clear in the detailed text below. +*Note: This RFC was originally accepted before RFC 246 added consts and changed +the definition of statics. The text has been updated to clarify that both consts +and statics can be associated with a trait. Other than that modification, the +proposal has not been changed to reflect newer Rust features or syntax.* + # Motivation A typical example where associated items are helpful is data structures like From 1ad497df2877d46a85fa256c63797e3d00c24491 Mon Sep 17 00:00:00 2001 From: Sean Patrick Santos Date: Sat, 21 Mar 2015 23:09:03 -0600 Subject: [PATCH 3/4] Remove associated statics, and clarify limitations on associated constants pending further design work. --- text/0195-associated-items.md | 192 +++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 15 deletions(-) diff --git a/text/0195-associated-items.md b/text/0195-associated-items.md index 718b63b9fea..0a76c63d5fc 100644 --- a/text/0195-associated-items.md +++ b/text/0195-associated-items.md @@ -10,7 +10,6 @@ set of methods, together with: * Associated functions (already present as "static" functions) * Associated consts -* Associated statics * Associated types * Associated lifetimes @@ -21,10 +20,12 @@ This RFC also provides a mechanism for *multidispatch* traits, where the `impl` is selected based on multiple types. The connection to associated items will become clear in the detailed text below. -*Note: This RFC was originally accepted before RFC 246 added consts and changed -the definition of statics. The text has been updated to clarify that both consts -and statics can be associated with a trait. Other than that modification, the -proposal has not been changed to reflect newer Rust features or syntax.* +*Note: This RFC was originally accepted before RFC 246 introduced the +distinction between const and static items. The text has been updated to clarify +that associated consts will be added rather than statics, and to provide a +summary of restrictions on the initial implementation of associated +consts. Other than that modification, the proposal has not been changed to +reflect newer Rust features or syntax.* # Motivation @@ -179,7 +180,7 @@ provide a distinct `impl` for every member of this family. Associated types, lifetimes, and functions can already be expressed in today's Rust, though it is unwieldy to do so (as argued above). -But associated _consts_ and _statics_ cannot be expressed using today's traits. +But associated _consts_ cannot be expressed using today's traits. For example, today's Rust includes a variety of numeric traits, including `Float`, which must currently expose constants as static functions: @@ -196,7 +197,7 @@ trait Float { } ``` -Because these functions cannot be used in static initializers, the modules for +Because these functions cannot be used in constant expressions, the modules for float types _also_ export a separate set of constants as consts, not using traits. @@ -288,15 +289,14 @@ distinction" below. ## Trait bodies: defining associated items -Trait bodies are expanded to include four new kinds of items: consts, statics, -types, and lifetimes: +Trait bodies are expanded to include three new kinds of items: consts, types, +and lifetimes: ``` TRAIT = TRAIT_HEADER '{' TRAIT_ITEM* '}' TRAIT_ITEM = ... | 'const' IDENT ':' TYPE [ '=' CONST_EXP ] ';' - | 'static' IDENT ':' TYPE [ '=' CONST_EXP ] ';' | 'type' IDENT [ ':' BOUNDS ] [ WHERE_CLAUSE ] [ '=' TYPE ] ';' | 'lifetime' LIFETIME_IDENT ';' ``` @@ -359,7 +359,7 @@ external to the trait. ### Defaults -Notice that associated consts, statics, and types permit defaults, just as trait +Notice that associated consts and types both permit defaults, just as trait methods and functions can provide defaults. Defaults are useful both as a code reuse mechanism, and as a way to expand the @@ -431,14 +431,13 @@ We deal with this in a very simple way: ## Trait implementations -Trait `impl` syntax is much the same as before, except that static, type, and +Trait `impl` syntax is much the same as before, except that const, type, and lifetime items are allowed: ``` IMPL_ITEM = ... | 'const' IDENT ':' TYPE '=' CONST_EXP ';' - | 'static' IDENT ':' TYPE '=' CONST_EXP ';' | 'type' IDENT' '=' 'TYPE' ';' | 'lifetime' LIFETIME_IDENT '=' LIFETIME_REFERENCE ';' ``` @@ -776,7 +775,6 @@ trait Foo { type AssocType; lifetime 'assoc_lifetime; const ASSOC_CONST: uint; - static ASSOC_STATIC: &'static [uint, ..1024]; fn assoc_fn() -> Self; // Note: 'assoc_lifetime and AssocType in scope: @@ -786,7 +784,6 @@ trait Foo { // method in scope UFCS-style, assoc_fn in scope let _ = method(self, assoc_fn()); ASSOC_CONST // in scope - ASSOC_STATIC // in scope } } @@ -1200,6 +1197,73 @@ trait Mappable While the above demonstrates the versatility of associated types and `where` clauses, it is probably too much of a hack to be viable for use in `libstd`. +### Associated consts in generic code + +There are some restrictions on uses of associated consts in generic code. These +might be loosened or removed in the future (see the related sub-sections in +"Unresolved questions" below). + + 1. Values of constant expressions in match patterns cannot depend on a type + parameter (by extension, neither can the types of such expressions). This + restriction is necessary for exhaustiveness and reachability to be checked + in generic code. + + Note that the dependence of a value on a type parameter may be indirect: + + ```rust + enum MyEnum { + Var1, + Var2, + } + trait HasVar { + const VAR: MyEnum; + } + fn do_something(x: MyEnum) { + const y: MyEnum = ::VAR; + // The following is forbidden because the value `y` depends on `T`. + match x { + y => { /* ... */ } + _ => { /* ... */ } + } + // However, this is OK because the guard is not a part of the pattern. + match x { + z if z == y => { /* ... */ } + _ => { /* ... */ } + } + } + ``` + + 2. Array sizes that depend on type parameters cannot be compared for equality + by type-checking, with one exception: if the expression for an array size + comprises only a single reference to a constant item (or associated item), + it will be considered equal to any other array size that refers to the same + item, even if that item itself depends on the type parameters. + + For clarification, here are some examples. Assume that `T` is a type + parameter in the outer scope, and that it is known to have an associated + const `::N` of type `usize`. + + ```rust + // This is OK (but there are limitations to how x can be used). + let x: [u8; ::N] = [0u8; ::N]; + // Equivalent to the above. + let x = [0u8; ::N]; + // Neither of the following are allowed (type checking shouldn't have to + // know anything about arithmetic). + let x: [u8; 2 * ::N] = [0u8; ::N + ::N]; + let x: [u8; ::N + 1] = [0u8; 1 + ::N]; + // Still not allowed. + let x: [u8; ::N + 1] = [0u8; ::N + 1]; + // Workaround for the expression above. + const N_PLUS_1: usize = ::N + 1; + let x: [u8; N_PLUS_1] = [0u8; N_PLUS_1]; + // Neither of the following are allowed. + const ALIAS_N_PLUS_1: usize = N_PLUS_1; + let x: [u8; N_PLUS_1] = [0u8; ALIAS_N_PLUS_1]; + const ALIAS_N: usize = ::N; + let x: [u8; ::N] = [0u8; ALIAS_N]; + ``` + # Staging Associated lifetimes are probably not necessary for the 1.0 timeframe. While we @@ -1403,3 +1467,101 @@ This seems like a potentially useful feature, and should be unproblematic for bounds, but may have implications for vtables that make it problematic for trait objects. Whether or not such trait combinations are allowed will likely depend on implementation concerns, which are not yet clear. + +## Generic associated consts in match patterns + +It seems desirable to allow constants that depend on type parameters in match +patterns, but it's not clear how to do so. + +Looking at the `HasVar` example above, one possibility would be to simply treat +the first, forbidden match expression as syntactic sugar for the second, allowed +match expression that uses a pattern guard. This is simple to implement because +one can simply ignore the constant when performing exhaustiveness and +reachability checks. Unfortunately, this approach blurs the difference between +match patterns (which provide strict checks) and pattern guards (which are just +useful syntactic sugar), and it does not increase the expressiveness of the +language. + +An alternative would be to allow `where` clauses to place constraints on +associated consts. If an associated const is known to be equal/unequal to some +other value (or in the case of integers, inside/outside a given range), this can +inform exhaustiveness and reachability checks. But this requires more design and +implementation work, and more syntax. + +For now, we simply defer the question. + +## Generic associated consts in array sizes + +The above solution for type-checking array sizes is somewhat unsatisfactory. In +particular, it is counter-intuitive that neither of the following will type +check: + +```rust +// Shouldn't this be OK? +const ALIAS_N: usize = ::N; +let x: [u8; ::N] = [0u8; ALIAS_N]; +// This is likely to yield an embarrassing error message such as: +// "couldn't prove that `::N + 1` is equal to `::N + 1`" +let x: [u8; ::N + 1] = [0u8; ::N + 1]; +``` + +A function like this is especially affected: + +```rust +trait HasN { + const N: usize; +} +fn foo() -> [u8; ::N + 1] { + // Can't be verified to be correct for the return type, and can't use the + // intermediate const workaround due to scoping issues. + [0u8; ::N + 1] +} +``` + +This can be worked around with type-level naturals that use associated consts to +produce array sizes, but this is syntactically a bit inelegant. + +```rust +// Assume that `TypeAdd` and `One` are from a type-level naturals or similar +// library, and that `NAsTypeNatN` provides some way of translating the `N` +// on a `HasN` to a type compatible with that library. +trait HasN { + const N: usize; + type TypeNatN; +} +fn foo() -> [u8; TypeAdd<::TypeNatN, One>::AsUsize] { + // Because the type `TypeAdd<::TypeNatN, One>` can be verified to be + // equal to itself in type checking, we know that the associated const + // `AsUsize` below must be the same item as the `AsUsize` mentioned in the + // return type above. + [0u8; TypeAdd<::NAsTypeNat, One>::AsUsize] +} +``` + +There are a variety of possible ways to address the above issues, including: + + - Implementing smarter handling of consts that are just aliases of other + constant items. + - Allowing `where` clauses to constrain some associated constants to be equal, + to other expressions, and using this information in type checking. + - Adding normalization with little or no awareness of arithmetic (e.g. allowing + expressions that are exactly the same to be considered equal, or using only + a very basic understanding of which operations are commutative and/or + associative). + - Adding new syntax and/or new capability to plugins to allow type-level + naturals to be used with more ergonomic and clear syntax. + - Implementing a dependent type system that provides built-in semantics for + integer arithmetic at the type level, rather than implementing this in an + external or standard library. + - Using a full-fledged SMT solver. + - Some other creative solutions not on this list. + +While there are many ways to improve on the current design, and many of these +approaches are not mutually exclusive, much more work is needed to investigate +and implement a self-consistent, effective, and ideally intuitive set of +solutions. + +Though admittedly not very satisfying at the moment, the current approach has +the advantage of being (arguably) a good minimalist design, allowing associated +consts to be used for array sizes in generic code now, but also allowing for any +of a number of improved systems to be implemented later. From 992550b1ac1869c3ef0591ac64c55ca961656f2c Mon Sep 17 00:00:00 2001 From: Sean Patrick Santos Date: Wed, 27 May 2015 18:38:16 -0600 Subject: [PATCH 4/4] Shrink the scope of the associated const changes to RFC 195, and mention trait objects. --- text/0195-associated-items.md | 167 +++++----------------------------- 1 file changed, 22 insertions(+), 145 deletions(-) diff --git a/text/0195-associated-items.md b/text/0195-associated-items.md index 0a76c63d5fc..c17018b580e 100644 --- a/text/0195-associated-items.md +++ b/text/0195-associated-items.md @@ -882,6 +882,7 @@ trait Foo { type Output1; type Output2; lifetime 'a; + const C: bool; ... } ``` @@ -894,6 +895,7 @@ T: Foo T: Foo T: Foo T: Foo>(t: T) // this is valid fn consume_obj(t: Box>) // this is NOT valid // but this IS valid: -fn consume_obj(t: Box::N] = [0u8; ::N + ::N]; - let x: [u8; ::N + 1] = [0u8; 1 + ::N]; - // Still not allowed. - let x: [u8; ::N + 1] = [0u8; ::N + 1]; - // Workaround for the expression above. - const N_PLUS_1: usize = ::N + 1; - let x: [u8; N_PLUS_1] = [0u8; N_PLUS_1]; - // Neither of the following are allowed. - const ALIAS_N_PLUS_1: usize = N_PLUS_1; - let x: [u8; N_PLUS_1] = [0u8; ALIAS_N_PLUS_1]; - const ALIAS_N: usize = ::N; - let x: [u8; ::N] = [0u8; ALIAS_N]; - ``` +If the value of an associated const depends on a type parameter (including +`Self`), it cannot be used in a constant expression. This restriction will +almost certainly be lifted in the future, but this raises questions outside the +scope of this RFC. # Staging @@ -1471,97 +1414,31 @@ on implementation concerns, which are not yet clear. ## Generic associated consts in match patterns It seems desirable to allow constants that depend on type parameters in match -patterns, but it's not clear how to do so. - -Looking at the `HasVar` example above, one possibility would be to simply treat -the first, forbidden match expression as syntactic sugar for the second, allowed -match expression that uses a pattern guard. This is simple to implement because -one can simply ignore the constant when performing exhaustiveness and -reachability checks. Unfortunately, this approach blurs the difference between -match patterns (which provide strict checks) and pattern guards (which are just -useful syntactic sugar), and it does not increase the expressiveness of the -language. - -An alternative would be to allow `where` clauses to place constraints on -associated consts. If an associated const is known to be equal/unequal to some -other value (or in the case of integers, inside/outside a given range), this can -inform exhaustiveness and reachability checks. But this requires more design and -implementation work, and more syntax. +patterns, but it's not clear how to do so while still checking exhaustiveness +and reachability of the match arms. Most likely this requires new forms of +where clause, to constrain associated constant values. For now, we simply defer the question. ## Generic associated consts in array sizes -The above solution for type-checking array sizes is somewhat unsatisfactory. In -particular, it is counter-intuitive that neither of the following will type -check: +It would be useful to be able to use trait-associated constants in generic code. ```rust // Shouldn't this be OK? const ALIAS_N: usize = ::N; let x: [u8; ::N] = [0u8; ALIAS_N]; -// This is likely to yield an embarrassing error message such as: -// "couldn't prove that `::N + 1` is equal to `::N + 1`" -let x: [u8; ::N + 1] = [0u8; ::N + 1]; -``` - -A function like this is especially affected: - -```rust -trait HasN { - const N: usize; -} -fn foo() -> [u8; ::N + 1] { - // Can't be verified to be correct for the return type, and can't use the - // intermediate const workaround due to scoping issues. - [0u8; ::N + 1] -} +// Or... +let x: [u8; T::N + 1] = [0u8; T::N + 1]; ``` -This can be worked around with type-level naturals that use associated consts to -produce array sizes, but this is syntactically a bit inelegant. +However, this causes some problems. What should we do with the following case in +type checking, where we need to prove that a generic is valid for any `T`? ```rust -// Assume that `TypeAdd` and `One` are from a type-level naturals or similar -// library, and that `NAsTypeNatN` provides some way of translating the `N` -// on a `HasN` to a type compatible with that library. -trait HasN { - const N: usize; - type TypeNatN; -} -fn foo() -> [u8; TypeAdd<::TypeNatN, One>::AsUsize] { - // Because the type `TypeAdd<::TypeNatN, One>` can be verified to be - // equal to itself in type checking, we know that the associated const - // `AsUsize` below must be the same item as the `AsUsize` mentioned in the - // return type above. - [0u8; TypeAdd<::NAsTypeNat, One>::AsUsize] -} +let x: [u8; T::N + T::N] = [0u8; 2 * T::N]; ``` -There are a variety of possible ways to address the above issues, including: - - - Implementing smarter handling of consts that are just aliases of other - constant items. - - Allowing `where` clauses to constrain some associated constants to be equal, - to other expressions, and using this information in type checking. - - Adding normalization with little or no awareness of arithmetic (e.g. allowing - expressions that are exactly the same to be considered equal, or using only - a very basic understanding of which operations are commutative and/or - associative). - - Adding new syntax and/or new capability to plugins to allow type-level - naturals to be used with more ergonomic and clear syntax. - - Implementing a dependent type system that provides built-in semantics for - integer arithmetic at the type level, rather than implementing this in an - external or standard library. - - Using a full-fledged SMT solver. - - Some other creative solutions not on this list. - -While there are many ways to improve on the current design, and many of these -approaches are not mutually exclusive, much more work is needed to investigate -and implement a self-consistent, effective, and ideally intuitive set of -solutions. - -Though admittedly not very satisfying at the moment, the current approach has -the advantage of being (arguably) a good minimalist design, allowing associated -consts to be used for array sizes in generic code now, but also allowing for any -of a number of improved systems to be implemented later. +We would like to handle at least some obvious cases (e.g. proving that +`T::N == T::N`), but without trying to prove arbitrary statements about +arithmetic. The question of how to do this is deferred.