mirror of
https://github.com/rust-unofficial/patterns
synced 2024-11-04 18:00:27 +00:00
Added a few idioms and patterns - some are still WIP
This commit is contained in:
parent
cf8479320a
commit
325b642a01
12
README.md
12
README.md
@ -14,29 +14,29 @@ language.
|
||||
* [Concatenating strings with `format!`](idioms/concat-format.md)
|
||||
* TODO private field to indicate extensibility
|
||||
* TODO trait to separate visibility of methods from visibility of data (https://github.com/sfackler/rust-postgres/blob/master/src/lib.rs#L1400)
|
||||
* TODO Deref on Vec/String to treat like a smart pointer/borrowed view of such data
|
||||
* [Collections are smart pointers](idioms/deref.md)
|
||||
* TODO leak amplification ("Vec::drain sets the Vec's len to 0 prematurely so that mem::forgetting Drain "only" mem::forgets more stuff. instead of exposing uninitialized memory or having to update the len on every iteration")
|
||||
* TODO dtor for finally
|
||||
* [Finalisation in destructors](idioms/dtor-finally.md)
|
||||
* TODO interior mutability - UnsafeCell, Cell, RefCell
|
||||
* TODO treating Option like a list
|
||||
|
||||
### Design patterns
|
||||
|
||||
* [Builder](patterns/builder.md)
|
||||
* TODO RAII ( + borrows - mutex guard, [style guide entry]())
|
||||
* [RAII guards](patterns/raii.md)
|
||||
* [Newtype](patterns/newtype.md)
|
||||
* TODO iterators (to safely avoid bounds checks)
|
||||
* TODO closures and lifetimes (coupling to lifetime)
|
||||
* TODO platform-specific sub-modules (https://github.com/rust-lang/rfcs/blob/master/text/0517-io-os-reform.md#platform-specific-opt-in)
|
||||
* TODO affine types/session types
|
||||
* TODO Entry API vs insert_or_update etc.
|
||||
* [Entry API](patterns/entry.md)
|
||||
* TODO visitor
|
||||
* TODO fold
|
||||
* TODO small crates and semver
|
||||
* TODO extension traits
|
||||
* TODO destructor bombs (ensure linear typing dynamically, e.g., https://github.com/Munksgaard/session-types/commit/0f25ccb7c3bc9f65fa8eaf538233e8fe344a189a)
|
||||
* TODO convertible to Foo trait for more generic generics (e.g., http://static.rust-lang.org/doc/master/std/fs/struct.File.html#method.open)
|
||||
* TODO late binding of bounds for better APIs (i.e., Mutex's don't require Send)
|
||||
* [Late bound bounds](patterns/late-bounds.md)
|
||||
* TODO 'shadow' borrowed version of struct - e.g., double buffering, Niko's parser generator
|
||||
* TODO composition of structs to please the borrow checker
|
||||
|
||||
@ -49,7 +49,7 @@ language.
|
||||
* TODO Deref polymorphism
|
||||
* TODO Matching all fields of a struct (back compat)
|
||||
* TODO wildcard matches
|
||||
* TODO tkaing an enum rather than having multiple functions
|
||||
* TODO taking an enum rather than having multiple functions
|
||||
|
||||
|
||||
|
||||
|
81
idioms/deref.md
Normal file
81
idioms/deref.md
Normal file
@ -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).
|
96
idioms/dtor-finally.md
Normal file
96
idioms/dtor-finally.md
Normal file
@ -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).
|
115
patterns/RAII.md
Normal file
115
patterns/RAII.md
Normal file
@ -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).
|
38
patterns/entry.md
Normal file
38
patterns/entry.md
Normal file
@ -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)
|
41
patterns/late-bounds.md
Normal file
41
patterns/late-bounds.md
Normal file
@ -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.
|
@ -7,7 +7,10 @@ A short, prose description of the pattern.
|
||||
|
||||
## Example
|
||||
|
||||
An example of the pattern in action, should be mostly code, commented liberally.
|
||||
```rust
|
||||
// An example of the pattern in action, should be mostly code, commented
|
||||
// liberally.
|
||||
```
|
||||
|
||||
|
||||
## Motivation
|
||||
|
Loading…
Reference in New Issue
Block a user