Skip to content

Commit 98ca3b9

Browse files
authored
Write chapter for blanket implementations (#5)
1 parent 1467f95 commit 98ca3b9

File tree

3 files changed

+221
-2
lines changed

3 files changed

+221
-2
lines changed

content/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# Basic Patterns
1212

13-
- [Auto Traits](auto-traits.md)
13+
- [Blanket Implementations](blanket-implementations.md)
1414
- [Impl-side Dependencies](impl-side-dependencies.md)
1515
- [Provider Traits](provider-traits.md)
1616
- [Auto Consumer Impl](auto-consumer-impl.md)

content/auto-traits.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

content/blanket-implementations.md

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Blanket Trait Implementations
2+
3+
In the previous chapter, we have an implementation of `CanGreet` for `Person` that
4+
makes use of `HasName` to retrieve the person's name to be printed.
5+
However, the implementation is _context-specific_ to the `Person` context,
6+
and cannot be reused for other contexts.
7+
8+
Ideally, we want to be able to define _context-generic_ implementations
9+
of `Greet` that works with any context type that also implements `HasName`.
10+
For this, the _blanket trait implementations_ pattern is one basic way which we can use for
11+
defining context-generic implementations:
12+
13+
```rust
14+
trait HasName {
15+
fn name(&self) -> &str;
16+
}
17+
18+
trait CanGreet {
19+
fn greet(&self);
20+
}
21+
22+
impl<Context> CanGreet for Context
23+
where
24+
Context: HasName,
25+
{
26+
fn greet(&self) {
27+
println!("Hello, {}!", self.name());
28+
}
29+
}
30+
```
31+
32+
The above example shows a blanket trait implementation of `CanGreet` for any
33+
`Context` type that implements `HasName`. With that, contexts like `Person`
34+
do not need to explicitly implement `CanGreet`, if they already implement
35+
`HasName`:
36+
37+
```rust
38+
# trait HasName {
39+
# fn name(&self) -> &str;
40+
# }
41+
#
42+
# trait CanGreet {
43+
# fn greet(&self);
44+
# }
45+
#
46+
# impl<Context> CanGreet for Context
47+
# where
48+
# Context: HasName,
49+
# {
50+
# fn greet(&self) {
51+
# println!("Hello, {}!", self.name());
52+
# }
53+
# }
54+
#
55+
struct Person { name: String }
56+
57+
impl HasName for Person {
58+
fn name(&self) -> &str {
59+
&self.name
60+
}
61+
}
62+
63+
let person = Person { name: "Alice".to_owned() };
64+
person.greet();
65+
```
66+
67+
As shown above, we are able to call `person.greet()` without having a context-specific
68+
implementation of `CanGreet` for `Person`.
69+
70+
The use of blanket trait implementation is commonly found in many Rust libraries today.
71+
For example, [`Itertools`](https://docs.rs/itertools/latest/itertools/trait.Itertools.html)
72+
provides a blanket implementation for any context that implements `Iterator`.
73+
Another example is [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html),
74+
which is implemented for any context that implements `Stream`.
75+
76+
## Overriding Blanket Implementations
77+
78+
Traits containing blanket implementation are usually not meant to be implemented manually
79+
by individual contexts. They are usually meant to serve as convenient methods that extends the
80+
functionality of another trait. However, Rust's trait system does _not_ completely prevent us
81+
from overriding the blanket implementation.
82+
83+
Supposed that we have a `VipPerson` context that we want to implement a different way of
84+
greeting the VIP person. We could override the implementation as follows:
85+
86+
```rust
87+
trait HasName {
88+
fn name(&self) -> &str;
89+
}
90+
91+
trait CanGreet {
92+
fn greet(&self);
93+
}
94+
95+
impl<Context> CanGreet for Context
96+
where
97+
Context: HasName,
98+
{
99+
fn greet(&self) {
100+
println!("Hello, {}!", self.name());
101+
}
102+
}
103+
104+
struct VipPerson { name: String, /* other fields */ }
105+
106+
impl CanGreet for VipPerson {
107+
fn greet(&self) {
108+
println!("A warm welcome to you, {}!", self.name);
109+
}
110+
}
111+
```
112+
113+
The example above shows _two_ providers of `CanGreet`. The first provider is
114+
a context-generic provider that we covered previously, but the second provider
115+
is a context-specific provider for the `VipPerson` context.
116+
117+
## Conflicting Implementations
118+
119+
In the previous example, we are able to define a custom provider for `VipPerson`,
120+
but with an important caveat: that `VipPerson` does _not_ implement `HasName`.
121+
If we try to define a custom provider for contexts that already implement `HasName`,
122+
such as for `Person`, the compilation would fail:
123+
124+
```rust,compile_fail
125+
trait HasName {
126+
fn name(&self) -> &str;
127+
}
128+
129+
trait CanGreet {
130+
fn greet(&self);
131+
}
132+
133+
impl<Context> CanGreet for Context
134+
where
135+
Context: HasName,
136+
{
137+
fn greet(&self) {
138+
println!("Hello, {}!", self.name());
139+
}
140+
}
141+
142+
struct Person { name: String }
143+
144+
impl HasName for Person {
145+
fn name(&self) -> &str {
146+
&self.name
147+
}
148+
}
149+
150+
impl CanGreet for Person {
151+
fn greet(&self) {
152+
println!("Hi, {}!", self.name());
153+
}
154+
}
155+
```
156+
157+
If we try to compile the example code above, we would get an error with the message:
158+
159+
```text
160+
conflicting implementations of trait `CanGreet` for type `Person`
161+
```
162+
163+
The reason for the conflict is because Rust trait system requires all types
164+
to have unambigious implementation of any given trait. To see why such requirement
165+
is necessary, consider the following example:
166+
167+
```rust
168+
# trait HasName {
169+
# fn name(&self) -> &str;
170+
# }
171+
#
172+
# trait CanGreet {
173+
# fn greet(&self);
174+
# }
175+
#
176+
# impl<Context> CanGreet for Context
177+
# where
178+
# Context: HasName,
179+
# {
180+
# fn greet(&self) {
181+
# println!("Hello, {}!", self.name());
182+
# }
183+
# }
184+
#
185+
fn call_greet_generically<Context>(context: &Context)
186+
where
187+
Context: HasName,
188+
{
189+
context.greet()
190+
}
191+
```
192+
193+
The example above shows a generic function `call_greet_generically`, which work with
194+
any `Context` that implements `HasName`. Even though it does not require `Context` to
195+
implement `CanGreet`, it nevertheless can call `context.greet()`. This is because with
196+
the guarantee from Rust's trait system, the compiler can always safely use the blanket
197+
implementation of `CanGreet` during compilation.
198+
199+
If Rust were to allow ambiguous override of blanket implementations, such as what we
200+
tried with `Person`, it would have resulted in inconsistencies in the compiled code,
201+
depending on whether it is known that the generic type is instantiated to `Person`.
202+
203+
Note that in general, it is not always possible to know locally about the concrete type
204+
that is instantiated in a generic code. This is because a generic function like
205+
`call_greet_generically` can once again be called by other generic code. This is why
206+
even though there are unstable Rust features such as
207+
[_trait specialization_](https://rust-lang.github.io/rfcs/1210-impl-specialization.html),
208+
such feature has to be carefully considered to ensure that no inconsistency can arise.
209+
210+
## Limitations of Blanket Implementations
211+
212+
Due to potential conflicting implementations, the use of blanket implementations offer
213+
limited customizability, in case if a context wants to have a different implementation.
214+
Although a context many define its own context-specific provider to override the blanket
215+
provider, it would face other limitations such as not being able to implement other traits
216+
that may cause a conflict.
217+
218+
In practice, we consider that blanket implementations allow for _singular context-generic provider_
219+
to be defined. In future chapters, we will look at how to relax the singular constraint,
220+
to make it possible to allow _multiple_ context-generic or context-specific providers to co-exist.

0 commit comments

Comments
 (0)