Skip to content

Commit 8134c33

Browse files
committed
rfc: Π-types
1 parent 3a16a14 commit 8134c33

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

text/0000-pi-types.md

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
- Feature Name: pi-types
2+
- Start Date: 2016-06-22
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
I propose a simple, yet sufficiently expressive, addition of dependent-types
10+
(also known as, Π-types and value-types).
11+
12+
Type checking remains decidable.
13+
14+
# Motivation
15+
[motivation]: #motivation
16+
17+
An often requested feature is the "type-level numerals", which enables generic
18+
length arrays. The current proposals are often limited to integers or even lack
19+
of value maps, and other critical features.
20+
21+
There is a whole lot of other usecases as well. These allows certain often
22+
requested features to live in standalone libraries (e.g., bounded-integers,
23+
type level arithmetics, lattice types).
24+
25+
# Detailed design
26+
[design]: #detailed-design
27+
28+
## The new value-type construct, `const`
29+
30+
The first construct, I will introduce is `ε → τ` constructor, `const`. All this
31+
does is taking a const-expr (struct construction, arithmetic expression, and so
32+
on) and constructs a _type-level version_ of this.
33+
34+
In particular, we extend the type grammar with an additional `const C`, a type
35+
whose semantics can be described as follows,
36+
37+
ValueTypeRepresentation:
38+
Π ⊢ x: const c
39+
--------------
40+
Π ⊢ x = c
41+
42+
In other words, if `x` has type `const c`, its value _is_ `c`. That is, any
43+
constexpr, `c`, will either be of its underlying type or of the type, `const
44+
c`.
45+
46+
It is important to understand that values of `const c` are constexprs, and
47+
follows their rules.
48+
49+
## `const fn`s as Π-constructors
50+
51+
We are interested in value dependency, but at the same time, we want to avoid
52+
complications such as SMT-solvers and so on.
53+
54+
Thus, we follow a purely constructible model, by using `const fn`s.
55+
56+
Let `f` be a `const fn` function. From the rules of `const fn`s and constexprs,
57+
we can derive the rule,
58+
59+
PiConstructorInference:
60+
Π ⊢ x: const c
61+
Π ⊢ f(c): τ
62+
--------------
63+
Π ⊢ f(x): const τ
64+
65+
This allows one to take some const parameter and map it by some arbitrary, pure
66+
function.
67+
68+
## Type inference
69+
70+
Since we are able to evaluate the function on compile time, we can easily infer
71+
const types, by adding an unification relation, from the rule above.
72+
73+
The relational edge between two const types is simple a const fn, which is
74+
resolved under unification.
75+
76+
## `where` clauses
77+
78+
Often, it is wanted to have some statically checked clause satisfied by the
79+
constant parameters. To archive this, in a reasonable manner, we use const
80+
exprs, returning a boolean.
81+
82+
We allow such constexprs in `where` clauses of functions. Whenever the
83+
function is invoked given constant parameters `<a, b...>`, the compiler
84+
evaluates this expression, and if it returns `false`, an aborting error is
85+
invoked.
86+
87+
## The type grammar
88+
89+
These extensions expand the type grammar to:
90+
91+
T = scalar (i32, u32, ...) // Scalars
92+
| X // Type variable
93+
| Id<P0..Pn> // Nominal type (struct, enum)
94+
| &r T // Reference (mut doesn't matter here)
95+
| O0..On+r // Object type
96+
| [T] // Slice type
97+
| for<r..> fn(T1..Tn) -> T0 // Function pointer
98+
| <P0 as Trait<P1..Pn>>::Id // Projection
99+
| C // const types
100+
F = c // const fn name
101+
C = E // Pi constructed const type
102+
P = r // Region name
103+
| T // Type
104+
O = for<r..> TraitId<P1..Pn> // Object type fragment
105+
r = 'x // Region name
106+
E = F(E) // Constant function application.
107+
| p // const type parameter
108+
| [...] // etc.
109+
110+
Note that the `const` prefix is only used when declaring the parameter.
111+
112+
## An example
113+
114+
This is the proposed syntax:
115+
116+
```rust
117+
use std::{mem, ptr};
118+
119+
// We start by declaring a struct which is value dependent.
120+
struct Array<n: const usize, T> {
121+
// `n` is a constexpr, sharing similar behavior with `const`s, thus this
122+
// is possible.
123+
content: [T; n],
124+
}
125+
126+
// We are interested in exploring the `where` clauses and Π-constructors:
127+
impl<n: const usize, T> Array<n, T> {
128+
// This is simple statically checked indexing.
129+
fn const_index<i: const usize>(&self) -> &T where i < n {
130+
// note that this is constexpr ^^^^^
131+
unsafe { self.content.unchecked_index(i) }
132+
}
133+
134+
// "Push" a new element, incrementing its length **statically**.
135+
fn push(self, elem: T) -> Array<n + 1, T> {
136+
let mut new: [T; n + 1] = mem::uninitialized();
137+
// ^^^^^ constexpr
138+
unsafe {
139+
ptr::copy(self.content.as_ptr(), new.as_mut_ptr(), n);
140+
ptr::write(new.as_mut_ptr().offset(n), elem);
141+
}
142+
143+
// Don't call destructors.
144+
mem::forget(self.content);
145+
146+
// So, the compiler knows the type of `new`. Thus, it can easily check
147+
// if the return type is matching. By siply evaluation `n + 1`, then
148+
// comparing against the given return type.
149+
Array { content: new }
150+
}
151+
}
152+
153+
fn main() {
154+
let array: Array<2, u32> = Array { content: [1, 2] };
155+
156+
assert_eq!(array.const_index::<0>(), 1);
157+
assert_eq!(array.const_index::<1>(), 2);
158+
assert_eq!(array.push(3).const_index::<2>(), 3);
159+
}
160+
```
161+
162+
# Drawbacks
163+
[drawbacks]: #drawbacks
164+
165+
If we want to have type-level Turing completeness, the halting problem is
166+
inevitable. One could "fix" this by adding timeouts, like the current recursion
167+
bounds.
168+
169+
Another draw back is the lack of implication proves.
170+
171+
# Alternatives
172+
[alternatives]: #alternatives
173+
174+
Use full SMT-based dependent types. These are more expressive, but severely
175+
more complex as well.
176+
177+
# Unresolved questions
178+
[unresolved]: #unresolved-questions
179+
180+
What syntax is preferred? How does this play together with HKP? Can we improve
181+
the converse type inference? What should be the naming conventions? Should we
182+
segregate the value parameters and type parameters by `;`?

0 commit comments

Comments
 (0)