-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC for static generic parameters #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
81e0e76
Draft an RFC for static generic parameters
milibopp 5c23243
Put link URLs at the end of the document
milibopp cfe5100
Further motivation and extend summary
milibopp 7d95567
Note small vector types as usage examples
milibopp 917cb2a
Elaborate on type inference somewhat more
milibopp 83b2e03
Use `static` as keyword to distinguish parameters
milibopp 0ce8ff6
Include range and mod types in examples
milibopp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
- Start Date: 2014-04-29 | ||
- RFC PR #: | ||
- Rust Issue #: | ||
|
||
# Summary | ||
|
||
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 | ||
|
||
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 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 | ||
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. | ||
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. | ||
* *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 | ||
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 | ||
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][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 | ||
|
||
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<static n: int>(x: int) -> int { | ||
x + n | ||
} | ||
|
||
fn main() { | ||
add_n<3>(4); // => 7 | ||
} | ||
|
||
``` | ||
|
||
The syntax `<static n: int>` 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<T, static n: uint> { | ||
pub data: [T, ..n] | ||
} | ||
|
||
impl<T, static n: uint> Vector<T, n> { | ||
fn new(data: [T, ..n]]) -> Vector<T, n> { | ||
Vector{data: data} | ||
} | ||
} | ||
|
||
impl<T: Add, static n: uint> Add<Vector<T, n>, Vector<T, n>> for Vector<T, n> { | ||
fn add(&self, rhs: &Vector<T, n>) -> Vector<T, n> { | ||
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<T, static n: uint, static m: uint> | ||
(x: Vector<T, n>, y: Vector<T, m>) -> Vector<T, n + m> | ||
{ | ||
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) | ||
} | ||
|
||
``` | ||
|
||
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<static n: int>(x: T<n>) -> T<n + 1> {...} | ||
``` | ||
|
||
while this one wouldn't | ||
|
||
```rust | ||
fn inc<static n: int>(x: T<n - 1>) -> T<n + 1> {...} | ||
``` | ||
|
||
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 | ||
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? | ||
|
||
[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 | ||
[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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could be wrong, but this would probably require a dependent type system. This is far too big a problem to be tackled by Rust on top of all the other challenges it is taking on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this whole RFC is proposing dependent types (with or without the algebra). These algebraic expressions are just like constant expressions, and aren't anything like full dependent types in terms of complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so there is a technical term for this type system feature. Thanks for pointing that out. I've read a little more on that topic now and it looks like a full dependent type system is a bit of an overkill.
Having at least something comparable to what C++ provides would still be an asset though. Do you think that's possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way I'm very much in favour of generic static parameters, I just think it might be worth defining the limits of this proposal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i hope this goes through . fixed point arithmetic is another use case, also shift values for compressed/relative pointers