Skip to content
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

Closed
wants to merge 36 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
27c33a3
rfc: Π-types
ticki Jun 22, 2016
e4446c5
Expand the `overview` section
ticki Jun 22, 2016
eb60cd3
Propose alternative syntaxes
ticki Jun 22, 2016
71f2d8b
Improve type inference with a new rule allowing direct substitution
ticki Jun 22, 2016
b355811
Add section on impl unification
ticki Jun 22, 2016
a705532
Add transitivity suggestion
ticki Jun 22, 2016
946aa55
Change syntax, add transitivity rule
ticki Jun 22, 2016
d52e728
Add 'How we teach this'
ticki Jun 22, 2016
551688c
Fix the code example
ticki Jun 22, 2016
ea92a57
Add optional extension
ticki Jun 22, 2016
1d8813b
s/baz/foo
ticki Jun 22, 2016
d6a9b05
Use unicode for the lines
ticki Jun 22, 2016
99b2005
Expand on the implication checking and the rules governing it
ticki Jun 23, 2016
60467c2
Add subsection of 'motivation' dedicated to examples, at exit-point i…
ticki Jun 23, 2016
468f16a
Expand motivation with aims
ticki Jun 23, 2016
a01250c
Double negation and division by zero handling
ticki Jun 23, 2016
817ec48
Prove decidability
ticki Jun 23, 2016
d46f550
Thanks for the feedback, gnzlbg, killercup, and kennytm
ticki Jun 23, 2016
54e00af
"Informalize" the rules
ticki Jun 23, 2016
5718d4b
Fix some typos in Pi Types RFCs
killercup Jun 23, 2016
386de25
Merge pull request #1 from killercup/patch-1
ticki Jun 23, 2016
dc4635a
Add section on SMT-solvers, fix gnzlbg's notes
ticki Jun 23, 2016
d75674b
Merge branch 'pi-types' of github.com:Ticki/rfcs into pi-types
ticki Jun 23, 2016
4e36bed
Add remark
ticki Jun 23, 2016
b555120
Add section on possible extensions
ticki Jun 23, 2016
892e3d6
Fix title on the second question
ticki Jun 23, 2016
754b944
Add section on structural equality, add symbolic implication
ticki Jun 23, 2016
c7a2f28
Fix structural equality section
ticki Jun 23, 2016
9573c4d
Add alternative with inherited bounds
ticki Jun 23, 2016
74ef189
Move the SMT-section
ticki Jun 24, 2016
9f06922
Add Hoare-based value/type optional extension
ticki Jun 24, 2016
6b88ae5
Fix typos
ticki Jun 24, 2016
998c292
Transcription rules for inequalities
ticki Jun 24, 2016
09aef59
Clarify notation
ticki Jun 24, 2016
0f3a499
Typos
ticki Jun 25, 2016
2dbfe2d
Fix typos
ticki Jun 27, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions text/0000-pi-types.md
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.
Copy link
Member

@brendanzab brendanzab Jun 22, 2016

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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).


# 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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).
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

@gnzlbg gnzlbg Jun 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are interested in value dependency but want to avoid having to solve "complex" constraint problems (e.g. via SMT solvers).

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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We achieve this by using const fns, which allow us to take ...

## 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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> {
Copy link
Member

Choose a reason for hiding this comment

The 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>

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 const fns" through {} blocks?

Copy link
Contributor

@gnzlbg gnzlbg Jun 23, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just call any const fn there that does whatever you want:

const fn foo() -> usize { 
    let n = "foo"; 
    let m = foo.len(); 
    if m > 2 { m * 3 } else { 42 } 
} 
Array<foo(), T>

Or maybe even use a closure:

Array<const || {
        let n = "foo"; 
        let m = foo.len(); 
        if m > 2 { m * 3 } else { 42 } 
}(), T>

@ticki I don't think that the RFC mentions const closures yet, it probably should. At least just to say that if const closures would ever become a thing, they should be usable whenever const fn are or something like that. const closures would be a completely different RFC.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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 `;`?