-
Notifications
You must be signed in to change notification settings - Fork 375
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
136 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
# RAII guards | ||
# RAII with guards | ||
|
||
## Description | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters