forked from rust-unofficial/patterns
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added a few idioms and patterns - some are still WIP
- Loading branch information
Showing
7 changed files
with
381 additions
and
7 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,81 @@ | ||
# Collections are smart pointers | ||
|
||
## Description | ||
|
||
Use the `Deref` trait to treat collections like smart pointers, offering owning | ||
and borrowed views of data. | ||
|
||
|
||
## Example | ||
|
||
```rust | ||
struct Vec<T> { | ||
... | ||
} | ||
|
||
impl<T> Deref for Vec<T> { | ||
type Target = [T]; | ||
|
||
fn deref(&self) -> &[T] { | ||
... | ||
} | ||
} | ||
``` | ||
|
||
A `Vec<T>` is an owning collection of `T`s, a slice (`&[T]`) is a borrowed | ||
collection of `T`s. Implementing `Deref` for `Vec` allows implicit dereferencing | ||
from `&Vec<T>` to `&[T]` and includes the relationship in auto-derefencing | ||
searches. Most methods you might expect to be implemented for `Vec`s are instead | ||
implemented for slices. | ||
|
||
See also `String` and `&str`. | ||
|
||
## Motivation | ||
|
||
Ownership and borrowing are key aspects of the Rust language. Data structures | ||
must account for these semantics properly in order to give a good user | ||
experience. When implementing a data structure which owns its data, offering a | ||
borrowed view of that data allows for more flexible APIs. | ||
|
||
|
||
## Advantages | ||
|
||
Most methods can be implemented only for the borrowed view, they are then | ||
implicitly available for the owning view. | ||
|
||
Gives clients a choice between borrowing or taking ownership of data. | ||
|
||
|
||
## Disadvantages | ||
|
||
Methods and traits only available via dereferencing are not taken into account | ||
when bounds checking, so generic programming with data structures using this | ||
pattern can get complex (see the `Borrow` and `AsRef` traits, etc.). | ||
|
||
|
||
## Discussion | ||
|
||
Smart pointers and collections are analogous: a smart pointer points to a single | ||
object, whereas a collection points to many objects. From the point of view of | ||
the type system there is little difference between the two. A collection owns | ||
its data if the only way to access each datum is via the collection and the | ||
collection is responsible for deleting the data (even in cases of shared | ||
ownership, some kind of borrowed view may be appropriate). If a collection owns | ||
its data, it is usually useful to provide a view of the data as borrowed so that | ||
it can be multiply referenced. | ||
|
||
Most smart pointers (e.g., `Foo<T>`) implement `Deref<Target=T>`. However, | ||
collections will usually dereference to a custom type. `[T]` and `str` have some | ||
language support, but in the general case, this is not necessary. `Foo<T>` can | ||
implement `Deref<Target=Bar<T>>` where `Bar` is a dynamically sized type and | ||
`&Bar<T>` is a borrowed view of the data in `Foo<T>`. | ||
|
||
Commonly, ordered collections will implement `Index` for `Range`s to provide | ||
slicing syntax. The target will be the borrowed view. | ||
|
||
|
||
## See also | ||
|
||
Deref polymorphism anti-pattern. | ||
|
||
[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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# Finalisation in destructors | ||
|
||
## Description | ||
|
||
Rust does not provide he equivalent to `finally` blocks - code that will be | ||
executed no matter how a function is exited. Instead an object's destructor can | ||
be used to run code that must be run before exit. | ||
|
||
|
||
## Example | ||
|
||
```rust | ||
fn bar() -> Result<(), ()> { | ||
// These don't need to be defined inside the function. | ||
struct Foo; | ||
|
||
// Implement a destructor for Foo. | ||
impl Drop for Foo { | ||
fn drop(&mut self) { | ||
println!("exit"); | ||
} | ||
} | ||
|
||
// The dtor of _exit is run however `bar` is exited. | ||
let _exit = Foo; | ||
// Implicit return in try!. | ||
try!(baz()); | ||
// Normal return. | ||
OK(()) | ||
} | ||
``` | ||
|
||
|
||
## Motivation | ||
|
||
If a function has multiple return points, then executing code on exit becomes | ||
difficult and repetitive (and thus bug-prone). This is especially the case where | ||
return is implicit due to a macro. A common case is `try!` which returns if the | ||
result is an `Err`, but continues if it is `Ok`. `try!` is used as an exception | ||
handling mechanism, but unlike Java (which has `finally`), there is no way to | ||
schedule code to run in both the normal and exceptional cases. Panicking will | ||
also exit a function early. | ||
|
||
|
||
## Advantages | ||
|
||
Code in destructors will (nearly) always be run - copes with panics, early | ||
returns, etc. | ||
|
||
|
||
## Disadvantages | ||
|
||
It is not guaranteed that destructors will run. For example, if there is an | ||
infinite loop in a function or if running a function crashes before exit. | ||
Destructors are also not run in the case of a panic in an already panicking | ||
thread. Therefore destructors cannot be relied on as finalisers where it is | ||
absolutely essential that finalisation happens. | ||
|
||
This pattern introduces some hard to notice, implicit code. Reading a function | ||
gives no clear indication of destructors to be run on exit. This can make | ||
debugging tricky. | ||
|
||
Requiring an object and `Drop` impl just for finalisation is heavy on boilerplate. | ||
|
||
|
||
## Discussion | ||
|
||
There is some subtlety about how exactly to store the object used as a | ||
finaliser. It must be kept alive until the end of the function and must then be | ||
destroyed. The object must always be a value or uniquely owned pointer (e.g., | ||
`Box<Foo>`). If a shared pointer (such as `Rc`) is used, then the finaliser can | ||
be kept alive beyond the lifetime of the function. For similar reasons, the | ||
finaliser should not be moved or returned. | ||
|
||
The finaliser must be assigned into a variable, otherwise it will be destroyed | ||
immediately, rather than when it goes out of scope. The variable name must start | ||
with `_` if the variable is only used as a finaliser, otherwise the compiler | ||
will warn that the finaliser is never used. However, do not call the variable | ||
`_` with no suffix - in that case it will be again be destroyed immediately. | ||
|
||
In Rust, destructors are run when an object goes out of scope. This happens | ||
whether we reach the end of block, there is an early return, or the program | ||
panics. When panicking, Rust unwinds the stack running destructors for each | ||
object in each stack frame. So, destructors get called even if the panic happens | ||
in a function being called. | ||
|
||
If a destructor panics while unwinding, there is no good action to take, so Rust | ||
aborts the thread immediately, without running further destructors. This means | ||
that desctructors are not absolutely guaranteed to run. It also means that you | ||
must take extra care in your destructors not to panic, since it could leave | ||
resources in an unexpected state. | ||
|
||
|
||
## See also | ||
|
||
[RAII](../patterns/raii.md). |
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,115 @@ | ||
# RAII guards | ||
|
||
## Description | ||
|
||
RAII stands for "Resource allocation is initialisation" which is a terrible | ||
name. The essence of the pattern is that resource initialisation is done in the | ||
constructor of an object and finalisation in the destructor. This pattern is | ||
extended in Rust by using an RAII object as a guard of some resource and relying | ||
on the type system to ensure that access is always mediated by the guard object. | ||
|
||
## Example | ||
|
||
Mutex guards are the classic example of this pattern from the std library (this | ||
is a simplified version of the real implementation): | ||
|
||
```rust | ||
struct Mutex<T> { | ||
// We keep a reference to our data: T here. | ||
... | ||
} | ||
|
||
struct MutexGuard<'a, T: 'a> { | ||
data: &'a T, | ||
... | ||
} | ||
|
||
// Locking the mutex is explicit. | ||
impl<T> Mutex<T> { | ||
fn lock(&self) -> MutexGuard<T> { | ||
// Lock the underlying OS mutex. | ||
... | ||
|
||
// MutexGuard keeps a reference to self | ||
MutexGuard { data: self, ... } | ||
} | ||
} | ||
|
||
// Destructor for unlocking the mutex. | ||
impl<'a, T> Drop for MutexGuard<'a, T> { | ||
fn drop(&mut self) { | ||
// Unlock the underlying OS mutex. | ||
... | ||
} | ||
} | ||
|
||
// Implementing Deref means we can treat MutexGuard like a pointer to T. | ||
impl<'a, T> Deref for MutexGuard<'a, T> { | ||
type Target = T; | ||
|
||
fn deref(&self) -> &T { | ||
self.data | ||
} | ||
} | ||
|
||
fn main(x: Mutex<Foo>) { | ||
let xx = x.lock(); | ||
xx.foo(); // foo is a method on Foo. | ||
// The borrow checker ensures we can't store a reference to the underlying | ||
// Foo which will outlive the guard xx. | ||
|
||
// x is unlocked when we exit this function and xx's destructor is executed. | ||
} | ||
``` | ||
|
||
|
||
## Motivation | ||
|
||
Where a resource must be finalised after use, RAII can be used to do this | ||
finalisation. If it is an error to access that resource after finalisation, then | ||
this pattern can be used to prevent such errors. | ||
|
||
|
||
## Advantages | ||
|
||
Prevents errors where a resource is not finalised and where a resource is used | ||
after finalisation. | ||
|
||
|
||
## Discussion | ||
|
||
RAII is a useful pattern for ensuring resources are properly deallocated or | ||
finalised. We can make use of the borrow checker in Rust to statically prevent | ||
errors stemming from using resources after finalisation takes place. | ||
|
||
The core aim of the borrow checker is to ensure that references to data do not | ||
outlive that data. The RAII guard pattern works because the guard object | ||
contains a reference to the underlying resource and only exposes such | ||
references. Rust ensures that the guard cannot outlive the underlying resource | ||
and that references to the resource mediated by the guard cannot outlive the | ||
guard. To see how this works it is helpful to examine the signature of `deref` | ||
without lifetime elision: | ||
|
||
```rust | ||
fn deref<'a>(&'a self) -> &'a T { ... } | ||
``` | ||
|
||
The returned reference to the resource has the same lifetime as `self` (`'a`). | ||
The borrow checker therefore ensures that the lifetime of the reference to `T` | ||
is shorter than the lifetime of `self`. | ||
|
||
Note that implementing `Deref` is not a core part of this pattern, it only makes | ||
using the guard object more ergonomic. Implementing a `get` method on the guard | ||
works just as well. | ||
|
||
|
||
|
||
## See also | ||
|
||
[Finalisation in destructors idiom](../idioms/dtor-finally.md) | ||
|
||
RAII is a common pattern in C++: [cppreference.com](http://en.cppreference.com/w/cpp/language/raii), | ||
[wikipedia](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization). | ||
|
||
[Style guide enty](http://doc.rust-lang.org/stable/style/ownership/raii.html) | ||
(currently just a placeholder). |
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,38 @@ | ||
# Entry API | ||
|
||
## Description | ||
|
||
A short, prose description of the pattern. | ||
|
||
|
||
## Example | ||
|
||
```rust | ||
// An example of the pattern in action, should be mostly code, commented | ||
// liberally. | ||
``` | ||
|
||
|
||
## Motivation | ||
|
||
Why and where you should use the pattern | ||
|
||
|
||
## Advantages | ||
|
||
Good things about this pattern. | ||
|
||
|
||
## Disadvantages | ||
|
||
Bad things about this pattern. Possible contraindications. | ||
|
||
|
||
## Discussion | ||
|
||
TODO vs insert_or_update etc. | ||
|
||
|
||
## See also | ||
|
||
[RFC](https://github.com/rust-lang/rfcs/blob/master/text/0216-collection-views.md) |
Oops, something went wrong.