Skip to content

Commit e7952bb

Browse files
committed
Clarifications to traits post
1 parent 3340ee9 commit e7952bb

File tree

1 file changed

+37
-34
lines changed

1 file changed

+37
-34
lines changed

_posts/2015-05-11-traits.md

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,9 @@ impl Hash for i64 {
145145
}
146146
```
147147

148-
Unlike interfaces in languages like Java, C# or Scala, new traits can be implemented
149-
for existing types (as with `Hash` above). **Code in upstream crates can be
150-
adapted to interfaces in downstream crates**.
148+
Unlike interfaces in languages like Java, C# or Scala, **new traits can be
149+
implemented for existing types** (as with `Hash` above). That means abstractions
150+
can be created after-the-fact, and applied to existing libraries.
151151

152152
Unlike inherent methods, trait methods are in scope only when their trait
153153
is. But assuming `Hash` is in scope, you can write `true.hash()`, so
@@ -181,7 +181,13 @@ C++ templates, the compiler will generate *two copies* of the `print_hash`
181181
method to handle the above code, one for each concrete argument type. That in
182182
turn means that the internal call to `t.hash()` -- the point where the
183183
abstraction is actually used -- has zero cost: it will be compiled to a direct,
184-
static call to the relevant implementation.
184+
static call to the relevant implementation:
185+
186+
```rust
187+
// The compiled code:
188+
__print_hash_bool(&true); // invoke specialized bool version directly
189+
__print_hash_i64(&true); // invoke specialized i64 version directly
190+
```
185191

186192
This compilation model isn't so useful for a function like `print_hash`, but
187193
it's *very* useful for more realistic uses of hashing. Suppose we also introduce
@@ -214,6 +220,7 @@ The static compilation model for generics will then yield several benefits:
214220
there is no extra cost dispatching to calls to `hash` and `eq`, as above. It
215221
also means that the optimizer gets to work with the fully concrete code --
216222
that is, from the point of view of the optimizer, *there is no abstraction*.
223+
In particular, static dispatch allows for *inlining* across uses of generics.
217224

218225
Altogether, just as in C++ templates, these aspects of generics mean that you
219226
can write quite high-level abstractions that are *guaranteed* to compile down to
@@ -226,36 +233,6 @@ rather than being checked repeatedly when applied to concrete types. That means
226233
earlier, clearer compilation errors for library authors, and less typechecking
227234
overhead for clients.
228235

229-
#### Conditional trait implementation
230-
231-
Generics also make it possible to implement a trait *conditionally*:
232-
233-
```rust
234-
struct Pair<A, B> {
235-
first: A,
236-
second: B,
237-
}
238-
239-
impl<A: Hash, B: Hash> Hash for Pair<A, B> {
240-
fn hash(&self) -> u64 {
241-
self.first.hash()
242-
.xor(self.second.hash())
243-
.wrapping_mul(0x100000001b3);
244-
}
245-
}
246-
```
247-
248-
Here, the `Pair` type implements `Hash` if, and only if, its components
249-
do. Conditional implementation enables flexible reuse of code: it would be a
250-
shame to have to introduce separate `Pair` types that varied by the traits their
251-
components implement. It's such a common pattern in Rust that there is built-in
252-
support for generating certain kinds of "mechanical" implementations automatically:
253-
254-
```rust
255-
#[derive(Hash)]
256-
struct Pair<A, B> { .. }
257-
```
258-
259236
### Dynamic dispatch
260237

261238
We've seen one compilation model for traits, where all abstraction is compiled
@@ -330,6 +307,32 @@ wind up playing a few other important roles in Rust. Here's a taste:
330307
simply particular traits. You can read more about how this works in
331308
Huon Wilson's [in-depth post][closures] on the topic.
332309

310+
* **Conditional APIs**. Generics make it possible to implement a trait
311+
conditionally:
312+
313+
```rust
314+
struct Pair<A, B> { first: A, second: B }
315+
316+
impl<A: Hash, B: Hash> Hash for Pair<A, B> {
317+
fn hash(&self) -> u64 {
318+
self.first.hash()
319+
.xor(self.second.hash())
320+
.wrapping_mul(0x100000001b3);
321+
}
322+
}
323+
```
324+
325+
Here, the `Pair` type implements `Hash` if, and only if, its components do --
326+
allowing the single `Pair` type to be used in different contexts, while
327+
supporting the largest API available for each context. It's such a common
328+
pattern in Rust that there is built-in support for generating certain kinds of
329+
"mechanical" implementations automatically:
330+
331+
```rust
332+
#[derive(Hash)]
333+
struct Pair<A, B> { .. }
334+
```
335+
333336
* **Extension methods**. Traits can be used to extend an existing type (defined
334337
elsewhere) with new methods, for convenience, similarly to C#'s extension
335338
methods. This falls directly out of the scoping rules for traits: you just

0 commit comments

Comments
 (0)