Skip to content

Commit 963d624

Browse files
committed
add section on overlap checks
1 parent 5ec72a2 commit 963d624

File tree

4 files changed

+77
-2
lines changed

4 files changed

+77
-2
lines changed

src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
- [Caching subtleties](./traits/caching.md)
138138
- [Implied bounds](./traits/implied-bounds.md)
139139
- [Specialization](./traits/specialization.md)
140+
- [Overlap checks](./traits/overlap.md)
140141
- [Chalk-based trait solving](./traits/chalk.md)
141142
- [Lowering to logic](./traits/lowering-to-logic.md)
142143
- [Goals and clauses](./traits/goals-and-clauses.md)

src/solve/invariants.md

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ in the trait solver
113113

114114
#### The type system is complete during the implicit negative overlap check in coherence ✅
115115

116+
For more on overlap checking: [./overlap.md]
117+
116118
During the implicit negative overlap check in coherence we must never return *error* for
117119
goals which can be proven. This would allow for overlapping impls with potentially different
118120
associated items, breaking a bunch of other invariants.

src/traits/overlap.md

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Overlap checks
2+
3+
As part of checking items (specifically: structs, enums, traits, unions),
4+
the compiler checks whether its impl blocks overlap, for example because they define the same functions.
5+
This is an example an overlap check.
6+
The same overlap check is done when constructing a [specialization graph](./specialization.md).
7+
Here, traits implementations could overlap because of a conflicting blanket implementation overlapping with some specific implementation.
8+
9+
The overlap check always compares two impls.
10+
In the case of inherent impl blocks, this means that for small n,
11+
rustc quite literally compares each impl to each other impl block in an n^2 loop
12+
(see `fn check_item` in coherence/inherent_impls_overlap.rs).
13+
14+
Overlapping is sometimes partially allowed:
15+
1. for maker traits
16+
2. under [specialization](./specialization.md)
17+
18+
but normally isn't.
19+
20+
The overlap check has various modes (see [`OverlapMode`]).
21+
Importantly, there's the explicit negative impl check, and the implicit negative impl check.
22+
Both try to apply negative reasoning to prove that an overlap is definitely impossible.
23+
24+
[`OverlapMode`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/traits/specialization_graph/enum.OverlapMode.html
25+
26+
## The explicit negative impl check
27+
28+
This check is done in [`impl_intersection_has_negative_obligation`].
29+
30+
This check tries to find a negative trait implementation.
31+
For example:
32+
33+
```rust
34+
struct MyCustomBox<T: ?Sized>(Box<T>)
35+
36+
// both in your own crate
37+
impl From<&str> for MyCustomBox<dyn Error> {}
38+
impl<E> From<E> for MyCustomBox<dyn Error> where E: Error {}
39+
```
40+
41+
In this example, we'd get:
42+
`MyCustomBox<dyn Error>: From<&str>` and `MyCustomBox<dyn Error>: From<?E>`, giving `?E = &str`.
43+
44+
And thus, these two implementations would overlap.
45+
However, libstd provides `&str: !Error`, and therefore guarantees that there
46+
will never be a positive implementation of `&str: Error`, and thus there is no overlap.
47+
48+
Note that for this kind of negative impl check, we must have explicit negative implementations provided.
49+
This is not currently stable.
50+
51+
[`impl_intersection_has_negative_obligation`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_trait_selection/traits/coherence/fn.impl_intersection_has_impossible_obligation.htmlhttps://doc.rust-lang.org/beta/nightly-rustc/rustc_trait_selection/traits/coherence/fn.impl_intersection_has_negative_obligation.html
52+
53+
## The implicit negative impl check
54+
55+
This check is done in [`impl_intersection_has_impossible_obligation`],
56+
and does not rely on negative trait implementations and is stable.
57+
58+
Let's say there's a
59+
```rust
60+
impl From<MyLocalType> for Box<dyn Error> {} // in your own crate
61+
impl<E> From<E> for Box<dyn Error> where E: Error {} // in std
62+
```
63+
64+
This would give: `Box<dyn Error>: From<MyLocalType>`, and `Box<dyn Error>: From<?E>`,
65+
giving `?E = MyLocalType`.
66+
67+
In your crate there's no `MyLocalType: Error`, downstream crates cannot implement `Error` (a remote trait) for `MyLocalType` (a remote type).
68+
Therefore, these two impls do not overlap.
69+
Importantly, this works even if there isn't a `impl !Error for MyLocalType`.
70+
71+
[`impl_intersection_has_impossible_obligation`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_trait_selection/traits/coherence/fn.impl_intersection_has_impossible_obligation.html
72+

src/traits/specialization.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
Defined in the `specialize` module.
66

77
The basic strategy is to build up a *specialization graph* during
8-
coherence checking (recall that coherence checking looks for overlapping
9-
impls). Insertion into the graph locates the right place
8+
coherence checking (coherence checking looks for [overlapping impls](./overlap.md)).
9+
Insertion into the graph locates the right place
1010
to put an impl in the specialization hierarchy; if there is no right
1111
place (due to partial overlap but no containment), you get an overlap
1212
error. Specialization is consulted when selecting an impl (of course),

0 commit comments

Comments
 (0)