Skip to content

Commit c2cf5e6

Browse files
committed
Deref polymorphism anti-pattern
1 parent a06ca32 commit c2cf5e6

File tree

5 files changed

+136
-3
lines changed

5 files changed

+136
-3
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ language.
1919
* [Finalisation in destructors](idioms/dtor-finally.md)
2020
* TODO interior mutability - UnsafeCell, Cell, RefCell
2121
* TODO treating Option like a list
22+
* TODO `Default` trait
2223

2324
### Design patterns
2425

@@ -40,14 +41,15 @@ language.
4041
* TODO 'shadow' borrowed version of struct - e.g., double buffering, Niko's parser generator
4142
* TODO composition of structs to please the borrow checker
4243
* TODO `Error` traits and `Result` forwarding
44+
* TODO graphs
4345

4446

4547

4648
### Anti-patterns
4749

4850
* TODO thread + catch_panic for exceptions
4951
* TODO Clone to satisfy the borrow checker
50-
* TODO Deref polymorphism
52+
* [Deref polymorphism](anti_patterns/deref.md)
5153
* TODO Matching all fields of a struct (back compat)
5254
* TODO wildcard matches
5355
* TODO taking an enum rather than having multiple functions

anti_patterns/deref.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# `Deref` polymorphism
2+
3+
## Description
4+
5+
Abuse the `Deref` trait to emulate inheritance between structs, and thus reuse
6+
methods.
7+
8+
9+
## Example
10+
11+
Sometimes we want to emulate the following common pattern from OO languages such
12+
as Java:
13+
14+
```java
15+
class Foo {
16+
void m() { ... }
17+
}
18+
19+
class Bar extends Foo {}
20+
21+
public static void main(String[] args) {
22+
Bar b = new Bar();
23+
b.m();
24+
}
25+
```
26+
27+
We can use the deref polymorphism anti-pattern to do so:
28+
29+
```rust
30+
struct Foo {}
31+
32+
impl Foo {
33+
fn m(&self) { ... }
34+
}
35+
36+
struct Bar {
37+
f: Foo
38+
}
39+
40+
impl Deref for Bar {
41+
type Target = Foo;
42+
fn deref(&self) -> &Foo {
43+
&self.f
44+
}
45+
}
46+
47+
fn main() {
48+
let b = Bar { Foo {} };
49+
b.m();
50+
}
51+
```
52+
53+
There is no struct inheritance in Rust. Instead we use composition and include
54+
an instance of `Foo` in `Bar` (since the field is a value, it is stored inline,
55+
so if there were fields, they would have the same layout in memory as the Java
56+
version (probably, you should use `#[repr(C)]` if you want to be sure)).
57+
58+
In order to make the method call work we implement `Deref` for `Bar` with `Foo`
59+
as the target (returning the embedded `Foo` field). That means that when we
60+
dereference a `Foo` (for example, using `*`) then we will get a `Bar`. That is
61+
pretty weird. Dereferencing usually gives a `T` from a reference to `T`, here we
62+
have two unrelated types. However, since the dot operator does implicit
63+
dereferencing, it means that the method call will search for methods on `Foo` as
64+
well as `Bar`.
65+
66+
67+
## Advantages
68+
69+
You save a little boilerplate, e.g.,
70+
71+
```rust
72+
impl Bar {
73+
fn m(&self) {
74+
self.f.m()
75+
}
76+
}
77+
```
78+
79+
80+
## Disadvantages
81+
82+
Most importantly this is a surprising idiom - future programmers reading this in
83+
code will not expect this to happen. That's because we are abusing the `Deref`
84+
trait rather than using it as intended (and documented, etc.). It's also because
85+
the mechanism here is completely implicit.
86+
87+
This pattern does not introduce subtyping between `Foo` and `Bar` like
88+
inheritance in Java or C++ does. Furthermore, traits implemented by `Foo` are
89+
not automatically implemented for `Bar`, so this pattern interacts badly with
90+
bounds checking and thus generic programming.
91+
92+
Using this pattern gives subtly different semantics from most OO languages with
93+
regards to `self`. Usually it remains a reference to the sub-class, with this
94+
pattern it will be the 'class' where the method is defined.
95+
96+
Finally, this pattern only supports single inheritance, and has no notion of
97+
interfaces, class-based privacy, or other inheritance-related features. So, it
98+
gives an experience that will be subtly surprising to programmers used to Java
99+
inheritance, etc.
100+
101+
102+
## Discussion
103+
104+
There is no one good alternative. Depending on the exact circumstances it might
105+
be better to re-implement using traits or to write out the facade methods to
106+
dispatch to `Foo` manually. We do intend to add a mechanism for inheritance
107+
similar to this to Rust, but it is likely to be some time before it reaches
108+
stable Rust. See these [blog](http://aturon.github.io/blog/2015/09/18/reuse/)
109+
[posts](http://smallcultfollowing.com/babysteps/blog/2015/10/08/virtual-structs-part-4-extended-enums-and-thin-traits/)
110+
and this [RFC issue](https://github.com/rust-lang/rfcs/issues/349) for more details.
111+
112+
The `Deref` trait is designed for the implementation of custom pointer types.
113+
The intention is that it will take a pointer-to-`T` to a `T`, not convert
114+
between different types. It is a shame that this isn't (probably cannot be)
115+
enforced by the trait definition.
116+
117+
Rust tries to strike a careful balance between explicit and implicit mechanisms,
118+
favouring explicit conversions between types. Automatic dereferencing in the dot
119+
operator is a case where the ergonomics strongly favour an implicit mechanism,
120+
but the intention is that this is limited to degrees of indirection, not
121+
conversion between arbitrary types.
122+
123+
124+
## See also
125+
126+
[Collections are smart pointers idiom](../idioms/deref.md).
127+
128+
[Documentation for `Deref` trait](https://doc.rust-lang.org/std/ops/trait.Deref.html).

idioms/deref.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,6 @@ slicing syntax. The target will be the borrowed view.
7676

7777
## See also
7878

79-
Deref polymorphism anti-pattern.
79+
[Deref polymorphism anti-pattern](../anti_patterns/deref.md).
8080

8181
[Documentation for `Deref` trait](https://doc.rust-lang.org/std/ops/trait.Deref.html).

patterns/RAII.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# RAII guards
1+
# RAII with guards
22

33
## Description
44

patterns/entry.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ TODO vs insert_or_update etc.
3636
## See also
3737

3838
[RFC](https://github.com/rust-lang/rfcs/blob/master/text/0216-collection-views.md)
39+
[RFC](https://github.com/rust-lang/rfcs/blob/8e2d3a3341da533f846f61f10335b72c9a9f4740/text/0921-entry_v3.md)
40+
41+
[Hashmap::entry docs](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry)

0 commit comments

Comments
 (0)