|
| 1 | +# Inductive cycles |
| 2 | + |
| 3 | +Recursive solving without cycles is easy. Solving with cycles is rather more |
| 4 | +complicated. Before we get into the details of the implementation, |
| 5 | +let's talk a bit about what behavior we actually *expect* in the face |
| 6 | +of possible cycles. |
| 7 | + |
| 8 | +## Inductive cycles |
| 9 | + |
| 10 | +By default, Rust trait solving is **inductive**. What that means is that, roughly |
| 11 | +speaking, you have to prove something is true without any cycles (i.e., you |
| 12 | +can't say "it's true because it's true"!). |
| 13 | + |
| 14 | +For our purpose, a "cycle" means that, in the course of proving some canonical |
| 15 | +goal G, we had to prove that same goal G again. |
| 16 | + |
| 17 | +Consider this Rust program: |
| 18 | + |
| 19 | +```rust |
| 20 | +trait A { } |
| 21 | +impl<T: A> A for Vec<T> { } |
| 22 | +impl A for u32 { } |
| 23 | +``` |
| 24 | + |
| 25 | +Whether or not we hit a cycle will depend on the goal we are trying |
| 26 | +to solve. If for example we are trying to prove `Implemented(Vec<u32>: A)`, |
| 27 | +then we don't hit any cycle: |
| 28 | + |
| 29 | +* `Implemented(Vec<u32>: A) :- Implemented(u32: A)` // from the first impl |
| 30 | + * `Implemented(u32: A)` // from the second impl |
| 31 | + |
| 32 | +But what if we are trying to prove `Implemented(?X: A)`? This is a bit |
| 33 | +more interesting. Because we don't know what `?X` is, both impls are |
| 34 | +actually potentially applicable, so we wind up with two ways to |
| 35 | +prove our goal. We will try them out one after the other. |
| 36 | + |
| 37 | +One possible execution might be: |
| 38 | + |
| 39 | +* Prove `Implemented(?X: A)` |
| 40 | + * we find the program clause `forall<T> { Implemented(Vec<T>: A) :- Implemented(T: A) }` from the first impl |
| 41 | + * we create the variable `?Y` to represent `T` and unify `?X = Vec<?Y>`. |
| 42 | + * after unification, we have the subgoal `Implemented(?Y: A)` |
| 43 | + * when we go to recursively prove this impl, however, we find that it is already on the stack |
| 44 | + * this is because the [canonical form] of `Implemented(?X: A)` and `Implemented(?Y: A)` is the same |
| 45 | + |
| 46 | +[canonical form]: ../canonical_queries.md |
| 47 | + |
| 48 | +## What happens if we treat inductive cycles as errors? |
| 49 | + |
| 50 | +So, what do we do when we hit an inductive cycle? Given that we told you that an |
| 51 | +inductive proof cannot contain cycles, you might imagine that we can just treat |
| 52 | +such a cycle as an error. But this won't give us the correct result. |
| 53 | + |
| 54 | +Consider our previous example. If we just treat that cycle as an error, then we |
| 55 | +will conclude that the impl for `Vec<T>` doesn't apply to `?X: A`, and we'll |
| 56 | +proceed to try the impl for `u32`. This will let us reason that `?X: A` is |
| 57 | +provable if `?X = u32`. This is, in fact, correct: `?X = u32` *is* a possible |
| 58 | +answer. The problem is, it's not the only one! |
| 59 | + |
| 60 | +In fact, `Implemented(?X: A)` has an **infinite** number of answers. It is true |
| 61 | +for `?X = u32`. It is true for `?X = Vec<u32>`. It is also true for |
| 62 | +`Vec<Vec<u32>>` and `Vec<Vec<Vec<u32>>>` and so on. |
| 63 | + |
| 64 | +Given this, the correct result for our query is actually "ambiguous" -- in |
| 65 | +particular, there is no unique substitution that we can give that would make the |
| 66 | +query provable. |
| 67 | + |
| 68 | +## How we solve cycles: loop and try again |
| 69 | + |
| 70 | +The way we actually handle cycles is by iterating until we reach a fixed point |
| 71 | +(or ambiguity). We start out by assuming that all cycles are errors and we try |
| 72 | +to find some solution S. If we succeed, then we can do a loop and iterate again |
| 73 | +-- this time, for each cycle, we assume the result is S. This may yield some new |
| 74 | +solution, S1. The key point here is that we now have **two possible solutions** |
| 75 | +to the same goal, S and S1. This implies two possibilities: |
| 76 | + |
| 77 | +* If S == S1, then in fact there is a unique solution, so we can return `Provable(S)`. |
| 78 | +* If S != S1, then we know there are two solutions, which means that there is |
| 79 | + not one unique solution, and hence the correct result is **ambiguous**, |
| 80 | + and in fact we can just stop and return right now. |
| 81 | + |
| 82 | +This technique is very similar to the traditional Prolog technique of handling |
| 83 | +cycles, which is called **tabling**. The difference between our solution and |
| 84 | +table is that we are always looking for a unique solution, whereas Prolog (like |
| 85 | +the [SLG solver]) tries to enumerate all solutions (i.e., in prolog, solving a |
| 86 | +goal is not a function but an iterator that yields solutions, and hence it would |
| 87 | +yield up S first, and then S1, and then any further answers we might get). |
| 88 | + |
| 89 | +[SLG solver]: ../engine.md |
| 90 | + |
| 91 | +Intuitively, what is happening here is that we're building bigger and bigger |
| 92 | +"proof trees" (i.e., trees of impl applications). In the first iteration, where |
| 93 | +we assumed that all recursive calls were errors, we would find exactly one |
| 94 | +solution, `u32: A` -- this is the root tree. In the next iteration, we can use |
| 95 | +this result to build a tree for `Vec<u32>: A` and so forth. |
| 96 | + |
| 97 | +## Inductive cycles with no base case |
| 98 | + |
| 99 | +It is interesting to look at what happens without the base case. Consider this |
| 100 | +program: |
| 101 | + |
| 102 | +```rust |
| 103 | +trait B { } |
| 104 | +impl<T: B> B for Vec<T> { } |
| 105 | +``` |
| 106 | + |
| 107 | +In this case, there is no base case -- this means that in fact there are no |
| 108 | +solutions at all to the query `?X: B`. The reason is that the only type that |
| 109 | +could match would be a type of infinite size like `Vec<Vec<Vec<...>>>: B`, where |
| 110 | +the chain of `Vec` never terminates. |
| 111 | + |
| 112 | +In our solver, this will work out just fine. We will wind up recursing |
| 113 | +and encountering a cycle. This will be treated as an error in the first |
| 114 | +iteration -- and then, at the end, we'll still have an error. This means |
| 115 | +that we've reached a fixed point, and we can stop. |
| 116 | + |
| 117 | + |
| 118 | +## Inductive cycles: when do we ever terminate |
| 119 | + |
| 120 | +You might be wondering whether there are any examples of inductive cycles that |
| 121 | +actually terminate successfully and without ambiguity. In fact, there are very |
| 122 | +few, but you can construct an example like this: |
| 123 | + |
| 124 | +```rust |
| 125 | +trait C { } |
| 126 | +impl<T: C + D> C for Vec<T> { } |
| 127 | +impl C for u32 { } |
| 128 | + |
| 129 | +trait D { } |
| 130 | +``` |
| 131 | + |
| 132 | +In this case, the only valid result of `Implemented(?X: C)` is `?X = u32`. It can't |
| 133 | +be `Vec<u32>` because `Implemented(u32: D)` is not true. |
| 134 | + |
| 135 | +How does this work out with the recursive solver? In the first iteration, |
| 136 | +we wind up with `?X = u32`, but we do encounter a cycle: |
| 137 | + |
| 138 | +* proving `Implemented(?X: C)` has two possibilities... |
| 139 | + * `?X = Vec<?Y>` and `Implemented(?Y: C)`, which is a cycle (error, at least in this iteration) |
| 140 | + * `?X = u32`, succeeds |
| 141 | + |
| 142 | +So then we try the next iteration: |
| 143 | + |
| 144 | +* proving `Implemented(?X: C)` has two possibilities... |
| 145 | + * `?X = Vec<?Y>` and `Implemented(?Y: C)`, which is a cycle, so we use our previous result of `?Y = u32` |
| 146 | + * we then have to prove `Implemented(u32: D)`, which fails |
| 147 | + * `?X = u32`, succeeds |
0 commit comments