Added a few idioms and patterns - some are still WIP
parent
cf8479320a
commit
325b642a01
@ -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).
|
@ -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).
|
@ -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).
|
@ -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)
|
@ -0,0 +1,41 @@
|
|||||||
|
# Late bound bounds
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
TODO late binding of bounds for better APIs (i.e., Mutex's don't require Send)
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
A deeper discussion about this pattern. You might want to cover how this is done
|
||||||
|
in other languages, alternative approaches, why this is particularly nice in
|
||||||
|
Rust, etc.
|
||||||
|
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
Related patterns (link to the pattern file). Versions of this pattern in other
|
||||||
|
languages.
|
Loading…
Reference in New Issue