Skip to content

Commit

Permalink
Deref polymorphism anti-pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
nrc committed Nov 2, 2015
1 parent a06ca32 commit c2cf5e6
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 3 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ language.
* [Finalisation in destructors](idioms/dtor-finally.md)
* TODO interior mutability - UnsafeCell, Cell, RefCell
* TODO treating Option like a list
* TODO `Default` trait

### Design patterns

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



### Anti-patterns

* TODO thread + catch_panic for exceptions
* TODO Clone to satisfy the borrow checker
* TODO Deref polymorphism
* [Deref polymorphism](anti_patterns/deref.md)
* TODO Matching all fields of a struct (back compat)
* TODO wildcard matches
* TODO taking an enum rather than having multiple functions
Expand Down
128 changes: 128 additions & 0 deletions anti_patterns/deref.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# `Deref` polymorphism

## Description

Abuse the `Deref` trait to emulate inheritance between structs, and thus reuse
methods.


## Example

Sometimes we want to emulate the following common pattern from OO languages such
as Java:

```java
class Foo {
void m() { ... }
}

class Bar extends Foo {}

public static void main(String[] args) {
Bar b = new Bar();
b.m();
}
```

We can use the deref polymorphism anti-pattern to do so:

```rust
struct Foo {}

impl Foo {
fn m(&self) { ... }
}

struct Bar {
f: Foo
}

impl Deref for Bar {
type Target = Foo;
fn deref(&self) -> &Foo {
&self.f
}
}

fn main() {
let b = Bar { Foo {} };
b.m();
}
```

There is no struct inheritance in Rust. Instead we use composition and include
an instance of `Foo` in `Bar` (since the field is a value, it is stored inline,
so if there were fields, they would have the same layout in memory as the Java
version (probably, you should use `#[repr(C)]` if you want to be sure)).

In order to make the method call work we implement `Deref` for `Bar` with `Foo`
as the target (returning the embedded `Foo` field). That means that when we
dereference a `Foo` (for example, using `*`) then we will get a `Bar`. That is
pretty weird. Dereferencing usually gives a `T` from a reference to `T`, here we
have two unrelated types. However, since the dot operator does implicit
dereferencing, it means that the method call will search for methods on `Foo` as
well as `Bar`.


## Advantages

You save a little boilerplate, e.g.,

```rust
impl Bar {
fn m(&self) {
self.f.m()
}
}
```


## Disadvantages

Most importantly this is a surprising idiom - future programmers reading this in
code will not expect this to happen. That's because we are abusing the `Deref`
trait rather than using it as intended (and documented, etc.). It's also because
the mechanism here is completely implicit.

This pattern does not introduce subtyping between `Foo` and `Bar` like
inheritance in Java or C++ does. Furthermore, traits implemented by `Foo` are
not automatically implemented for `Bar`, so this pattern interacts badly with
bounds checking and thus generic programming.

Using this pattern gives subtly different semantics from most OO languages with
regards to `self`. Usually it remains a reference to the sub-class, with this
pattern it will be the 'class' where the method is defined.

Finally, this pattern only supports single inheritance, and has no notion of
interfaces, class-based privacy, or other inheritance-related features. So, it
gives an experience that will be subtly surprising to programmers used to Java
inheritance, etc.


## Discussion

There is no one good alternative. Depending on the exact circumstances it might
be better to re-implement using traits or to write out the facade methods to
dispatch to `Foo` manually. We do intend to add a mechanism for inheritance
similar to this to Rust, but it is likely to be some time before it reaches
stable Rust. See these [blog](http://aturon.github.io/blog/2015/09/18/reuse/)
[posts](http://smallcultfollowing.com/babysteps/blog/2015/10/08/virtual-structs-part-4-extended-enums-and-thin-traits/)
and this [RFC issue](https://github.com/rust-lang/rfcs/issues/349) for more details.

The `Deref` trait is designed for the implementation of custom pointer types.
The intention is that it will take a pointer-to-`T` to a `T`, not convert
between different types. It is a shame that this isn't (probably cannot be)
enforced by the trait definition.

Rust tries to strike a careful balance between explicit and implicit mechanisms,
favouring explicit conversions between types. Automatic dereferencing in the dot
operator is a case where the ergonomics strongly favour an implicit mechanism,
but the intention is that this is limited to degrees of indirection, not
conversion between arbitrary types.


## See also

[Collections are smart pointers idiom](../idioms/deref.md).

[Documentation for `Deref` trait](https://doc.rust-lang.org/std/ops/trait.Deref.html).
2 changes: 1 addition & 1 deletion idioms/deref.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,6 @@ slicing syntax. The target will be the borrowed view.

## See also

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

[Documentation for `Deref` trait](https://doc.rust-lang.org/std/ops/trait.Deref.html).
2 changes: 1 addition & 1 deletion patterns/RAII.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# RAII guards
# RAII with guards

## Description

Expand Down
3 changes: 3 additions & 0 deletions patterns/entry.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ TODO vs insert_or_update etc.
## See also

[RFC](https://github.com/rust-lang/rfcs/blob/master/text/0216-collection-views.md)
[RFC](https://github.com/rust-lang/rfcs/blob/8e2d3a3341da533f846f61f10335b72c9a9f4740/text/0921-entry_v3.md)

[Hashmap::entry docs](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry)

0 comments on commit c2cf5e6

Please sign in to comment.