From 81e0e76fdc3bd3f5485b5cc0222e2b675e51e977 Mon Sep 17 00:00:00 2001 From: Eduard Bopp Date: Tue, 29 Apr 2014 18:00:09 +0200 Subject: [PATCH 1/7] Draft an RFC for static generic parameters --- active/0000-static-generic-parameters.md | 135 +++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 active/0000-static-generic-parameters.md diff --git a/active/0000-static-generic-parameters.md b/active/0000-static-generic-parameters.md new file mode 100644 index 00000000000..f333ca65b89 --- /dev/null +++ b/active/0000-static-generic-parameters.md @@ -0,0 +1,135 @@ +- Start Date: 2014-04-29 +- RFC PR #: +- Rust Issue #: + +# Summary + +Allow generics to have static values as parameters in addition to lifetime and +type parameters. + +# Motivation + +Generic types are very useful to ensure type-safety while writing generic code +for a variety of types. Parametrisation over static values extends this feature +to certain use cases, where it is necessary to maintain type-safety in terms of +these values. + +To illustrate this further, consider the following two use cases as examples: + +* *Algebraic types*: algebraic vectors and matrices generally have a certain + dimensionality, which changes their behaviour. For example, it does not make + sense to add a 2-vector with a 3-vector or multiply a 3x4 matrix by a + 5-vector. But the algorithms can be written generically in terms of the + dimensionality of these types. +* *Physical units*: In science one often deals with numerical quantities + equipped with units (meters, hours, kilometers per hour, etc.). To avoid + errors dealing with these units, it makes sense to include in the data type. + For performance reasons, however, having the computational overhead in every + single calculation at run-time might be prohibitively slow. Here static + values as generic parameters could allow to convert between units and check + for consistency at compile-time. + +# Drawbacks + +First of all, allowing another kind of parameter for generics adds a certain +degree of complexity to Rust. Thus the potential merits of this feature have to +justify this. + +Furthermore, it is not entirely clear, how well this feature fits with Rust's +current approach to meta-programming. When implemented completely, this feature +requires compile-time function execution (CTFE), which has been [discussed in +the past](https://mail.mozilla.org/pipermail/rust-dev/2014-January/008252.html) +without a clear outcome. This feature would also introduce the possibility for +[template metaprogramming](http://en.wikipedia.org/wiki/Template_metaprogramming). + +# Detailed design + +Currently generics can be parametrized over type and lifetime parameters. This +RFC proposes to additionally allow for static values as parameters. These +values must be static, since they encode type-information that must be known at +compile-time. + +To propose a concrete syntax, consider this simple generic function: + +```rust +fn add_n(x: int) -> int { + x + n +} + +fn main() { + add_n<3>(4); // => 7 +} + +``` + +The syntax `` closely resembles the syntax for type parameters with +trait bounds. Traits would probably not be allowed as the type of a static +parameter, since they could not be statically resolved at compile-time. +Therefore the parser should be able to distinguish type parameters from static +value parameters despite the similarity. However, one could also annotate the +parameter in some way to differentiate it more clearly from type parameters. + +Structs could be parametrized similarly, as this (incomplete) implementation of +an arbitrarily-sized algebraic vector illustrates: + +```rust +struct Vector { + pub data: [T, ..n] +} + +impl Vector { + fn new(data: [T, ..n]]) -> Vector { + Vector{data: data} + } +} + +impl Add, Vector> for Vector { + fn add(&self, rhs: &Vector) -> Vector { + let mut new_data: [T, ..n] = [0, ..n]; + for (i, (&x, &y)) in self.data.iter().zip(rhs.data.iter()).enumerate() { + new_data[i] = x + y; + } + Vector::new(new_data) + } +} + +fn main() { + assert_eq!( + Vector::new([1, 2]) + Vector([2, 3]), + Vector::new([3, 4]) + ); + assert_eq!( + Vector::new([1, 2, 3]) + Vector([2, 3, 7]), + Vector::new([3, 4, 5]) + ); +} + +``` + +It should also be possible to do some algebra with the parameters, like this: + +```rust +fn concatenate + (x: Vector, y: Vector) -> Vector +{ + let mut new_data: [T, ..n + m] = [0, ..n + m]; + for (i, &xx) in x.data.iter().enumerate() { new_data[i] = xx; } + for (i, &yy) in y.data.iter().enumerate() { new_data[i + n] = yy; } + Vector::new(new_data) +} + +``` + +# Alternatives + +Parts of the functionality provided by this change could be achieved using +macros, which is currently done in some libraries (see for example @sebcrozet's +libraries [nalgebra](https://github.com/sebcrozet/nalgebra) and +[nphysics](https://github.com/sebcrozet/nphysics)). However, macros are fairly +limited in this regard. + +# Unresolved questions + +* How does type inference work in this context? +* In how far is compile-time function execution acceptable to support this? +* How exactly does this work with traits and enums? From 5c23243f264dad2919af251470f8ee2b895d06c2 Mon Sep 17 00:00:00 2001 From: Eduard Bopp Date: Tue, 6 May 2014 16:08:58 +0200 Subject: [PATCH 2/7] Put link URLs at the end of the document --- active/0000-static-generic-parameters.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/active/0000-static-generic-parameters.md b/active/0000-static-generic-parameters.md index f333ca65b89..10bf8d19a55 100644 --- a/active/0000-static-generic-parameters.md +++ b/active/0000-static-generic-parameters.md @@ -37,10 +37,10 @@ justify this. Furthermore, it is not entirely clear, how well this feature fits with Rust's current approach to meta-programming. When implemented completely, this feature -requires compile-time function execution (CTFE), which has been [discussed in -the past](https://mail.mozilla.org/pipermail/rust-dev/2014-January/008252.html) -without a clear outcome. This feature would also introduce the possibility for -[template metaprogramming](http://en.wikipedia.org/wiki/Template_metaprogramming). +requires compile-time function execution (CTFE), which has been +[discussed][issue_11621] [in the past][ctfe_mail] without a clear outcome. This +feature would also introduce the possibility for +[template metaprogramming][template_meta]. # Detailed design @@ -123,13 +123,19 @@ fn concatenate # Alternatives Parts of the functionality provided by this change could be achieved using -macros, which is currently done in some libraries (see for example @sebcrozet's -libraries [nalgebra](https://github.com/sebcrozet/nalgebra) and -[nphysics](https://github.com/sebcrozet/nphysics)). However, macros are fairly -limited in this regard. +macros, which is currently done in some libraries (see for example in +[Servo][servo_macros] or @sebcrozet's libraries [nalgebra][nalgebra] and +[nphysics][nphysics]. However, macros are fairly limited in this regard. # Unresolved questions * How does type inference work in this context? * In how far is compile-time function execution acceptable to support this? * How exactly does this work with traits and enums? + + +[nalgebra]: https://github.com/sebcrozet/nalgebra +[nphysics]: https://github.com/sebcrozet/nphysics +[issue_11621]: https://github.com/mozilla/rust/issues/11621 +[ctfe_mail]: https://mail.mozilla.org/pipermail/rust-dev/2014-January/008252.html +[template_meta]: http://en.wikipedia.org/wiki/Template_metaprogramming From cfe510011d51069074ef59076cd4886109215283 Mon Sep 17 00:00:00 2001 From: Eduard Bopp Date: Tue, 6 May 2014 16:15:47 +0200 Subject: [PATCH 3/7] Further motivation and extend summary Mentions the relation to a dependent type system. --- active/0000-static-generic-parameters.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/active/0000-static-generic-parameters.md b/active/0000-static-generic-parameters.md index 10bf8d19a55..2fb745fa38e 100644 --- a/active/0000-static-generic-parameters.md +++ b/active/0000-static-generic-parameters.md @@ -4,8 +4,9 @@ # Summary -Allow generics to have static values as parameters in addition to lifetime and -type parameters. +Implement a simple form of a dependent type system similar to C++ and D by +allowing generics to have static values as parameters in addition to lifetime +and type parameters. # Motivation @@ -29,6 +30,11 @@ To illustrate this further, consider the following two use cases as examples: values as generic parameters could allow to convert between units and check for consistency at compile-time. +In general, this feature allows for nicer abstractions that are dealt with at +compile-time and thus have no cost in terms of run-time performance. It is also +a very expressive feature that Rust is currently lacking in comparison with +similar languages such as C++ and D. + # Drawbacks First of all, allowing another kind of parameter for generics adds a certain From 7d95567debebc5b66bc3b7e78e57e983c8c754b1 Mon Sep 17 00:00:00 2001 From: Eduard Bopp Date: Tue, 6 May 2014 16:16:43 +0200 Subject: [PATCH 4/7] Note small vector types as usage examples --- active/0000-static-generic-parameters.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/active/0000-static-generic-parameters.md b/active/0000-static-generic-parameters.md index 2fb745fa38e..b369f67f684 100644 --- a/active/0000-static-generic-parameters.md +++ b/active/0000-static-generic-parameters.md @@ -22,6 +22,13 @@ To illustrate this further, consider the following two use cases as examples: sense to add a 2-vector with a 3-vector or multiply a 3x4 matrix by a 5-vector. But the algorithms can be written generically in terms of the dimensionality of these types. +* *Compile-time sizes*: define sizes for small buffers and other data + structures at compile-time. This can be quite important to only allocate + larger chunks of memory, when they are really required. If this is unlikely, + it can have a meaningful impact on performance. For instance, + [Servo][servo_macros] + currently resorts to a range of types implemented with macros to deal with + this issue. * *Physical units*: In science one often deals with numerical quantities equipped with units (meters, hours, kilometers per hour, etc.). To avoid errors dealing with these units, it makes sense to include in the data type. @@ -139,7 +146,7 @@ macros, which is currently done in some libraries (see for example in * In how far is compile-time function execution acceptable to support this? * How exactly does this work with traits and enums? - +[servo_macros]: https://github.com/mozilla/servo/blob/b14b2eca372ea91dc40af66b1f8a9cd510c37abf/src/components/util/smallvec.rs#L475-L525 [nalgebra]: https://github.com/sebcrozet/nalgebra [nphysics]: https://github.com/sebcrozet/nphysics [issue_11621]: https://github.com/mozilla/rust/issues/11621 From 917cb2ad6452aadcbe5b8699ec2a6e8746017211 Mon Sep 17 00:00:00 2001 From: Eduard Bopp Date: Tue, 6 May 2014 16:17:10 +0200 Subject: [PATCH 5/7] Elaborate on type inference somewhat more --- active/0000-static-generic-parameters.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/active/0000-static-generic-parameters.md b/active/0000-static-generic-parameters.md index b369f67f684..4126d5628bc 100644 --- a/active/0000-static-generic-parameters.md +++ b/active/0000-static-generic-parameters.md @@ -133,6 +133,30 @@ fn concatenate ``` +Type inference can potentially become complex, especially when arbitrary +functions must be executed. First of all, compile-time function execution as +described in [this issue][issue_11621] is required to instantiate the types +parametrized with static parameters. + +It seems reasonable to restrict the use of algebraic expressions in types in +such a way, that it is still possible to immediately infer a type from an +expression without resorting to any kind of function inversion. + +So the following function signature would work + +```rust +fn inc(x: T) -> T {...} +``` + +while this one wouldn't + +```rust +fn inc(x: T) -> T {...} +``` + +Traits and enumerations should also be able to be parametrized like this in the +same manner. + # Alternatives Parts of the functionality provided by this change could be achieved using From 83b2e0360c5df1979c31fd6755e6bfc607d82943 Mon Sep 17 00:00:00 2001 From: Eduard Bopp Date: Wed, 7 May 2014 13:47:01 +0200 Subject: [PATCH 6/7] Use `static` as keyword to distinguish parameters --- active/0000-static-generic-parameters.md | 34 +++++++++++++++--------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/active/0000-static-generic-parameters.md b/active/0000-static-generic-parameters.md index 4126d5628bc..c7809d11c78 100644 --- a/active/0000-static-generic-parameters.md +++ b/active/0000-static-generic-parameters.md @@ -65,7 +65,7 @@ compile-time. To propose a concrete syntax, consider this simple generic function: ```rust -fn add_n(x: int) -> int { +fn add_n(x: int) -> int { x + n } @@ -75,28 +75,36 @@ fn main() { ``` -The syntax `` closely resembles the syntax for type parameters with -trait bounds. Traits would probably not be allowed as the type of a static -parameter, since they could not be statically resolved at compile-time. -Therefore the parser should be able to distinguish type parameters from static -value parameters despite the similarity. However, one could also annotate the -parameter in some way to differentiate it more clearly from type parameters. +The syntax `` closely resembles the syntax for type parameters +with trait bounds. There is the additional keyword `static` to clearly +distinguish it from type parameters. This keyword is already used in a +different context. This use has a semantic relation to other uses of +`static` as a keyword. There is no ambiguity due to the context of generic +parameters in which it appears here either. Therefore it makes sense to use it +in this context. + +Alternatively, one could also omit `static`. Traits would probably not be +allowed as the type of a static parameter, since they could not be statically +resolved at compile-time. Therefore the parser should be able to distinguish +type parameters from static value parameters despite the similarity, even +without `static`. However, the difference between type parameters and static +parameters might become less obvious that way. Structs could be parametrized similarly, as this (incomplete) implementation of an arbitrarily-sized algebraic vector illustrates: ```rust -struct Vector { +struct Vector { pub data: [T, ..n] } -impl Vector { +impl Vector { fn new(data: [T, ..n]]) -> Vector { Vector{data: data} } } -impl Add, Vector> for Vector { +impl Add, Vector> for Vector { fn add(&self, rhs: &Vector) -> Vector { let mut new_data: [T, ..n] = [0, ..n]; for (i, (&x, &y)) in self.data.iter().zip(rhs.data.iter()).enumerate() { @@ -122,7 +130,7 @@ fn main() { It should also be possible to do some algebra with the parameters, like this: ```rust -fn concatenate +fn concatenate (x: Vector, y: Vector) -> Vector { let mut new_data: [T, ..n + m] = [0, ..n + m]; @@ -145,13 +153,13 @@ expression without resorting to any kind of function inversion. So the following function signature would work ```rust -fn inc(x: T) -> T {...} +fn inc(x: T) -> T {...} ``` while this one wouldn't ```rust -fn inc(x: T) -> T {...} +fn inc(x: T) -> T {...} ``` Traits and enumerations should also be able to be parametrized like this in the From 0ce8ff6dae34e7c3a22377457d5b191862180a20 Mon Sep 17 00:00:00 2001 From: Eduard Bopp Date: Fri, 9 May 2014 11:12:34 +0200 Subject: [PATCH 7/7] Include range and mod types in examples --- active/0000-static-generic-parameters.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/active/0000-static-generic-parameters.md b/active/0000-static-generic-parameters.md index c7809d11c78..48f749b01b5 100644 --- a/active/0000-static-generic-parameters.md +++ b/active/0000-static-generic-parameters.md @@ -15,7 +15,7 @@ for a variety of types. Parametrisation over static values extends this feature to certain use cases, where it is necessary to maintain type-safety in terms of these values. -To illustrate this further, consider the following two use cases as examples: +To illustrate this further, consider the following use cases for this feature: * *Algebraic types*: algebraic vectors and matrices generally have a certain dimensionality, which changes their behaviour. For example, it does not make @@ -36,6 +36,9 @@ To illustrate this further, consider the following two use cases as examples: single calculation at run-time might be prohibitively slow. Here static values as generic parameters could allow to convert between units and check for consistency at compile-time. +* *Range and mod types*: This would allow [range and mod types similar to + Ada][range_mod_ada] in Rust, which enforce certain range constraints and + implement modular arithmetics respectively. In general, this feature allows for nicer abstractions that are dealt with at compile-time and thus have no cost in terms of run-time performance. It is also @@ -184,3 +187,4 @@ macros, which is currently done in some libraries (see for example in [issue_11621]: https://github.com/mozilla/rust/issues/11621 [ctfe_mail]: https://mail.mozilla.org/pipermail/rust-dev/2014-January/008252.html [template_meta]: http://en.wikipedia.org/wiki/Template_metaprogramming +[range_mod_ada]: http://en.wikipedia.org/wiki/Ada_%28programming_language%29#Data_types