-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Changes from 1 commit
81e0e76
5c23243
cfe5100
7d95567
917cb2a
83b2e03
0ce8ff6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<n: int>(x: int) -> int { | ||
| x + n | ||
| } | ||
|
|
||
| fn main() { | ||
| add_n<3>(4); // => 7 | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| The syntax `<n: int>` 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<T, n: uint> { | ||
| pub data: [T, ..n] | ||
| } | ||
|
|
||
| impl<T, n: uint> Vector<T, n> { | ||
| fn new(data: [T, ..n]]) -> Vector<T, n> { | ||
| Vector{data: data} | ||
| } | ||
| } | ||
|
|
||
| impl<T: Add, 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, n: uint, m: uint> | ||
| (x: Vector<T, n>, y: Vector<T, m>) -> Vector<T, n + m> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe 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 |
||
| { | ||
| 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? | ||
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.
Have you thought of using the
statickeyword to make this clearer?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.
Yes. That would be my favourite alternative to the syntax proposed, if it is necessary or desired to mark these parameters. I should probably explicitly add this to the RFC.
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.
Yeah I think it would remove any ambiguity. That way you would not need to know if
intwas a trait or type.intis easy, but user defined types could be more difficult.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.
Hm… I mostly thought about distinguishing them visually, since the static parameters will be lowercase, while the type parameters are uppercase. But then if the trait bounds and static types look alike this becomes less clear. I guess it makes sense to change it.