From c56a8150b577002d0bf64e62605a089a6e4e45f8 Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Sun, 16 Oct 2022 18:32:52 +0200 Subject: [PATCH 01/12] "Change RAII contents to OBRM contents so contributions get tracked correctly" --- patterns/behavioural/RAII.md | 72 ++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/patterns/behavioural/RAII.md b/patterns/behavioural/RAII.md index f27e6f02..dfc62eb3 100644 --- a/patterns/behavioural/RAII.md +++ b/patterns/behavioural/RAII.md @@ -1,15 +1,21 @@ -# 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 idom 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 idom 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 memory in Rust, determining when to free the memory. +`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 +77,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 asyncronous 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 +121,24 @@ 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 was designed without the need to interface with such code, so its far less common to implement OBRM yourself +* C++ doesn't have `Deref` nor 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++ equivalent to `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 +148,14 @@ 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). +[Rustnomicon entry]: https://doc.rust-lang.org/nomicon/obrm.html + +[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 + +[The Rule of 5/3/0 in C++]: https://en.cppreference.com/w/cpp/language/rule_of_three + +Rustdoc to std::marker::Copy explaining why [Copy forbids implementing Drop]: https://doc.rust-lang.org/std/marker/trait.Copy.html#when-cant-my-type-be-copy + +[Discussion of Copy: !Drop (highly theoretical)]: https://stackoverflow.com/a/67645936 \ No newline at end of file From 42813c592e692145fda4d2e513ed3a2cdbe7c33f Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Sun, 16 Oct 2022 18:40:24 +0200 Subject: [PATCH 02/12] "Rename RAII document to OBRM, add comments to dtor" --- SUMMARY.md | 2 +- idioms/dtor-finally.md | 4 ++++ patterns/behavioural/{RAII.md => OBRM.md} | 0 3 files changed, 5 insertions(+), 1 deletion(-) rename patterns/behavioural/{RAII.md => OBRM.md} (100%) diff --git a/SUMMARY.md b/SUMMARY.md index aa85661d..9ee111d0 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](./patterns/behavioural/OBRM.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/OBRM.md similarity index 100% rename from patterns/behavioural/RAII.md rename to patterns/behavioural/OBRM.md From f444de2cce6ee4932db637f3c1f15ee76cfd5815 Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Sun, 16 Oct 2022 18:46:12 +0200 Subject: [PATCH 03/12] "Add todos" --- patterns/behavioural/OBRM.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/patterns/behavioural/OBRM.md b/patterns/behavioural/OBRM.md index dfc62eb3..58691fc5 100644 --- a/patterns/behavioural/OBRM.md +++ b/patterns/behavioural/OBRM.md @@ -1,5 +1,12 @@ # Resource management with OBRM + ## Description "Ownership Based Resource Management" (OBRM) - also known as ["Resource Acquisition is Initialisation" (RAII)][wikipedia] - is an idom meant to make handling resources easier and less error-prone. From 0ff9c8017ba1b584a00b6cea8fcf3e53192d93da Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Sun, 16 Oct 2022 19:04:18 +0200 Subject: [PATCH 04/12] "Apply auto and easy fixes of markdownlint" --- CONTRIBUTING.md | 2 +- patterns/behavioural/OBRM.md | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) 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/patterns/behavioural/OBRM.md b/patterns/behavioural/OBRM.md index 58691fc5..e4b24c1f 100644 --- a/patterns/behavioural/OBRM.md +++ b/patterns/behavioural/OBRM.md @@ -19,7 +19,7 @@ The method releasing the resource is generally referred to as destructor, in Rus ## Example -OBRM is used to manage memory in Rust, determining when to free the memory. +OBRM is used to manage memory in Rust, determining when to free the memory. `Box` and `Rc` are classical examples of that. But most users will have closer contact with OBRM when managing other aspects. @@ -102,7 +102,7 @@ OBRM interaction with asyncronous code can also [be unexpected][Documentation of ## Discussion -OBRM is a useful pattern for ensuring resources are properly handled. +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. @@ -155,14 +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 -[Rustnomicon entry]: https://doc.rust-lang.org/nomicon/obrm.html - [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 -[The Rule of 5/3/0 in C++]: https://en.cppreference.com/w/cpp/language/rule_of_three - -Rustdoc to std::marker::Copy explaining why [Copy forbids implementing Drop]: https://doc.rust-lang.org/std/marker/trait.Copy.html#when-cant-my-type-be-copy - -[Discussion of Copy: !Drop (highly theoretical)]: https://stackoverflow.com/a/67645936 \ No newline at end of file +Rustdoc to std::marker::Copy explaining why [Copy forbids implementing Drop]: From 63045df7b95ed1387ef919d72a8f78c07de5f644 Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Sun, 16 Oct 2022 19:25:40 +0200 Subject: [PATCH 05/12] "Add another comment marking a problematic area" --- patterns/behavioural/OBRM.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patterns/behavioural/OBRM.md b/patterns/behavioural/OBRM.md index e4b24c1f..e48d2fb6 100644 --- a/patterns/behavioural/OBRM.md +++ b/patterns/behavioural/OBRM.md @@ -5,7 +5,6 @@ * clear up dtor finally discussion * execute and apply lints (contributing.md) * check unlinted formatting - --> ## Description @@ -139,6 +138,7 @@ In C++ behavior on a "move" (which is semantically meant to signify passing held 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++ equivalent to `Box`, can contain `nullptr`. From ad7baec9b544d2c8cf37ed86705d784324cbcb65 Mon Sep 17 00:00:00 2001 From: 9SMTM6 <44668330+9SMTM6@users.noreply.github.com> Date: Sun, 16 Oct 2022 21:31:20 +0200 Subject: [PATCH 06/12] Update patterns/behavioural/OBRM.md Co-authored-by: simonsan <14062932+simonsan@users.noreply.github.com> --- patterns/behavioural/OBRM.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patterns/behavioural/OBRM.md b/patterns/behavioural/OBRM.md index e48d2fb6..8054715c 100644 --- a/patterns/behavioural/OBRM.md +++ b/patterns/behavioural/OBRM.md @@ -97,7 +97,7 @@ after finalisation. 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 asyncronous code can also [be unexpected][Documentation of tokios Mutex]. +OBRM interaction with asynchronous code can also [be unexpected][Documentation of tokios Mutex]. ## Discussion From 04178d77397310f13d92e708cc2d186722e457cf Mon Sep 17 00:00:00 2001 From: 9SMTM6 <44668330+9SMTM6@users.noreply.github.com> Date: Mon, 17 Oct 2022 10:13:56 +0200 Subject: [PATCH 07/12] Revert file renaming (pt1) Co-authored-by: simonsan <14062932+simonsan@users.noreply.github.com> --- SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUMMARY.md b/SUMMARY.md index 9ee111d0..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) - - [Resource management with OBRM](./patterns/behavioural/OBRM.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) From 66ab0d273730e24d4518388fedf93adb73265d75 Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Mon, 17 Oct 2022 10:15:32 +0200 Subject: [PATCH 08/12] "Rename back OBRM file to RAII to keep links working" --- patterns/behavioural/{OBRM.md => RAII.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename patterns/behavioural/{OBRM.md => RAII.md} (100%) diff --git a/patterns/behavioural/OBRM.md b/patterns/behavioural/RAII.md similarity index 100% rename from patterns/behavioural/OBRM.md rename to patterns/behavioural/RAII.md From ace2c27500a83300642b6e511f4bb5809ab4277a Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Tue, 18 Oct 2022 15:25:19 +0200 Subject: [PATCH 09/12] "Fix gitignore for mdbook directory" --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 575c0ad5736a5aeedebac7c855f9f7a4a52d4cd9 Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Tue, 18 Oct 2022 15:26:04 +0200 Subject: [PATCH 10/12] 'Apply typo fix suggestion' --- patterns/behavioural/RAII.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patterns/behavioural/RAII.md b/patterns/behavioural/RAII.md index 8054715c..0ec16ef5 100644 --- a/patterns/behavioural/RAII.md +++ b/patterns/behavioural/RAII.md @@ -8,12 +8,12 @@ --> ## Description -"Ownership Based Resource Management" (OBRM) - also known as ["Resource Acquisition is Initialisation" (RAII)][wikipedia] - is an idom meant to make handling resources easier and less error-prone. +"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 idom is supported by the language as it allows to automatically insert calls to the releasing code in the spots where the object becomes unreachable. +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 From f0f3250ff03700b20890f6966bd7d0c0cd4045d2 Mon Sep 17 00:00:00 2001 From: Samuel Maier Date: Tue, 18 Oct 2022 15:35:05 +0200 Subject: [PATCH 11/12] "Apply a bunch of suggesions" --- patterns/behavioural/RAII.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/patterns/behavioural/RAII.md b/patterns/behavioural/RAII.md index 0ec16ef5..09c29371 100644 --- a/patterns/behavioural/RAII.md +++ b/patterns/behavioural/RAII.md @@ -18,7 +18,7 @@ The method releasing the resource is generally referred to as destructor, in Rus ## Example -OBRM is used to manage memory in Rust, determining when to free the memory. +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. @@ -129,7 +129,7 @@ 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 was designed without the need to interface with such code, so its far less common to implement OBRM yourself +* 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 `Deref` nor 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. @@ -140,7 +140,7 @@ In Rust, a moved-out-of variable can not be used, only reassigned a new value (t 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++ equivalent to `Box`, can contain `nullptr`. +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`. From 2001f9da3748673820b58f35811a115e283af6a9 Mon Sep 17 00:00:00 2001 From: 9SMTM6 <44668330+9SMTM6@users.noreply.github.com> Date: Tue, 18 Oct 2022 15:35:40 +0200 Subject: [PATCH 12/12] Update patterns/behavioural/RAII.md Co-authored-by: Julian Ganz --- patterns/behavioural/RAII.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patterns/behavioural/RAII.md b/patterns/behavioural/RAII.md index 09c29371..42274a5a 100644 --- a/patterns/behavioural/RAII.md +++ b/patterns/behavioural/RAII.md @@ -130,7 +130,7 @@ 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 `Deref` nor a borrow checker, so code using RAII can not archive the same combination of safety and ergonomics +* 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.