Skip to content

Commit 7f14018

Browse files
committed
RFC: extension trait conventions
1 parent 9bf2874 commit 7f14018

File tree

1 file changed

+139
-0
lines changed

1 file changed

+139
-0
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
- Start Date: 2014-11-05
2+
- RFC PR: (leave this empty)
3+
- Rust Issue: (leave this empty)
4+
5+
# Summary
6+
7+
This is a conventions RFC establishing a definition and naming
8+
convention for *extension traits*: `FooExt`.
9+
10+
# Motivation
11+
12+
This RFC is part of the ongoing API conventions and stabilization
13+
effort.
14+
15+
Extension traits are a programming pattern that makes it
16+
possible to add methods to an existing type outside of the crate
17+
defining that type. While they should be used sparingly, the new
18+
[object safety rules](https://github.com/rust-lang/rfcs/pull/255) have
19+
increased the need for this kind of trait, and hence the need for a
20+
clear convention.
21+
22+
# Detailed design
23+
24+
## What is an extension trait?
25+
26+
Rust currently allows inherent methods to be defined on a type only in
27+
the crate where that type is defined. But it is often the case that
28+
clients of a type would like to incorporate additional methods to
29+
it. Extension traits are a pattern for doing so:
30+
31+
```rust
32+
extern crate foo;
33+
use foo::Foo;
34+
35+
trait FooExt {
36+
fn bar(&self);
37+
}
38+
39+
impl FooExt for Foo {
40+
fn bar(&self) { .. }
41+
}
42+
```
43+
44+
By defining a new trait, a client of `foo` can add new methods to `Foo`.
45+
46+
Of course, adding methods via a new trait happens all the time. What
47+
makes it an *extension* trait is that the trait is not designed for
48+
*generic* use, but only as way of adding methods to a specific type or
49+
family of types.
50+
51+
This is of course a somewhat subjective distinction. Whenever
52+
designing an extension trait, one should consider whether the trait
53+
could be used in some more generic way. If so, the trait should be
54+
named and exported as if it were just a "normal" trait. But traits
55+
offering groups of methods that really only make sense in the context
56+
of some particular type(s) are true extension traits.
57+
58+
The new
59+
[object safety rules](https://github.com/rust-lang/rfcs/pull/255) mean
60+
that a trait can only be used for trait objects if *all* of its
61+
methods are usable; put differently, it ensures that for "object safe
62+
traits" there is always a canonical way to implement `Trait` for
63+
`Box<Trait>`. To deal with this new rule, it is sometimes necessary to
64+
break traits apart into an object safe trait and extension traits:
65+
66+
```rust
67+
// The core, object-safe trait
68+
trait Iterator<A> {
69+
fn next(&mut self) -> Option<A>;
70+
}
71+
72+
// The extension trait offering object-unsafe methods
73+
trait IteratorExt<A>: Iterator<A> {
74+
fn chain<U: Iterator<A>>(self, other: U) -> Chain<Self, U> { ... }
75+
fn zip<B, U: Iterator<B>>(self, other: U) -> Zip<Self, U> { ... }
76+
fn map<B>(self, f: |A| -> B) -> Map<'r, A, B, Self> { ... }
77+
...
78+
}
79+
80+
// A blanket impl
81+
impl<A, I> IteratorExt<A> for I where I: Iterator<A> {
82+
...
83+
}
84+
```
85+
86+
Note that, although this split-up definition is somewhat more complex,
87+
it is also more flexible: because `Box<Iterator<A>>` will implement
88+
`Iterator<A>`, you can now use *all* of the adapter methods provided
89+
in `IteratorExt` on trait objects, even though they are not object
90+
safe.
91+
92+
## The convention
93+
94+
The proposed convention is, first of all, to (1) prefer adding default
95+
methods to existing traits or (2) prefer generically useful traits to
96+
extension traits whenever feasible.
97+
98+
For true extension traits, there should be a clear type or trait that
99+
they are extending. The extension trait should be called `FooExt`
100+
where `Foo` is that type or trait.
101+
102+
In some cases, the extension trait only applies conditionally. For
103+
example, `AdditiveIterator` is an extension trait currently in `std`
104+
that applies to iterators over numeric types. These extension traits
105+
should follow a similar convention, putting together the type/trait
106+
name and the qualifications, together with the `Ext` suffix:
107+
`IteratorAddExt`.
108+
109+
## Future proofing
110+
111+
In the future, the need for many of these extension traits may
112+
disappear as other languages features are added. For example,
113+
method-level `where` clauses will eliminate the need for
114+
`AdditiveIterator`. And allowing inherent `impl`s like `impl<T: Trait>
115+
T { .. }` for the crate defining `Trait` would eliminate even more.
116+
117+
However, there will always be *some* use of extension traits, and we
118+
need to stabilize the 1.0 libraries prior to these language features
119+
landing. So this is the proposed convention for now, and in the future
120+
it may be possible to deprecate some of the resulting traits.
121+
122+
# Alternatives
123+
124+
It seems clear that we need *some* convention here. Other possible
125+
suffixes would be `Util` or `Methods`, but `Ext` is both shorter and
126+
connects to the name of the pattern.
127+
128+
# Drawbacks
129+
130+
In general, extension traits tend to require additional imports --
131+
especially painful when dealing with object safety. However, this is
132+
more to do with the language as it stands today than with the
133+
conventions in this RFC.
134+
135+
In the long run, one way to mitigate these problems would be to add a
136+
general "prelude" facility for external libraries that makes it
137+
possible to globally import a small set of names from the crate. Some
138+
early investigations of such a feature are already under way, but are
139+
outside the scope of this RFC.

0 commit comments

Comments
 (0)