diff --git a/README.md b/README.md index ce970c4..0405b11 100644 --- a/README.md +++ b/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 diff --git a/idioms/deref.md b/idioms/deref.md new file mode 100644 index 0000000..3bcbda4 --- /dev/null +++ b/idioms/deref.md @@ -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 { + ... +} + +impl Deref for Vec { + type Target = [T]; + + fn deref(&self) -> &[T] { + ... + } +} +``` + +A `Vec` 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` 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`) implement `Deref`. 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` can +implement `Deref>` where `Bar` is a dynamically sized type and +`&Bar` is a borrowed view of the data in `Foo`. + +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). diff --git a/idioms/dtor-finally.md b/idioms/dtor-finally.md new file mode 100644 index 0000000..49a8ac1 --- /dev/null +++ b/idioms/dtor-finally.md @@ -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`). 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). diff --git a/patterns/RAII.md b/patterns/RAII.md new file mode 100644 index 0000000..a26084a --- /dev/null +++ b/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 { + // We keep a reference to our data: T here. + ... +} + +struct MutexGuard<'a, T: 'a> { + data: &'a T, + ... +} + +// Locking the mutex is explicit. +impl Mutex { + fn lock(&self) -> MutexGuard { + // 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) { + 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). diff --git a/patterns/entry.md b/patterns/entry.md new file mode 100644 index 0000000..95962fb --- /dev/null +++ b/patterns/entry.md @@ -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) diff --git a/patterns/late-bounds.md b/patterns/late-bounds.md new file mode 100644 index 0000000..8c91ef3 --- /dev/null +++ b/patterns/late-bounds.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. diff --git a/template.md b/template.md index a87b532..4898200 100644 --- a/template.md +++ b/template.md @@ -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