]>
Commit | Line | Data |
---|---|---|
a1dfa0c6 XL |
1 | # Goals and clauses |
2 | ||
6a06907d XL |
3 | <!-- toc --> |
4 | ||
a1dfa0c6 XL |
5 | In logic programming terms, a **goal** is something that you must |
6 | prove and a **clause** is something that you know is true. As | |
7 | described in the [lowering to logic](./lowering-to-logic.html) | |
8 | chapter, Rust's trait solver is based on an extension of hereditary | |
9 | harrop (HH) clauses, which extend traditional Prolog Horn clauses with | |
10 | a few new superpowers. | |
11 | ||
12 | ## Goals and clauses meta structure | |
13 | ||
14 | In Rust's solver, **goals** and **clauses** have the following forms | |
15 | (note that the two definitions reference one another): | |
16 | ||
17 | ```text | |
18 | Goal = DomainGoal // defined in the section below | |
19 | | Goal && Goal | |
20 | | Goal || Goal | |
21 | | exists<K> { Goal } // existential quantification | |
22 | | forall<K> { Goal } // universal quantification | |
23 | | if (Clause) { Goal } // implication | |
24 | | true // something that's trivially true | |
25 | | ambiguous // something that's never provable | |
26 | ||
27 | Clause = DomainGoal | |
28 | | Clause :- Goal // if can prove Goal, then Clause is true | |
29 | | Clause && Clause | |
30 | | forall<K> { Clause } | |
31 | ||
32 | K = <type> // a "kind" | |
33 | | <lifetime> | |
34 | ``` | |
35 | ||
36 | The proof procedure for these sorts of goals is actually quite | |
37 | straightforward. Essentially, it's a form of depth-first search. The | |
38 | paper | |
39 | ["A Proof Procedure for the Logic of Hereditary Harrop Formulas"][pphhf] | |
40 | gives the details. | |
41 | ||
42 | In terms of code, these types are defined in | |
6a06907d | 43 | [`rustc_middle/src/traits/mod.rs`][traits_mod] in rustc, and in |
a1dfa0c6 XL |
44 | [`chalk-ir/src/lib.rs`][chalk_ir] in chalk. |
45 | ||
6a06907d XL |
46 | [pphhf]: https://rust-lang.github.io/chalk/book/bibliography.html#pphhf |
47 | [traits_mod]: https://github.com/rust-lang/rust/blob/master/compiler/rustc_middle/src/traits/mod.rs | |
ba9703b0 | 48 | [chalk_ir]: https://github.com/rust-lang/chalk/blob/master/chalk-ir/src/lib.rs |
a1dfa0c6 XL |
49 | |
50 | <a name="domain-goals"></a> | |
51 | ||
52 | ## Domain goals | |
53 | ||
54 | *Domain goals* are the atoms of the trait logic. As can be seen in the | |
55 | definitions given above, general goals basically consist in a combination of | |
56 | domain goals. | |
57 | ||
416331ca | 58 | Moreover, flattening a bit the definition of clauses given previously, one can |
a1dfa0c6 XL |
59 | see that clauses are always of the form: |
60 | ```text | |
61 | forall<K1, ..., Kn> { DomainGoal :- Goal } | |
62 | ``` | |
63 | hence domain goals are in fact clauses' LHS. That is, at the most granular level, | |
64 | domain goals are what the trait solver will end up trying to prove. | |
65 | ||
66 | <a name="trait-ref"></a> | |
67 | ||
68 | To define the set of domain goals in our system, we need to first | |
69 | introduce a few simple formulations. A **trait reference** consists of | |
70 | the name of a trait along with a suitable set of inputs P0..Pn: | |
71 | ||
72 | ```text | |
73 | TraitRef = P0: TraitName<P1..Pn> | |
74 | ``` | |
75 | ||
76 | So, for example, `u32: Display` is a trait reference, as is `Vec<T>: | |
77 | IntoIterator`. Note that Rust surface syntax also permits some extra | |
78 | things, like associated type bindings (`Vec<T>: IntoIterator<Item = | |
79 | T>`), that are not part of a trait reference. | |
80 | ||
81 | <a name="projection"></a> | |
82 | ||
83 | A **projection** consists of an associated item reference along with | |
84 | its inputs P0..Pm: | |
85 | ||
86 | ```text | |
87 | Projection = <P0 as TraitName<P1..Pn>>::AssocItem<Pn+1..Pm> | |
88 | ``` | |
89 | ||
90 | Given these, we can define a `DomainGoal` as follows: | |
91 | ||
92 | ```text | |
93 | DomainGoal = Holds(WhereClause) | |
94 | | FromEnv(TraitRef) | |
95 | | FromEnv(Type) | |
96 | | WellFormed(TraitRef) | |
97 | | WellFormed(Type) | |
98 | | Normalize(Projection -> Type) | |
99 | ||
100 | WhereClause = Implemented(TraitRef) | |
101 | | ProjectionEq(Projection = Type) | |
102 | | Outlives(Type: Region) | |
103 | | Outlives(Region: Region) | |
104 | ``` | |
105 | ||
106 | `WhereClause` refers to a `where` clause that a Rust user would actually be able | |
107 | to write in a Rust program. This abstraction exists only as a convenience as we | |
108 | sometimes want to only deal with domain goals that are effectively writable in | |
109 | Rust. | |
110 | ||
111 | Let's break down each one of these, one-by-one. | |
112 | ||
113 | #### Implemented(TraitRef) | |
114 | e.g. `Implemented(i32: Copy)` | |
115 | ||
116 | True if the given trait is implemented for the given input types and lifetimes. | |
117 | ||
118 | #### ProjectionEq(Projection = Type) | |
119 | e.g. `ProjectionEq<T as Iterator>::Item = u8` | |
120 | ||
121 | The given associated type `Projection` is equal to `Type`; this can be proved | |
122 | with either normalization or using placeholder associated types. See | |
6a06907d | 123 | [the section on associated types in Chalk Book][at]. |
a1dfa0c6 XL |
124 | |
125 | #### Normalize(Projection -> Type) | |
126 | e.g. `ProjectionEq<T as Iterator>::Item -> u8` | |
127 | ||
128 | The given associated type `Projection` can be [normalized][n] to `Type`. | |
129 | ||
130 | As discussed in [the section on associated | |
6a06907d | 131 | types in Chalk Book][at], `Normalize` implies `ProjectionEq`, |
a1dfa0c6 XL |
132 | but not vice versa. In general, proving `Normalize(<T as Trait>::Item -> U)` |
133 | also requires proving `Implemented(T: Trait)`. | |
134 | ||
6a06907d XL |
135 | [n]: https://rust-lang.github.io/chalk/book/clauses/type_equality.html#normalize |
136 | [at]: https://rust-lang.github.io/chalk/book/clauses/type_equality.html | |
a1dfa0c6 XL |
137 | |
138 | #### FromEnv(TraitRef) | |
139 | e.g. `FromEnv(Self: Add<i32>)` | |
140 | ||
141 | True if the inner `TraitRef` is *assumed* to be true, | |
142 | that is, if it can be derived from the in-scope where clauses. | |
143 | ||
144 | For example, given the following function: | |
145 | ||
146 | ```rust | |
147 | fn loud_clone<T: Clone>(stuff: &T) -> T { | |
148 | println!("cloning!"); | |
149 | stuff.clone() | |
150 | } | |
151 | ``` | |
152 | ||
153 | Inside the body of our function, we would have `FromEnv(T: Clone)`. In-scope | |
154 | where clauses nest, so a function body inside an impl body inherits the | |
155 | impl body's where clauses, too. | |
156 | ||
157 | This and the next rule are used to implement [implied bounds]. As we'll see | |
158 | in the section on lowering, `FromEnv(TraitRef)` implies `Implemented(TraitRef)`, | |
159 | but not vice versa. This distinction is crucial to implied bounds. | |
160 | ||
161 | #### FromEnv(Type) | |
162 | e.g. `FromEnv(HashSet<K>)` | |
163 | ||
164 | True if the inner `Type` is *assumed* to be well-formed, that is, if it is an | |
165 | input type of a function or an impl. | |
166 | ||
167 | For example, given the following code: | |
168 | ||
169 | ```rust,ignore | |
170 | struct HashSet<K> where K: Hash { ... } | |
171 | ||
172 | fn loud_insert<K>(set: &mut HashSet<K>, item: K) { | |
173 | println!("inserting!"); | |
174 | set.insert(item); | |
175 | } | |
176 | ``` | |
177 | ||
178 | `HashSet<K>` is an input type of the `loud_insert` function. Hence, we assume it | |
179 | to be well-formed, so we would have `FromEnv(HashSet<K>)` inside the body of our | |
180 | function. As we'll see in the section on lowering, `FromEnv(HashSet<K>)` implies | |
181 | `Implemented(K: Hash)` because the | |
182 | `HashSet` declaration was written with a `K: Hash` where clause. Hence, we don't | |
183 | need to repeat that bound on the `loud_insert` function: we rather automatically | |
184 | assume that it is true. | |
185 | ||
186 | #### WellFormed(Item) | |
187 | These goals imply that the given item is *well-formed*. | |
188 | ||
189 | We can talk about different types of items being well-formed: | |
190 | ||
191 | * *Types*, like `WellFormed(Vec<i32>)`, which is true in Rust, or | |
192 | `WellFormed(Vec<str>)`, which is not (because `str` is not `Sized`.) | |
193 | ||
194 | * *TraitRefs*, like `WellFormed(Vec<i32>: Clone)`. | |
195 | ||
196 | Well-formedness is important to [implied bounds]. In particular, the reason | |
197 | it is okay to assume `FromEnv(T: Clone)` in the `loud_clone` example is that we | |
198 | _also_ verify `WellFormed(T: Clone)` for each call site of `loud_clone`. | |
199 | Similarly, it is okay to assume `FromEnv(HashSet<K>)` in the `loud_insert` | |
200 | example because we will verify `WellFormed(HashSet<K>)` for each call site of | |
6a06907d | 201 | `loud_insert`. |
a1dfa0c6 XL |
202 | |
203 | #### Outlives(Type: Region), Outlives(Region: Region) | |
204 | e.g. `Outlives(&'a str: 'b)`, `Outlives('a: 'static)` | |
205 | ||
206 | True if the given type or region on the left outlives the right-hand region. | |
207 | ||
208 | <a name="coinductive"></a> | |
209 | ||
210 | ## Coinductive goals | |
211 | ||
212 | Most goals in our system are "inductive". In an inductive goal, | |
213 | circular reasoning is disallowed. Consider this example clause: | |
214 | ||
215 | ```text | |
216 | Implemented(Foo: Bar) :- | |
217 | Implemented(Foo: Bar). | |
218 | ``` | |
219 | ||
220 | Considered inductively, this clause is useless: if we are trying to | |
221 | prove `Implemented(Foo: Bar)`, we would then recursively have to prove | |
222 | `Implemented(Foo: Bar)`, and that cycle would continue ad infinitum | |
223 | (the trait solver will terminate here, it would just consider that | |
224 | `Implemented(Foo: Bar)` is not known to be true). | |
225 | ||
226 | However, some goals are *co-inductive*. Simply put, this means that | |
227 | cycles are OK. So, if `Bar` were a co-inductive trait, then the rule | |
228 | above would be perfectly valid, and it would indicate that | |
229 | `Implemented(Foo: Bar)` is true. | |
230 | ||
231 | *Auto traits* are one example in Rust where co-inductive goals are used. | |
232 | Consider the `Send` trait, and imagine that we have this struct: | |
233 | ||
234 | ```rust | |
235 | struct Foo { | |
236 | next: Option<Box<Foo>> | |
237 | } | |
238 | ``` | |
239 | ||
240 | The default rules for auto traits say that `Foo` is `Send` if the | |
241 | types of its fields are `Send`. Therefore, we would have a rule like | |
242 | ||
243 | ```text | |
244 | Implemented(Foo: Send) :- | |
245 | Implemented(Option<Box<Foo>>: Send). | |
246 | ``` | |
247 | ||
248 | As you can probably imagine, proving that `Option<Box<Foo>>: Send` is | |
249 | going to wind up circularly requiring us to prove that `Foo: Send` | |
250 | again. So this would be an example where we wind up in a cycle – but | |
251 | that's ok, we *do* consider `Foo: Send` to hold, even though it | |
252 | references itself. | |
253 | ||
254 | In general, co-inductive traits are used in Rust trait solving when we | |
255 | want to enumerate a fixed set of possibilities. In the case of auto | |
256 | traits, we are enumerating the set of reachable types from a given | |
257 | starting point (i.e., `Foo` can reach values of type | |
258 | `Option<Box<Foo>>`, which implies it can reach values of type | |
259 | `Box<Foo>`, and then of type `Foo`, and then the cycle is complete). | |
260 | ||
261 | In addition to auto traits, `WellFormed` predicates are co-inductive. | |
262 | These are used to achieve a similar "enumerate all the cases" pattern, | |
263 | as described in the section on [implied bounds]. | |
264 | ||
6a06907d | 265 | [implied bounds]: https://rust-lang.github.io/chalk/book/clauses/implied_bounds.html#implied-bounds |
a1dfa0c6 XL |
266 | |
267 | ## Incomplete chapter | |
268 | ||
269 | Some topics yet to be written: | |
270 | ||
271 | - Elaborate on the proof procedure | |
272 | - SLG solving – introduce negative reasoning |