# Rust for non-Rustacean math-y types
*This hackmd is prepared for the purposes of discussion with folks familiar with PL/proof/logic/etc but not so much with Rust.*
## Overarching goals for today
* We have some practical problems
* Want to extend our semantics to be coinductive but not sure how to handle
* How to best describe associated types ("beyond syntactic equality")
* We have some more abstract problems
* We want to map Rust's trait system to logical concepts so we can reason about it better
* Building an (executable) model of the Rust type system to serve as a "spec" ([a-mir-formality](https://github.com/rust-lang/a-mir-formality))
## Simple Rust program
```rust
trait Clone {
fn clone(&self) -> Self;
}
impl Clone for u32 {
fn clone(&self) -> Self {
*self
}
}
impl<T> Clone for Option<T>
where
T: Clone,
{
fn clone(&self) {
match self {
Some(x) => x.clone(),
None => (),
}
}
}
fn foo<T: Clone>(t: T) {
t.clone();
}
fn main() {
foo(22);
foo(Some(22));
}
```
## Supertraits
```rust
trait Eq: PartialEq {} // supertrait
trait PartialEq {
fn partial_eq(self, other: Self) -> bool;
}
fn foo<T: Eq>(a: T, b: T) {
// ^^
a.partial_eq(b)
}
```
## Associated types
```rust
trait Iterator {
type Item: Clone;
fn next(&mut self) -> Option<Self::Item>;
}
fn clone_all<I: Iterator>(i: I) {
while let Some(elem) = i.next() {
// elem has type `I::Item`...
elem.clone(); // ...and we know that impl `Clone`
}
}
```
## Rust 0
Grammar
```
Program := (TraitDefn | StructDefn | Implementation)*
TraitDefn := trait Tr {}
Ty := S < {Ty} >
| X
StructDefn := struct S < {X} >
Implementation := impl<{X}> Tr for Ty where WC {}
WC := (Ty : Tr)
| WC, WC
X := identifier of a generic parameter
```
## Defining Rust trait solver
So given a `Program`, when can we judge a given trait to be implemented?
We'll define a judgment `WC_a |- WC_g`. This means that, given the assumptions `WC_a`, `WC_g` is provable.
So far WC is just `(Ty: Tr)`, so e.g. `|- (u32: Clone)` says that `Clone` is implemented for `u32`.
```
substituted impl Tr for Ty_t where WC
Ty_t = Ty_s
A |- WC
------------------------------- impl
A |- (Ty_s: Tr)
```
```
impl<{X}> Tr for Ty_t where WC
let S be some substitution for {X}
S Ty_t = Ty_s
A |- S WC
------------------------------- impl
A |- (Ty_s: Tr)
```
```rust
impl Clone for u32 where true
impl<T> Clone for Option<T>
where
T: Clone,
```
```
impl<T> Clone for Option<T> where (T: Clone)
S = { T => u32 } -- inferred by the compiler via unification
S Option<T> = Option<u32> --after-substitution--> Option<u32> = Option<u32>
A |- S (T: Clone) --after-substitution--> A |- (u32: Clone)
------------------------------- impl
A |- (Option<u32>: Clone)
```
And we can define some basic connectives:
```
------------------------------- environment
..., WC_i, ... |- WC_i
```
```
A |- WC_0
A |- WC_1
------------------------------- and
A |- WC_0 && WC_1
```
...and some basic things like weakening, reordering, which I won't bother to list.
## Defining Rust trait solver
Now we can use this to prove interesting things like `Option<u32>: Clone`:
* `Option<u32>: Clone`, from `impl<T> Clone for Option<T> where T: Clone` with substitution `[u32 / T]`
* `u32: Clone`, from `impl Clone for u32`
## Extending to higher-ranked stuff
More interestingly, we can extend WC to include higher-ranked things and implications:
```
WC = (WC && WC)
| (T: Trait)
| forall<X> WC
| (WC => WC)
```
with rules like
```
X not free in A, Program
A |- WC
------------------------------- forall-introduction
A |- forall<X> WC
```
and
```
(A, WC_0) |- WC_1
------------------------------- implication-introduction
A |- (WC_0 => WC_1)
```
and
```
A |- (forall<X> WC_0)
------------------------------- forall-elimination
A |- { Ty / X } WC_0
```
```
A |- (WC_0 => WC_1)
A |- WC_0
------------------------------- implies-left
A |- WC_1
```
Why would we do this? As it happens, Rust includes "higher-ranked trait bounds",
but we also need to be able to do forall and implication just to do basic type checking.
For example, consider this function:
```rust
fn clone_t<T>(t: T)
where
T: Clone,
{
let o: Option<T> = Some(t);
o.clone(); // valid if `Option<T>: Clone`
}
```
To prove that `o.clone()` is valid, the type-checker wants to judge that `Option<T>: Clone`.
We can express that as
```
|- forall<T> ((T: Clone) => (Option<T>: Clone))
```
and we can prove this via the above rules
* `|- forall<T> ((T: Clone) => (Option<T>: Clone))`, then by rule "forall-introduction"..
* `|- ((T: Clone) => (Option<T>: Clone))`, by rule "implication-introduction"...
* `(T: Clone) |- (Option<T>: Clone)`, by rule "impl"...
* `(T: Clone) |- (T: Clone)`, by rule environment.
### ill-typed program
```rust
fn clone_t<T>(t: T)
{
let o: Option<T> = Some(t);
o.clone(); // valid if `Option<T>: Clone`
}
```
* `|- forall<T> (Option<T>: Clone)`, then by rule "forall-introduction"..
* `|- (Option<T>: Clone)`, by rule "impl"...
* `|- (T: Clone)` cannot be proven
### Supertrait declarations
Extend grammar
```
TraitDefn = trait Tr: Tr {}
```
so that e.g.
```rust
trait Eq: PartialEq {} // supertrait
trait PartialEq {}
```
can be permitted by a rule
```
trait Trait2: Trait1 {}
A |- (T: Trait2)
------------------------- supertrait v1
A |- (T: Trait1)
```
## More ill-typed
```rust
trait Clone { fn clone(&self) -> Self; }
struct S { }
fn main() {
let s = S { };
s.clone(); // ERROR -- if this were to execute, we would have no method definition to use
// "goes wrong"
}
```
## What does soundness mean?
What does proving
```
|- WC
```
*mean*? We are trying to maintain an invariant here, that `WC` is *truly true*, meaning...
* When `WC = (T: Trait)`, then
* `T` is a struct in the program
* and there exists a (substituted) impl such that `Self = T` and all WC are *truly true*
* When `WC = forall<{X}> WC_0`, then
* for any well-defined substitution `S` such that `S WC_0` is *truly true*
* where well-defined means that it only refers to structs declared in the program
* When `WC = WC_0 => WC_1`, then
* if `WC_0` is *truly true*, then `WC_1` is *truly true*
* (or *provable* implies *truly true*?)
* When `WC = WC_0 && WC_1`, then
* both `WC_0` and `WC_1` are *truly true*
We would like to guarantee that
* If `|- WC` then `WC` is *truly true*.
The rule system we developed so far does not meet this property -- because of supertraits.
## But
```rust
trait Eq
where
Self: PartialEq,
{ }
trait PartialEq {
fn partial_eq(&self);
}
impl Eq for u32 { }
// This is missing:
//
// impl PartialEq for u32 { fn partial_eq(&self) { X } }
fn main() {
let t: u32 = 22;
t.partial_eq(); // Provable, but should it be?
}
```
* `|- (u32: PartialEq)`, via supertrait rule (which says that `Eq=>PartialEq`)
* `|- (u32: Eq)`, via impl (`impl Eq for u32`)
* ok!
## Possible solutions
### Validate the impl
```
trait Tr: Tr1 {}
|- forall<{X}> (WC => Ty: Tr1)
----------------------------
|- impl<{X}> Tr for Ty where WC { } OK
```
But what about
```rust
trait Clone { fn clone(&self) -> Self; }
trait Magic: Clone
impl<T: Magic> Magic for T {
/* ok on its own */
}
fn main() {
let t: u32 = 22;
t.clone()
// "almost" provable, even though there is no `impl Clone for u32`
}
```
* `|- u32: Clone`, via supertrait (MAgic => Clone)
* `|- u32: Magic`, via impl
* `|- u32: Magic`, via impl
* `|- u32: Magic`, via impl
* ...never get a finite proof tree...
* today we are inductive, but we want to be coinductive
This impl appears well-formed. Using it will cause an infinite proof, however (but see coinduction notes later).
#### Forbid blanket impls like this?
Also forbids useful patterns that we rely on everywhere, most obviously...
```rust
trait Extension: BaseTrait {
fn new_method(&self);
}
impl<T: BaseTrait> Extension for T {
// ^^^^^^^^^^^^ "syntactically" the same
fn new_method(&self) {
/* just use methods from the base trait */
}
}
```
...in general enforcing ordering etc gets tricky.
### Check the assumptions when you use the impl
Our solution:
```
impl<{X}> Tr for Ty_t where WC {}
trait Tr: Tr1 {} // <-- changed
let S be some substitution for {X}
S Ty_t = Ty_s
A |- S WC
A |- (Ty_s: Tr1) // <-- changed
------------------------------- impl v2
A |- (Ty_s: Tr)
```
## Desired change to Rust: coinductive semantics
We allow people to write `#[derive(Clone)]`. This will autogenerate the code for cloning. Given a struct like this
```rust
#[derive(Clone)]
struct Tree<T> {
data: T,
children: Vec<Tree<T>>,
}
// impl<T> Clone for Vec<T> where T: Clone { .. }
```
it could for example generate
```rust
impl<T> Clone for Tree<T>
where
T: Clone,
Vec<Tree<T>>: Clone,
{
fn clone(&self) -> Self {
Tree {
data: self.data.clone(),
children: self.children.clone(),
}
}
}
```
OK, so far so good. But now you go to prove, say, that `Tree<u32>: Clone` and you get...
* `Tree<u32>: Clone` if
* `u32: Clone` -- true
* `Vec<Tree<u32>>: Clone` if
* `Tree<u32>: Clone`
* cycle -- in an inductive semantics, this is an error
We would like to shift to coinductive semantics. In fact, we already have it for some traits (e.g., `Send`, which indicates a thread-safe data structure).
### BUT: implications
We can't just take the rules we've been using and interpret them coinductively.
It will break with supertraits.
```rust
trait Eq: PartialEq
```
then we had the equivalent of a rule like
```
A |- T: Eq
-------------
A |- T: PartialEq
```
which is clearly not productive. This can be "weaponized":
```rust
trait Haha: Clone { }
impl<T: Haha> Haha for T { }
```
would result in
```
A |- T: Haha
--------- "supertrait"
A |- T: Clone
```
```
A |- T: Haha
--------- "impl"
A |- T: Haha
```
given these traits and impls, you can prove `X: Clone` for any type `X`
(not just those types that have impls).
How?
* `X: Clone`, using "supertrait"
* `X: Haha`, using "impl"
* ... (ad infinitum)
* `X: Haha`, using "impl"
### How we are handling it in our model today
Don't permit "general" coinduction. Instead, only allow coinductive reasoning when proving impl where clauses:
```
trait Tr: Tr1 {}
impl<{X}> Tr for Ty_t where WC {}
let S be some substitution for {X}
S Ty_t = Ty_s
A, (Ty_s: Tr) |- S WC // <-- changed
// ~~~~~~~~~~ permit cycles
A |- (Ty_s: Tr1)
------------------------------- impl v3
A |- (Ty_s: Tr)
```
Simplified
```
trait Trait: TrSuper {}
substituted impl Trait for Ty_self where WC {}
A, (Ty_self: Trait) |- WC // <-- coinductive
A |- (Ty_self: TrSuper)
------------------------------- impl v3
A |- (Ty_self: Trait)
```
```
trait Trait: TrSuper {}
A, later (Ty_self: Trait) |- (Ty_self: Trait) implemented
A, later (Ty_self: Trait) |- (Ty_self: TrSuper)
-------------------------------
A |- (Ty_self: Trait)
```
```
substituted impl Trait for Ty_self where WC {}
A0, (Ty_self: Trait) |- WC
------------------------------- impl
A0, later (Ty_self: Trait) |- (Ty_self: Trait) implemented
```
Now if we have a "cyclic" impl like so
```rust
trait Magic: Clone { }
impl<T: Magic> Magic for T { }
```
* `|- u32: Clone`, via supertrait rule
* `|- u32: Magic`, via impl
* `(u32: Magic) |- (u32: Magic)` (impl where clauses) -- OK!
* via assumption
* `|- u32: Clone` (because `trait Magic: Clone`)
* cycle (rules are inductive --> error)
you can still try to prove `T: Clone` by first proving `T: Magic` but then you just have to prove `T: Clone` again (without the benefit of the cyclic assumption).
## What does soundness mean... coinductively?
Let's say a WC `WC` is *truly true* if
* When `WC = (T: Trait)`, then there exists a (substituted) impl such that `Self = T` and all WC are *truly true*
* When `WC = forall<{X}> WC_0`, then for any substitution `S` such that `S WC_0` is *truly true*
* When `WC = WC_0 => WC_1`, then if `WC_0` is *truly true*, then `WC_1` is *truly true*
* (or *provable* implies *truly true*?)
* When `WC = WC_0, WC_1`, then both `WC_0` and `WC_1` are *truly true*
We would like to guarantee that
* If `|- WC` then `WC` is *truly true*.
## Challenges and questions so far
* How do we define *soundness*?
* We can show that whenever you can prove `|- Ty: Foo`, there wll always be an `impl` rather trivially by induction
* But can we relate that to an approximation of an underlying "logic"?
* e.g., translate `impl<T> Clone for Option<T> where T: Clone` to `(Option<T>: Clone) :- (T: Clone)` and interpret coinductively
* show that our solver is an approximation of that
* for example `impl<T> Trait for T where Option<T>: Trait` is coinductively true (infinite proof tree) but it is not provable in our system
* but then what about `forall` and implication, and how do we take into account supertraits?
* e.g. `forall<T> ((T: Eq) => (T: PartialEq))` should be a provable WC, but we can't translate `trait Eq: PartialEq` to `(T: PartialEq) :- (T: Eq)` and still interpret coinductively
* what precedent is there here to look at?
### Sidenote: Elaborating assumptions?
```
(Ty: Eq) in A
A, (Ty: PartialEq) |- WC
------------------------ "supertrait(Eq/PartialEq)"
A |- WC
```
## Associated types (or "aliases")
Grammar
```
Program := (TraitDefn | StructDefn | Implementation)*
TraitDefn := trait Tr where WC { AssocTypeDefn }
// ~~~~~~~~~~~~~ new
AssocTypeDefn := type Assoc : {Tr} // <-- new
Ty := S < {Ty} >
| X
| Ty::Assoc // <-- new
StructDefn := struct S < {X} >
Implementation := impl<{X}> Tr for Ty where WC { AssocTypeValue }
// ~~~~~~~~~~~~~~ new
AssocTypeValue := type Assoc = Ty // <-- new
WC := (Ty : Tr)
| Ty: Tr<Assoc = Ty> // <-- new
| WC, WC
X := identifier of a generic parameter
```
```rust!
trait Clone { }
impl Clone for u32 { }
trait Iterator {
type Item: Clone; // <-- associated type
fn next(&mut self) -> Option<Self::Item>;
}
impl<T> Iterator for Vec<T>
where
T: Clone,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
..
}
}
fn foo<I>(iter: &mut I)
where
I: Iterator,
{
let v: Option<I::Item> = iter.next();
v.clone();
}
fn main() {
let v: Vec<u32> = Vec::new();
let i: Option< u32 > = v.next();
let j: Option< <Vec<u32> as Iterator>::Item > = v.next();
}
```
## What does soundness mean... for associated types
Let's say a WC `WC` is *truly true* if
* When `WC = Ty0: Tr<Assoc = Ty1>`, then
* there exists exactly one (substituted) impl `I` such that
* `Self = Ty0`,
* all its WC are *truly true*,
* `I` defines `Assoc` as `Ty1` (after subst).
* (exactly one = job of coherence, out of scope?)
and, as before, unchanged
* When `WC = (T: Trait)`, then there exists a (substituted) impl such that `Self = T` and all WC are *truly true*
* When `WC = forall<{X}> WC_0`, then for any substitution `S` such that `S WC_0` is *truly true*
* When `WC = WC_0 => WC_1`, then if `WC_0` is *truly true*, then `WC_1` is *truly true*
* (or *provable* implies *truly true*?)
* When `WC = WC_0, WC_1`, then both `WC_0` and `WC_1` are *truly true*
We would like to guarantee that
* If `|- WC` then `WC` is *truly true*.
##
* `Vec<X> = Vec<u32>` => `X = u32`
* `X::Item = u32::Item` => ?
* just as `f(x) = f(y)` does not tell me that `x = y` for an arbitrary function `f`
```rust
fn foo<X: Iterator<Item = u32>>(x: X) {
X::Item ==> u32
}
```
```rust!
fn foo<U, X: Iterator<Item = U>>(x: X) {
X::Item ==> U
}
```
##
```
WC = T: Trait<Assoc = U>
```
```
A |- U = T::Assoc
------------------------------- fallback
A |- T: Trait<Assoc = U>
```
```
substituted impl Tr for T where WC { type Assoc = S<U>; }
A, T: Trait<Assoc = S<U>> |- WC
------------------------------- normalize-via-impl
A, later(T: Trait<Assoc = S<U>>) |- T: Trait<Assoc = S<U>>
```
```
trait Trait0 { type Assoc: Trait1 }
------------------------------- fallback
A |- T::Assoc: Trait1
```
```
---------------------------
A |- T = T
```
```
A |- T: Trait<Assoc = U1>
A |- U1 = U2
---------------------------
A |- (T::Assoc) = U2
```
### cyclic normalization impl
```rust
trait Trait {
type Assoc;
}
impl Trait for i32 {
type Assoc = <u32 as Trait>::Assoc;
}
impl Trait for u32
where
i32: Trait<Assoc = ()>,
{
type Assoc = ();
}
```
- `|- i32: Trait<Assoc = ()>` normalize via impl
- (no where clause, output = `<u32 as Trait>::Assoc`)
- `|- <u32 as Trait>::Assoc = ()` eq-via-normalize
- `|- u32: Trait<Assoc = ()>` normalize via impl <-- productive
- in where-clause `|- i32: Trait<Assoc = ()>` cycle
- `|- () = ()`, ok because "duh"
```rust=
trait OtherTrait {
type OtherAssoc;
}
trait Trait<U> {
type Assoc;
}
impl<U: OtherTrait> Trait<U> for i32 {
type Assoc = <u32 as Trait<U>>::Assoc;
}
impl<U: OtherTrait> Trait<U> for u32
where
i32: Trait<U, Assoc = U::OtherAssoc>,
{
type Assoc = U::OtherAssoc;
}
impl OtherTrait for i32 {
type OtherAssoc = <i32 as Trait<i32>>::Assoc;
} // forall<T> { i32: Trait<i32, Assoc = T> }
```
- `|- i32: Trait<U, Assoc = U::OtherAssoc>` normalize via impl
- (no where clause, output = `<u32 as Trait<U>>::Assoc`)
- `|- <u32 as Trait<U>>::Assoc = U::OtherAssoc` eq-via-normalize
- `|- u32: Trait<U, Assoc = U::OtherAssoc>` normalize via impl
- `|- i32: Trait<Assoc = U::OtherAssoc>` cycle
- `|- i32: Trait<i32, Assoc = T>` normalize-via-impl
- no where clauses
- `|- <u32 as Trait<i32>>::Assoc == T` eq-via-norm
- `|- u32: Trait<i32, Assoc = T>` normalize via impl
- where-clause `|- i32: Trait<i32, Assoc = <i32 as OtherTrait>::OtherAssoc>`, normalize-via-impl
- no where-clauses
- `|- <u32 as Trait<i32>>::Assoc = <i32 as OtherTrait>::OtherAssoc`, eq-via-norm
- `|- u32: Trait<i32, Assoc = <i32 as OtherTrait>::OtherAssoc>`, normalize-via-impl
- `|- i32: Trait<i32, Assoc = ...>`
- normalized type eq `|- <i32 as OtherTrait>::OtherAssoc = T` eq-via-nom
- `|- i32: OtherTrait<OtherAssoc = T>` norm-via-impl
- no where-clauses
- `|- <i32 as Trait<i32>>::Assoc = T`, eq-via-norm
- `|- i32: Trait<i32, Assoc = T>` -- cycle (at the top)
### failure mode 1: normalize to anything
```rust
trait Trait {
type Assoc;
}
impl Trait for u32 {
type Assoc = u32::Assoc;
}
```
With naive rules we can prove...
* `|- forall<T> { u32::Assoc = T }`, via whatevr:
* `|- u32::Assoc = T`, via alias-norm:
* `|- u32: Trait<Assoc = u32::Assoc>`, via normalize-fallback
* `|- u32::Assoc = T`, cycle (coinductively ok?)
...this seems bad.
### associated type bounds cyclic reasoning
```rust
trait Trait {
type Assoc: Copy;
}
impl<T: Trait> Trait for T {
type Assoc = String;
// <T as Trait>::Assoc: Copy
}
impl<T: Trait> Trait for T {
type Assoc = String;
// <T as Trait>::Assoc: Copy
}
fn foo<T: Trait>(y: T::Assoc) -> (T::Assoc, T::Assoc) {
(y, y)
}
fn main() {
foo::<u32>(String::from("hello, world")); // double free!!
// (u32: Trait) but `(String: Copy)` doesn't hold.
}
```
currently proves the following when checking that the impl is correct
(proving that the impl is valid)
- `|- forall<T> (T: Trait => T::Assoc: Copy)`
- `|- T: Trait => T::Assoc: Copy`
could this be
- `|- forall<T> (T: Trait => String: Copy)`
- TODO
we also try to prove `T::Assoc: Copy` in `foo`
we also prove `u32: Trait` when you call `foo` in `main`
```
trait Tr { type Assoc: Tr_a }
substituted impl Tr for T where WC { type Assoc = U1; }
A, (T: Trait<Assoc = U>) |- WC
A |- U1 = U2
A |- (U1: Tr_a)
------------------------------- normalize-via-impl
A |- T: Trait<Assoc = U2>
```
## "implied bounds"
```
A |- (Ty : Tr_a)
trait Tr_a { .. type Assoc : Tr .. }
-------------------------------
A |- (Ty::Assoc: Tr)
```
## "normalization"
```
trait Tr: Tr1 { .. type Assoc : {Tr_a} .. }
impl<{X}> Tr for Ty_t where WC { .. type Assoc = Ty_i .. }
let S be some substitution for {X}
S Ty_t = Ty_s
S Ty_i = Ty_a // <-- new
A, (Ty_s: Tr) |- S WC
A |- (Ty_s: Tr1)
A |- (Ty_a: Tr_a) // <-- new
------------------------------- normalize-impl
A |- (Ty_s: Tr<Assoc = Ty_a>)
```
## "normalization fallback"
```
------------------------------- normalize-fallback
A |- (Ty: Tr<Assoc = Ty::Assoc>)
```
## "equality"
### "reflexive"
```
A |- (Ty_1 = Ty_0)
------------------------------- fallback
A |- (Ty_0 = Ty_1)
```
*this can't be interpreted coinductively...*
### "rigid equality"
```
{Ty_0} = {Ty_1}
------------------------------- rigid
A |- (S<{Ty_0}> = S<{Ty_1}>)
```
### "alias equality, base case"
```
A |- T_0 = T_1
------------------------------- alias-opaque
A |- (T_0::Assoc = T_1::Assoc)
```
### "alias equality, normalized case"
```
A |- (T_0: Tr<Assoc = U0>)
A |- U0 = U1
------------------------------- alias-norm
A |- (T_0::Assoc = U1)
```
## some "fun" examples
from [lcnr's notes](https://hackmd.io/-8p0AHnzSq2VAE6HE_wX-w?view)
### ex0
```rust
trait Trait {
type Assoc;
}
impl Trait for () {
type Assoc = <()>::Assoc;
}
```
With naive rules we can prove...
* `|- forall<T> { <u32>::Assoc = T }`
* `|- u32::Assoc = T`, via alias-norm:
* `|- u32: Trait<Assoc = u32::Assoc>`, via normalize-fallback
* `|- u32::Assoc = T`, cycle (coinductively ok?)
...this seems bad.
## Random links
* https://hackmd.io/cnX6oA28RwOPfRCTeyLw4Q?view
* https://hackmd.io/-8p0AHnzSq2VAE6HE_wX-w?view
#### stuff
```rust
trait CloneIterator {
type Item: Clone;
}
fn foo<T: CloneIterator<Item = U>, U>(u: U) {
u.clone();
}
```