-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: const-dependent type system. #1657
Changes from 1 commit
27c33a3
e4446c5
eb60cd3
71f2d8b
b355811
a705532
946aa55
d52e728
551688c
ea92a57
1d8813b
d6a9b05
99b2005
60467c2
468f16a
a01250c
817ec48
d46f550
54e00af
5718d4b
386de25
dc4635a
d75674b
4e36bed
b555120
892e3d6
754b944
c7a2f28
9573c4d
74ef189
9f06922
6b88ae5
998c292
09aef59
0f3a499
2dbfe2d
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,182 @@ | ||
- Feature Name: pi-types | ||
- Start Date: 2016-06-22 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
We propose a simple, yet sufficiently expressive, addition of dependent-types | ||
(also known as, Π-types and value-types). | ||
|
||
Type checking remains decidable. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
An often requested feature is the "type-level numerals", which enables generic | ||
length arrays. The current proposals are often limited to integers or even lack | ||
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. Link to the Example below. |
||
of value maps, and other critical features. | ||
|
||
There is a whole lot of other usecases as well. These allows certain often | ||
requested features to live in standalone libraries (e.g., bounded-integers, | ||
type level arithmetics, lattice types). | ||
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 would like to see two more Examples below, bounded-integers and lattice types. They don't need to be a final library, just a partial implementation to show how they would look like with this RFC. |
||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
## The new value-type construct, `const` | ||
|
||
The first construct, we will introduce is `ε → τ` constructor, `const`. All this | ||
does is taking a const-expr (struct construction, arithmetic expression, and so | ||
on) and constructs a _type-level version_ of this. | ||
|
||
In particular, we extend the type grammar with an additional `const C`, a type | ||
whose semantics can be described as follows, | ||
|
||
ValueTypeRepresentation: | ||
Π ⊢ x: const c | ||
-------------- | ||
Π ⊢ x = c | ||
|
||
In other words, if `x` has type `const c`, its value _is_ `c`. That is, any | ||
constexpr, `c`, will either be of its underlying type or of the type, `const | ||
c`. | ||
|
||
It is important to understand that values of `const c` are constexprs, and | ||
follows their rules. | ||
|
||
## `const fn`s as Π-constructors | ||
|
||
We are interested in value dependency, but at the same time, we want to avoid | ||
complications such as SMT-solvers and so on. | ||
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.
Kill the so on (it doesn't add any new information). Link SMT solvers to its wikipedia page. |
||
|
||
Thus, we follow a purely constructible model, by using `const fn`s. | ||
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. What is a purely constructible model? Is this sentence necessary? I guess this could be killed. |
||
|
||
Let `f` be a `const fn` function. From the rules of `const fn`s and constexprs, | ||
we can derive the rule, | ||
|
||
PiConstructorInference: | ||
Π ⊢ x: const c | ||
Π ⊢ f(c): τ | ||
-------------- | ||
Π ⊢ f(x): const τ | ||
|
||
This allows one to take some const parameter and map it by some arbitrary, pure | ||
function. | ||
|
||
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.
|
||
## Type inference | ||
|
||
Since we are able to evaluate the function on compile time, we can easily infer | ||
const types, by adding an unification relation, from the rule above. | ||
|
||
The relational edge between two const types is simple a const fn, which is | ||
resolved under unification. | ||
|
||
## `where` clauses | ||
|
||
Often, it is wanted to have some statically checked clause satisfied by the | ||
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. Maybe it is better to motivate this with a small example right here (e.g. unchecked indexing into an array?), and then go to explain constant expressions in where clauses and their implications. |
||
constant parameters. To archive this, in a reasonable manner, we use const | ||
exprs, returning a boolean. | ||
|
||
We allow such constexprs in `where` clauses of functions. Whenever the | ||
function is invoked given constant parameters `<a, b...>`, the compiler | ||
evaluates this expression, and if it returns `false`, an aborting error is | ||
invoked. | ||
|
||
## The type grammar | ||
|
||
These extensions expand the type grammar to: | ||
|
||
T = scalar (i32, u32, ...) // Scalars | ||
| X // Type variable | ||
| Id<P0..Pn> // Nominal type (struct, enum) | ||
| &r T // Reference (mut doesn't matter here) | ||
| O0..On+r // Object type | ||
| [T] // Slice type | ||
| for<r..> fn(T1..Tn) -> T0 // Function pointer | ||
| <P0 as Trait<P1..Pn>>::Id // Projection | ||
| C // const types | ||
F = c // const fn name | ||
C = E // Pi constructed const type | ||
P = r // Region name | ||
| T // Type | ||
O = for<r..> TraitId<P1..Pn> // Object type fragment | ||
r = 'x // Region name | ||
E = F(E) // Constant function application. | ||
| p // const type parameter | ||
| [...] // etc. | ||
|
||
Note that the `const` prefix is only used when declaring the parameter. | ||
|
||
## An example | ||
|
||
This is the proposed syntax: | ||
|
||
```rust | ||
use std::{mem, ptr}; | ||
|
||
// We start by declaring a struct which is value dependent. | ||
struct Array<n: const usize, T> { | ||
// `n` is a constexpr, sharing similar behavior with `const`s, thus this | ||
// is possible. | ||
content: [T; n], | ||
} | ||
|
||
// We are interested in exploring the `where` clauses and Π-constructors: | ||
impl<n: const usize, T> Array<n, T> { | ||
// This is simple statically checked indexing. | ||
fn const_index<i: const usize>(&self) -> &T where i < n { | ||
// note that this is constexpr ^^^^^ | ||
unsafe { self.content.unchecked_index(i) } | ||
} | ||
|
||
// "Push" a new element, incrementing its length **statically**. | ||
fn push(self, elem: T) -> Array<n + 1, T> { | ||
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. Am I right to assume that you could eventually also write more complicated constexpr by supplying blocks? E.g. Array<{ let n = "foo"; let m = foo.len(); if m > 2 { m * 3 } else { 42 } }, T> 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. Well, that depends on how the constexpr definition changes over time. Is it planned to allow "anonymous 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. You can just call any
Or maybe even use a closure:
@ticki I don't think that the RFC mentions |
||
let mut new: [T; n + 1] = mem::uninitialized(); | ||
// ^^^^^ constexpr | ||
unsafe { | ||
ptr::copy(self.content.as_ptr(), new.as_mut_ptr(), n); | ||
ptr::write(new.as_mut_ptr().offset(n), elem); | ||
} | ||
|
||
// Don't call destructors. | ||
mem::forget(self.content); | ||
|
||
// So, the compiler knows the type of `new`. Thus, it can easily check | ||
// if the return type is matching. By siply evaluation `n + 1`, then | ||
// comparing against the given return type. | ||
Array { content: new } | ||
} | ||
} | ||
|
||
fn main() { | ||
let array: Array<2, u32> = Array { content: [1, 2] }; | ||
|
||
assert_eq!(array.const_index::<0>(), 1); | ||
assert_eq!(array.const_index::<1>(), 2); | ||
assert_eq!(array.push(3).const_index::<2>(), 3); | ||
} | ||
``` | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
If we want to have type-level Turing completeness, the halting problem is | ||
inevitable. One could "fix" this by adding timeouts, like the current recursion | ||
bounds. | ||
|
||
Another draw back is the lack of implication proves. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
Use full SMT-based dependent types. These are more expressive, but severely | ||
more complex as well. | ||
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. This needs expansion, there are a lot of issues about SMT-based dependent types that have already been mentioned. It would be worth it to summarize them here. It would be worth it to consider if it would be possible to add a theorem prover in the future without breaking backwards compatibility. And the possibility of adding one just as a quality of implementation issue (e.g. to detect "broken" bounds and tell the user how to fix them). |
||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
What syntax is preferred? How does this play together with HKP? Can we improve | ||
the converse type inference? What should be the naming conventions? Should we | ||
segregate the value parameters and type parameters by `;`? |
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.
Isn't Rust's type checker already undecidable? Are you just suggesting that this proposal doesn't add any more undecidable language features? And if so, how do you know?
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.
Well, yes and no. The recursion bound makes it decidable.
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.
But, what I really mean is that there is no theorem proving involved (SMT or SAT solvers).