From c2cf5e6c1aac52225a08247d17c295cd3ec745e9 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Sat, 31 Oct 2015 13:40:47 -0400 Subject: [PATCH] Deref polymorphism anti-pattern --- README.md | 4 +- anti_patterns/deref.md | 128 +++++++++++++++++++++++++++++++++++++++++ idioms/deref.md | 2 +- patterns/RAII.md | 2 +- patterns/entry.md | 3 + 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 anti_patterns/deref.md diff --git a/README.md b/README.md index dc44f2a..6b31edf 100644 --- a/README.md +++ b/README.md @@ -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 @@ -40,6 +41,7 @@ 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 @@ -47,7 +49,7 @@ language. * 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 diff --git a/anti_patterns/deref.md b/anti_patterns/deref.md new file mode 100644 index 0000000..8dccea5 --- /dev/null +++ b/anti_patterns/deref.md @@ -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). diff --git a/idioms/deref.md b/idioms/deref.md index 3bcbda4..86d67d1 100644 --- a/idioms/deref.md +++ b/idioms/deref.md @@ -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). diff --git a/patterns/RAII.md b/patterns/RAII.md index a26084a..04e8547 100644 --- a/patterns/RAII.md +++ b/patterns/RAII.md @@ -1,4 +1,4 @@ -# RAII guards +# RAII with guards ## Description diff --git a/patterns/entry.md b/patterns/entry.md index 95962fb..4e92437 100644 --- a/patterns/entry.md +++ b/patterns/entry.md @@ -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)