diff --git a/.gitignore b/.gitignore index 29b71da3..9f39f0c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ # Generated output of mdbook -/book +book .DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41383c59..772d33eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,7 +104,7 @@ A good principle: "Work together, share ideas, teach others." ### Important Note -Please **don't force push** commits in your branch, in order to keep commit +Please __don't force push__ commits in your branch, in order to keep commit history and make it easier for us to see changes between reviews. Make sure to `Allow edits of maintainers` (under the text box) in the PR so diff --git a/SUMMARY.md b/SUMMARY.md index aa85661d..cb2ac011 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -26,7 +26,7 @@ - [Command](./patterns/behavioural/command.md) - [Interpreter](./patterns/behavioural/interpreter.md) - [Newtype](./patterns/behavioural/newtype.md) - - [RAII Guards](./patterns/behavioural/RAII.md) + - [Resource management with OBRM (RAII)](./patterns/behavioural/RAII.md) - [Strategy](./patterns/behavioural/strategy.md) - [Visitor](./patterns/behavioural/visitor.md) - [Creational](./patterns/creational/intro.md) diff --git a/idioms/dtor-finally.md b/idioms/dtor-finally.md index 45152905..80c3c913 100644 --- a/idioms/dtor-finally.md +++ b/idioms/dtor-finally.md @@ -1,5 +1,9 @@ # Finalisation in destructors + + + + ## Description Rust does not provide the equivalent to `finally` blocks - code that will be diff --git a/patterns/behavioural/RAII.md b/patterns/behavioural/RAII.md index f27e6f02..42274a5a 100644 --- a/patterns/behavioural/RAII.md +++ b/patterns/behavioural/RAII.md @@ -1,15 +1,27 @@ -# RAII with guards - +# Resource management with OBRM + + ## Description -[RAII][wikipedia] stands for "Resource Acquisition 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. +"Ownership Based Resource Management" (OBRM) - also known as ["Resource Acquisition is Initialisation" (RAII)][wikipedia] - is an idiom meant to make handling resources easier and less error-prone. + +In essence it means that an object serves as proxy for a resource, to create the object you have to aquire the resource, once that object isn't used anymore - determined by it being unreachable - the resource is released. +It is said the object guards access to the resource. + +This idiom is supported by the language as it allows to automatically insert calls to the releasing code in the spots where the object becomes unreachable. +The method releasing the resource is generally referred to as destructor, in Rust [drop][Drop::drop] serves that role. ## Example +OBRM is used to manage heap memory in Rust, determining when to free it. +`Box` and `Rc` are classical examples of that. +But most users will have closer contact with OBRM when managing other aspects. + Mutex guards are the classic example of this pattern from the std library (this is a simplified version of the real implementation): @@ -71,24 +83,31 @@ fn baz(x: Mutex) { ## 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. +Often times a user will not need to implement [Drop::drop] themselves but will already be covered by just using the provided OBRM-Objects from the standard library or used crates. + + +But for managing external resources it is often helpful, when communicating with external systems, or of course if implementing your own resources. ## Advantages Prevents errors where a resource is not finalised and where a resource is used after finalisation. +## Disadvantages + +OBRM ensures correctness with implicit behavior, which isn't visible in the source code (one needs to be aware that said object uses OBRM). It also can be difficult to implement for some complex situations. For example resource aquisition and release in bulk, like in performance critical code. Or code which may not fail in some sections - resource aquisition is often fallible. + +OBRM interaction with asynchronous code can also [be unexpected][Documentation of tokios Mutex]. + ## 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. +OBRM is a useful pattern for ensuring resources are properly handled. +The borrow checker in Rust will statically prevent +errors stemming from using resources after the resource has been released. 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 +outlive that data. The OBRM guard pattern works because the guard object +acts as a proxy to the underlying resource and enables access only via 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` @@ -108,6 +127,25 @@ 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. +When compared with RAII in C++, there are a few significant differences: + +* while C++ code often interfaces with C code or code in older styles, which doesn't use RAII. Rust does so much less often and because of a few factors one often just pulls a crate that already has the API encapsulated. So its far less common to implement OBRM yourself +* C++ doesn't have a borrow checker, so code using RAII can not archive the same combination of safety and ergonomics +* perhaps most importantly, Rust has different semantics when it comes to moving and copying of values, this will be expanded on below. + +C++ has complex rules for copying and moving of values, that Rust managed to simplify while keeping most advantages. +In C++ behavior on a "move" (which is semantically meant to signify passing held resources to the moved-to value) is customizable in its move and move-assignment constructors. +But after a variable has been "moved out of", it must still be accessable in C++. +In Rust, a moved-out-of variable can not be used, only reassigned a new value (this is referred to as "destructive move"), and the behavior on a move is not customizable, instead a move simply copies the bytes of the moved-out value into the moved-into variable, and ensures the semantics of a destructive move. + + +This massively simplifies creation and management of OBRM Objects compared to C++, where one often has to do a lot more manual management of RAII classes - definition of the `destructor`, the `copy constructor`, the `copy assignment constructor`, the `move constructor` and the `move assignment constructor` all at once -, which is very error prone, and where RAII objects have to have a legal moved-out state, which often makes usage of these classes more problematic. +For example, `unique_ptr`, the C++ standard library type that solves similar purposes as `Box`, can contain `nullptr`. + +Rust also moves values by default, which can be opted out by explicitly calling `Clone::clone` on each assignment, or on a Type level by implementing `Copy`. +It is currently forbidden, and that is expected to continue, to implement `Copy` on a Type that implements `Drop` or contains a Type that implements `Drop`. +This means that resource aquisition in Rust is a lot more explicit than in C++, as it can not happen during a simple assignment as it can in C++. + ## See also [Finalisation in destructors idiom](../../idioms/dtor-finally.md) @@ -117,5 +155,8 @@ RAII is a common pattern in C++: [cppreference.com](http://en.cppreference.com/w [wikipedia]: https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization -[Style guide entry](https://doc.rust-lang.org/1.0.0/style/ownership/raii.html) -(currently just a placeholder). +[Drop::drop]: https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop + +[Documentation of tokios Mutex]: https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use + +Rustdoc to std::marker::Copy explaining why [Copy forbids implementing Drop]: