Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Changes from 1 commit
Commits
Show all changes
39 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
41c8ff3
Pi types core RFC.
ticki Feb 26, 2017
aaf7d55
Π → Γ
ticki Feb 26, 2017
9a9c028
Add note on notation.
ticki Mar 12, 2017
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
Prev Previous commit
Next Next commit
Add section on SMT-solvers, fix gnzlbg's notes
  • Loading branch information
ticki committed Jun 23, 2016
commit dc4635a2ef074f1271b91c25a695c509ea0cc269
108 changes: 93 additions & 15 deletions text/0000-pi-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ use std::ops;
///
/// This has two value parameter, respectively representing an upper and a lower bound.
pub struct BoundedInt<const lower: usize, const upper: usize>
// Compile time constraints.
where lower <= upper {
/// The inner runtime value.
n: usize,
Expand All @@ -103,7 +104,8 @@ impl<const n: usize> BoundedInt<n, n> {
impl<const upper_a: usize,
const lower_a: usize,
const upper_b: usize,
const lower_b: usize> ops::Add<BoundedInt<lower_b, upper_b>> for BoundedInt<lower_a, lower_b>
const lower_b: usize> ops::Add<BoundedInt<lower_b, upper_b>> for BoundedInt<lower_a, upper_a>
// We have to satisfy the constraints set out in the struct definition.
where lower_a <= upper_a,
lower_b <= upper_b,
// Check for overflow by some `const fn`.
Expand All @@ -121,7 +123,7 @@ impl<const upper_a: usize,
impl<const upper_a: usize,
const lower_a: usize,
const upper_b: usize,
const lower_b: usize> From<BoundedInt<lower_b, upper_b>> for BoundedInt<lower_a, lower_b>
const lower_b: usize> From<BoundedInt<lower_b, upper_b>> for BoundedInt<lower_a, upper_a>
where lower_a <= upper_a,
lower_b <= upper_b,
// We will only extend the bound, never shrink it without runtime
Expand Down Expand Up @@ -161,6 +163,8 @@ impl<const n: usize, T: Clone> Clone for [T; n] {

### Statically checked indexing

One can perform simple, interval based, statically checked indexing:

```rust
use std::ops;

Expand All @@ -176,6 +180,16 @@ impl<const n: usize, T: Clone> ops::Index<BoundedInt<0, n - 1>> for [T; n] {
}
```

### Fancy number stuff

```rust
struct Num<const n: usize>;

trait Divides<const m: usize> {}

impl<const a: usize, const b: usize> Divides<b> for Num<a> where b % a == 0 {}
```

# Detailed design
[design]: #detailed-design

Expand Down Expand Up @@ -235,14 +249,14 @@ We add an extra rule to improve inference:
Γ ⊢ x: A
Γ ⊢ a: T<c>
Γ ⊢ a: T<x>
──────────────
────────────
Γ ⊢ c = x

So, if two types share constructor by some Π-constructor, share a value, their
value parameter is equal. Take `a: [u8; 4]` as an example. If we have some
unknown variable `x`, such that `a: [u8; x]`, we can infer that `x = 4`.

This allows us infer e.g. array length parameters in functions:
This allows us to infer e.g. array length parameters in functions:

```rust
// [T; N] is a constructor, T → usize → 𝓤 (parameterize over T and you get A → 𝓤).
Expand All @@ -268,8 +282,8 @@ evaluates this expression, and if it returns `false`, an aborting error is
invoked.

To sum up, the check happens when typechecking the function calls (that is,
checking if the parameters satisfy the trait bounds, and so on). The caller's
bounds must imply the invoked functions' bounds:
checking if the parameters satisfy the trait bounds). The caller's bounds must
imply the invoked functions' bounds:

### Transitivity of bounds.

Expand Down Expand Up @@ -332,8 +346,8 @@ One can show this by considering each case:

#### (Optional extension:) "Exit-point" identities

These are simply identities which always holds. Whenever the compiler reach one
of these in the `where` clause unfolding, it returns "True":
These are simply identities which always holds. Whenever the compiler reaches one
of these when unfolding the `where` clause, it returns "True":

LeqReflexive:
f(x) <= f(x) for x primitive integer
Expand Down Expand Up @@ -369,13 +383,55 @@ checker):
Contradictive or unsatisfiable bounds (like `a < b, b < a`) cannot be detected,
since such a thing would be undecidable.

These bounds doesn't break anything, they are simply malformed and unreachable.
These bounds don't break anything, they are simply malformed and unreachable.

Take `a < b, b < a` as an example. We know the values of `a` and `b`, we can
thus calculate the two bounds, which will clearly fail. We cannot, however,
stop such malformed bounds in _declarations_ and _function definitions_, due to
mathematical limitations.

### SMT-solvers?

#### What a Rusty SMT-solver would look like

The simplest and least obstructive SMT-solver is the SAT-based one. SAT is a
class of decision problem, where a boolean formula, with some arbitrary number
of free variables, is determined to be satisfiable or not. Obviously, this is
decidable (bruteforcing is the simplest algorithm, since the search space is
finite, bruteforcing is guaranteed to terminate).

SAT is NP-complete, and even simple statements such as `x + y = y + x` can take
a long time to prove. A non-SAT (symbolic) SMT-solver is strictly more
expressive, due to not being limited to finite integers, however first-order
logic is not generally decidable, and thus such solvers are often returning
"Satisfiable", "Not satisfiable", "Not known".

In general, such algorithms are either slow or relatively limited. An example
of such a limitation is in the [Dafny
language](https://github.com/Microsoft/dafny), where programs exist that
compile when having the bound `a \/ b`, but fails when having the bound `b \/
a`. This can be relatively confusing the user.

It is worth noting that the technology on this area is still improving, and
these problems will likely be marginalized in a few years.

Another issue which is present in Rust, is that you don't have any logical
(invariant) information about the return values. Thus, a SMT-solver would work
relatively poorly (if at all) non-locally (e.g. user defined functions).

That issue is not something that prevents us from adopting a SMT-solver, but it
limits the experience with having one.

#### Backwards compatibility

While I am against adding SMT-solvers to `rustc`, it is worth noting that this
change is, in fact, compatible with future extensions for more advanced theorem
provers.

The only catch with adding a SMT-solver is that errors on unsatisfiability or
contradictions would be a breaking change. By throwing a warning instead, you
essentially get the same functionality.

## The grammar

These extensions expand the type grammar to:
Expand Down Expand Up @@ -404,7 +460,8 @@ Note that the `const` syntax is only used when declaring the parameter.
## `impl` unification

Only one `where` bound can be specified on each disjoint implementations (for
possible extensions, see below).
possible extensions, see below). In other words, no overlap is allowed, even if
the `where` bounds are mutually exclusive.

To find the right implementation, we use the data from the type inference (see
the inference rules above). Since the parameters are, in fact, not much
Expand All @@ -416,6 +473,21 @@ Likewise are disjointness checks based on structural equality. That is, we only
care about structural equality, not `Eq` or something else. This allows us to
reason more rigorously about the behavior.

Any non-identity-related term is threated as an unknown parameter, since reasoning about uniqueness of those is undecidable. For example,

```rust
impl<const x: usize> Trait<x * x> for Struct<x> where some_fn(x)
```

is, when checking for implementation uniqueness, semantically behaving like

```rust
impl<const x: usize, const y: usize> Trait<y> for Struct<x>
```

since we cannot prove injectivity. Note that this is only about behavior under
_uniqueness checking_.

Since not all parameters' edges are necessarily the identity function,
dispatching these would be undecidable. A way to solve this problem is to
introduce some syntax allowing to specify the `impl` parameters. This is not
Expand Down Expand Up @@ -499,7 +571,7 @@ A: Various other languages have dependent type systems. Strictly speaking, all
boundary between value and type. Unfortunately, as cool as it sounds, it has
some severe disadvantages: most importantly, the type checking becomes
undecidable. Often you would need some form of theorem prover to type check
the program, and those has their limitations too.
the program, and those have their limitations too.

Q: What are `const fn` and how is it linked to this RFC?

Expand All @@ -510,9 +582,10 @@ A: `const fn` is a function, which can be evaluated at compile time. While it

Q: What are the usecases?

A: There are many usecases for this. The most prominent one, perhaps, is the
generically sized arrays. Dependent types allows one to lift the length of the
array up to the type-level, effectively allowing one to parameterize over them.
A: There are many usecases for this. The most prominent one, perhaps, is
abstracting over generically sized arrays. Dependent types allows one to lift
the length of the array up to the type-level, effectively allowing one to
parameterize over them.

Q: What are the edge cases, and how can one work around those (e.g. failed
unification)?
Expand Down Expand Up @@ -570,7 +643,12 @@ _arguments_ instead of constant _parameters_. This allows for bounds on e.g.
fn do_something(const x: u32) -> u32 where x < 5 { x }
```

From the callers perspective, this one is especially nice to work with.
From the callers perspective, this one is especially nice to work with, however
it can lead to confusion about mixing up constargs and runtime args. One
possible solution is to segregate the constargs from the rest arguments by a
`;` (like in array types).

Another way to semantically justify such a change is by the [`Const` type constructor](#an-extension-a-constexpr-type-constructor)

### Square brackets

Expand Down