From 12f6f988d4a6d81bf20e3f0f74c0e6c704351b36 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 18 Aug 2023 20:08:03 +0200 Subject: [PATCH] chore: reformat repository with dprint (#370) * chore: reformat repository with dprint * ci: change action name to reflect that it's not only markdown * fix: broken link --- .github/workflows/lint.yml | 2 +- .github/workflows/url-check-config.json | 10 +- .gitignore | 1 + CONTRIBUTING.md | 54 ++--- README.md | 21 +- dprint.json | 38 +++- src/additional_resources/design-principles.md | 33 ++- src/anti_patterns/borrow_clone.md | 26 ++- src/anti_patterns/deny-warnings.md | 5 +- src/anti_patterns/deref.md | 8 +- src/anti_patterns/index.md | 6 +- src/functional/generics-type-classes.md | 50 ++--- src/functional/index.md | 13 +- src/functional/lenses.md | 172 +++++++-------- src/functional/paradigms.md | 33 ++- src/idioms/coercion-arguments.md | 30 +-- src/idioms/concat-format.md | 3 +- src/idioms/ctor.md | 14 +- src/idioms/default.md | 10 +- src/idioms/deref.md | 12 +- src/idioms/dtor-finally.md | 3 +- src/idioms/ffi/accepting-strings.md | 32 ++- src/idioms/ffi/errors.md | 10 +- src/idioms/ffi/intro.md | 6 +- src/idioms/ffi/passing-strings.md | 10 +- src/idioms/index.md | 10 +- src/idioms/mem-replace.md | 37 ++-- src/idioms/on-stack-dyn-dispatch.md | 13 +- src/idioms/option-iter.md | 18 +- src/idioms/priv-extend.md | 82 ++++--- src/idioms/return-consumed-arg-on-error.md | 16 +- src/idioms/rustdoc-init.md | 7 +- src/idioms/temporary-mutability.md | 8 +- src/intro.md | 49 ++--- src/patterns/behavioural/RAII.md | 12 +- src/patterns/behavioural/command.md | 26 +-- src/patterns/behavioural/interpreter.md | 37 ++-- src/patterns/behavioural/intro.md | 4 +- src/patterns/behavioural/newtype.md | 21 +- src/patterns/behavioural/strategy.md | 72 ++++--- src/patterns/behavioural/visitor.md | 14 +- src/patterns/creational/builder.md | 13 +- src/patterns/creational/fold.md | 12 +- src/patterns/creational/intro.md | 9 +- src/patterns/ffi/export.md | 202 +++++++++--------- src/patterns/ffi/intro.md | 10 +- src/patterns/ffi/wrappers.md | 105 ++++----- src/patterns/index.md | 14 +- src/patterns/structural/intro.md | 4 +- src/patterns/structural/small-crates.md | 28 +-- src/patterns/structural/unsafe-mods.md | 13 +- src/refactoring/index.md | 15 +- src/translations.md | 6 +- template.md | 4 +- 54 files changed, 753 insertions(+), 710 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 236691e..bf2ee6e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Lint Markdown +name: Lint with dprint on: push: diff --git a/.github/workflows/url-check-config.json b/.github/workflows/url-check-config.json index dd50607..3f96d79 100644 --- a/.github/workflows/url-check-config.json +++ b/.github/workflows/url-check-config.json @@ -1,7 +1,7 @@ { - "ignorePatterns": [ - { - "pattern": "^(http|https)://crates.io/" - } - ] + "ignorePatterns": [ + { + "pattern": "^(http|https)://crates.io/" + } + ] } diff --git a/.gitignore b/.gitignore index 29b71da..4a27d13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Generated output of mdbook /book .DS_Store +.vscode/settings.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85c61b4..99ad8b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,15 +2,14 @@ ## Introduction -This book is a catalogue of Rust programming techniques, (anti-)patterns, -idioms and other explanations. It is a compilation of collective (sometimes -implicit) knowledge as well as experiences that have emerged through -collaborative work. +This book is a catalogue of Rust programming techniques, (anti-)patterns, idioms +and other explanations. It is a compilation of collective (sometimes implicit) +knowledge as well as experiences that have emerged through collaborative work. -The patterns described here are **not rules**, but should be taken as -guidelines for writing idiomatic code in Rust. We are collecting Rust patterns -in this book so people can learn the tradeoffs between Rust idioms and use them -properly in their own code. +The patterns described here are **not rules**, but should be taken as guidelines +for writing idiomatic code in Rust. We are collecting Rust patterns in this book +so people can learn the tradeoffs between Rust idioms and use them properly in +their own code. If you want to be part of this effort here are some ways you can participate: @@ -18,7 +17,8 @@ If you want to be part of this effort here are some ways you can participate: If you have a question or an idea regarding certain content, but you want to have feedback of fellow community members, and you think it may not be -appropriate to file an issue open a discussion in our [discussion board](https://github.com/rust-unofficial/patterns/discussions). +appropriate to file an issue open a discussion in our +[discussion board](https://github.com/rust-unofficial/patterns/discussions). ## Writing a new article @@ -30,11 +30,13 @@ there is an existing discussion or if someone is already working on that topic: - [Pull Requests](https://github.com/rust-unofficial/patterns/pulls) If you don't find an issue regarding your topic, and you are sure it is not more -feasible to open a thread in the [discussion board](https://github.com/rust-unofficial/patterns/discussions) -please open a new issue, so we can discuss the ideas and future content -of the article together and maybe give some feedback/input on it. +feasible to open a thread in the +[discussion board](https://github.com/rust-unofficial/patterns/discussions) +please open a new issue, so we can discuss the ideas and future content of the +article together and maybe give some feedback/input on it. -When writing a new article it's recommended to copy the [pattern template](https://github.com/rust-unofficial/patterns/blob/master/template.md) +When writing a new article it's recommended to copy the +[pattern template](https://github.com/rust-unofficial/patterns/blob/master/template.md) into the appropriate directory and start editing it. You may not want to fill out every section and remove it, or you might want to add extra sections. @@ -43,12 +45,13 @@ Consider writing your article in a way that has a low barrier of entry so also the thought process behind it. So we can encourage people to use these patterns early on. -We encourage you to write idiomatic Rust code that builds in the [playground](https://play.rust-lang.org/). +We encourage you to write idiomatic Rust code that builds in the +[playground](https://play.rust-lang.org/). If you use links to blogposts or in general content that is not to be sure existing in a few years (e.g. pdfs) please take a snapshot with the -[Wayback Machine](https://web.archive.org/) and use the link to that snapshot -in your article. +[Wayback Machine](https://web.archive.org/) and use the link to that snapshot in +your article. Don't forget to add your new article to the `SUMMARY.md` to let it be rendered to the book. @@ -60,8 +63,10 @@ give early feedback (see the following section). In order to have a consistent style across the book, we suggest to: -- Follow the official Rust book's [style guide](https://github.com/rust-lang/book/blob/master/style-guide.md). -- Follow [RFC 1574](https://github.com/rust-lang/rfcs/blob/master/text/1574-more-api-documentation-conventions.md#appendix-a-full-conventions-text). +- Follow the official Rust book's + [style guide](https://github.com/rust-lang/book/blob/master/style-guide.md). +- Follow + [RFC 1574](https://github.com/rust-lang/rfcs/blob/master/text/1574-more-api-documentation-conventions.md#appendix-a-full-conventions-text). Tl;dr: - Prefer full types name. For example `Option` instead of `Option`. - Prefer line comments (`//`) over block comments (`/* */`) where applicable. @@ -73,10 +78,11 @@ the book builds and `mdbook test` to make sure that code examples are correct. ### Markdown lint -To make sure the files comply with our Markdown style we use [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). -To spare you some manual work to get through the CI test you can use the -following commands to automatically fix most of the emerging problems when -writing Markdown files. +To make sure the files comply with our Markdown style we use +[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). To spare +you some manual work to get through the CI test you can use the following +commands to automatically fix most of the emerging problems when writing +Markdown files. - Install: @@ -96,8 +102,8 @@ writing Markdown files. "Release early and often!" also applies to pull requests! -Once your article has some visible work, create a `[WIP]` draft pull request -and give it a description of what you did or want to do. Early reviews of the +Once your article has some visible work, create a `[WIP]` draft pull request and +give it a description of what you did or want to do. Early reviews of the community are not meant as an offense but to give feedback. A good principle: "Work together, share ideas, teach others." diff --git a/README.md b/README.md index 3d960a8..cdad791 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,24 @@ language that you can read [here](https://rust-unofficial.github.io/patterns/). ## Contributing You are missing content in this repository that can be helpful for others, and -you are eager to explain it? Awesome! We are always happy about new contributions -(e.g. elaboration or corrections on certain topics) to this project. +you are eager to explain it? Awesome! We are always happy about new +contributions (e.g. elaboration or corrections on certain topics) to this +project. -You can check the [Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116) -for all the patterns, anti-patterns, and idioms that could be added. +You can check the +[Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116) for all +the patterns, anti-patterns, and idioms that could be added. -We suggest reading our [Contribution guide](./CONTRIBUTING.md) to get more information -on how contributing to this repository works. +We suggest reading our [Contribution guide](./CONTRIBUTING.md) to get more +information on how contributing to this repository works. ## Building with mdbook This book is built with [mdbook](https://rust-lang.github.io/mdBook/). You can install it by running `cargo install mdbook`. -If you want to build it locally you can run one of these two commands in the root -directory of the repository: +If you want to build it locally you can run one of these two commands in the +root directory of the repository: - `mdbook build` @@ -35,4 +37,5 @@ directory of the repository: ## License -The content of this repository is licensed under **MPL-2.0**; see [LICENSE](./LICENSE). +The content of this repository is licensed under **MPL-2.0**; see +[LICENSE](./LICENSE). diff --git a/dprint.json b/dprint.json index 66ee5b5..c4b0b9f 100644 --- a/dprint.json +++ b/dprint.json @@ -1,11 +1,29 @@ { - "lineWidth": 80, - "markdown": {}, - "includes": [ - "**/*.{md}" - ], - "excludes": [], - "plugins": [ - "https://plugins.dprint.dev/markdown-0.15.2.wasm" - ] -} \ No newline at end of file + "lineWidth": 80, + "markdown": { + "lineWidth": 80, + "emphasisKind": "asterisks", + "strongKind": "asterisks", + "textWrap": "always" + }, + "toml": { + "lineWidth": 80 + }, + "json": { + "lineWidth": 80, + "indentWidth": 4 + }, + "includes": [ + "**/*.{md}", + "**/*.{toml}", + "**/*.{json}" + ], + "excludes": [ + "target/**/*" + ], + "plugins": [ + "https://plugins.dprint.dev/markdown-0.15.2.wasm", + "https://plugins.dprint.dev/toml-0.5.4.wasm", + "https://plugins.dprint.dev/json-0.17.4.wasm" + ] +} diff --git a/src/additional_resources/design-principles.md b/src/additional_resources/design-principles.md index d20ff91..daaea3b 100644 --- a/src/additional_resources/design-principles.md +++ b/src/additional_resources/design-principles.md @@ -7,8 +7,8 @@ ## [SOLID](https://en.wikipedia.org/wiki/SOLID) - [Single Responsibility Principle (SRP)](https://en.wikipedia.org/wiki/Single-responsibility_principle): - A class should only have a single responsibility, that is, only changes to - one part of the software's specification should be able to affect the + A class should only have a single responsibility, that is, only changes to one + part of the software's specification should be able to affect the specification of the class. - [Open/Closed Principle (OCP)](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle): "Software entities ... should be open for extension, but closed for @@ -30,8 +30,8 @@ representation within a system" ## [KISS principle](https://en.wikipedia.org/wiki/KISS_principle) most systems work best if they are kept simple rather than made complicated; -therefore, simplicity should be a key goal in design, and unnecessary -complexity should be avoided +therefore, simplicity should be a key goal in design, and unnecessary complexity +should be avoided ## [Law of Demeter (LoD)](https://en.wikipedia.org/wiki/Law_of_Demeter) @@ -54,14 +54,14 @@ unauthorized parties' direct access to them. ## [Command-Query-Separation(CQS)](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation) -“Functions should not produce abstract side effects...only commands -(procedures) will be permitted to produce side effects.” - Bertrand Meyer: -Object-Oriented Software Construction +“Functions should not produce abstract side effects...only commands (procedures) +will be permitted to produce side effects.” - Bertrand Meyer: Object-Oriented +Software Construction ## [Principle of least astonishment (POLA)](https://en.wikipedia.org/wiki/Principle_of_least_astonishment) -a component of a system should behave in a way that most users will expect it -to behave. The behavior should not astonish or surprise users +a component of a system should behave in a way that most users will expect it to +behave. The behavior should not astonish or surprise users ## Linguistic-Modular-Units @@ -70,8 +70,8 @@ Meyer: Object-Oriented Software Construction ## Self-Documentation -“The designer of a module should strive to make all information about the -module part of the module itself.” - Bertrand Meyer: Object-Oriented Software +“The designer of a module should strive to make all information about the module +part of the module itself.” - Bertrand Meyer: Object-Oriented Software Construction ## Uniform-Access @@ -82,14 +82,13 @@ through computation.” - Bertrand Meyer: Object-Oriented Software Construction ## Single-Choice -“Whenever a software system must support a set of alternatives, one and only -one module in the system should know their exhaustive list.” - Bertrand Meyer: +“Whenever a software system must support a set of alternatives, one and only one +module in the system should know their exhaustive list.” - Bertrand Meyer: Object-Oriented Software Construction ## Persistence-Closure “Whenever a storage mechanism stores an object, it must store with it the -dependents of that object. Whenever a retrieval mechanism retrieves a -previously stored object, it must also retrieve any dependent of that object -that has not yet been retrieved.” - Bertrand Meyer: Object-Oriented Software -Construction +dependents of that object. Whenever a retrieval mechanism retrieves a previously +stored object, it must also retrieve any dependent of that object that has not +yet been retrieved.” - Bertrand Meyer: Object-Oriented Software Construction diff --git a/src/anti_patterns/borrow_clone.md b/src/anti_patterns/borrow_clone.md index f5806aa..97482b4 100644 --- a/src/anti_patterns/borrow_clone.md +++ b/src/anti_patterns/borrow_clone.md @@ -39,8 +39,8 @@ There are special cases -- `Rc` is designed to handle clones intelligently. It internally manages exactly one copy of the data, and cloning it will only clone the reference. -There is also `Arc` which provides shared ownership of a value of type T -that is allocated in the heap. Invoking `.clone()` on `Arc` produces a new `Arc` +There is also `Arc` which provides shared ownership of a value of type T that +is allocated in the heap. Invoking `.clone()` on `Arc` produces a new `Arc` instance, which points to the same allocation on the heap as the source `Arc`, while increasing a reference count. @@ -48,22 +48,26 @@ In general, clones should be deliberate, with full understanding of the consequences. If a clone is used to make a borrow checker error disappear, that's a good indication this anti-pattern may be in use. -Even though `.clone()` is an indication of a bad pattern, sometimes -**it is fine to write inefficient code**, in cases such as when: +Even though `.clone()` is an indication of a bad pattern, sometimes **it is fine +to write inefficient code**, in cases such as when: - the developer is still new to ownership -- the code doesn't have great speed or memory constraints - (like hackathon projects or prototypes) +- the code doesn't have great speed or memory constraints (like hackathon + projects or prototypes) - satisfying the borrow checker is really complicated, and you prefer to optimize readability over performance -If an unnecessary clone is suspected, The [Rust Book's chapter on Ownership](https://doc.rust-lang.org/book/ownership.html) -should be understood fully before assessing whether the clone is required or not. +If an unnecessary clone is suspected, The +[Rust Book's chapter on Ownership](https://doc.rust-lang.org/book/ownership.html) +should be understood fully before assessing whether the clone is required or +not. -Also be sure to always run `cargo clippy` in your project, which will detect some -cases in which `.clone()` is not necessary, like [1](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone), +Also be sure to always run `cargo clippy` in your project, which will detect +some cases in which `.clone()` is not necessary, like +[1](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone), [2](https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy), -[3](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone) or [4](https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref). +[3](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone) or +[4](https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref). ## See also diff --git a/src/anti_patterns/deny-warnings.md b/src/anti_patterns/deny-warnings.md index ec4bbda..e5ca5f9 100644 --- a/src/anti_patterns/deny-warnings.md +++ b/src/anti_patterns/deny-warnings.md @@ -53,8 +53,9 @@ This can be done by any individual developer (or be set in a CI tool like Travis, but remember that this may break the build when something changes) without requiring a change to the code. -Alternatively, we can specify the lints that we want to `deny` in the code. -Here is a list of warning lints that is (hopefully) safe to deny (as of Rustc 1.48.0): +Alternatively, we can specify the lints that we want to `deny` in the code. Here +is a list of warning lints that is (hopefully) safe to deny (as of Rustc +1.48.0): ```rust,ignore #![deny(bad_style, diff --git a/src/anti_patterns/deref.md b/src/anti_patterns/deref.md index b6f8f78..5e04f88 100644 --- a/src/anti_patterns/deref.md +++ b/src/anti_patterns/deref.md @@ -107,7 +107,8 @@ dispatch to `Foo` manually. We do intend to add a mechanism for inheritance similar to this to Rust, but it is likely to be some time before it reaches stable Rust. See these [blog](http://aturon.github.io/blog/2015/09/18/reuse/) [posts](http://smallcultfollowing.com/babysteps/blog/2015/10/08/virtual-structs-part-4-extended-enums-and-thin-traits/) -and this [RFC issue](https://github.com/rust-lang/rfcs/issues/349) for more details. +and this [RFC issue](https://github.com/rust-lang/rfcs/issues/349) for more +details. The `Deref` trait is designed for the implementation of custom pointer types. The intention is that it will take a pointer-to-`T` to a `T`, not convert @@ -123,6 +124,7 @@ conversion between arbitrary types. ## See also - [Collections are smart pointers idiom](../idioms/deref.md). -- Delegation crates for less boilerplate like [delegate](https://crates.io/crates/delegate) - or [ambassador](https://crates.io/crates/ambassador) +- Delegation crates for less boilerplate like + [delegate](https://crates.io/crates/delegate) or + [ambassador](https://crates.io/crates/ambassador) - [Documentation for `Deref` trait](https://doc.rust-lang.org/std/ops/trait.Deref.html). diff --git a/src/anti_patterns/index.md b/src/anti_patterns/index.md index 84107e0..896a123 100644 --- a/src/anti_patterns/index.md +++ b/src/anti_patterns/index.md @@ -1,8 +1,8 @@ # Anti-patterns -An [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern) is a solution to -a "recurring problem that is usually ineffective and risks being highly +An [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern) is a solution to a +"recurring problem that is usually ineffective and risks being highly counterproductive". Just as valuable as knowing how to solve a problem, is -knowing how _not_ to solve it. Anti-patterns give us great counter-examples to +knowing how *not* to solve it. Anti-patterns give us great counter-examples to consider relative to design patterns. Anti-patterns are not confined to code. For example, a process can be an anti-pattern, too. diff --git a/src/functional/generics-type-classes.md b/src/functional/generics-type-classes.md index 5048ca7..4445383 100644 --- a/src/functional/generics-type-classes.md +++ b/src/functional/generics-type-classes.md @@ -4,9 +4,9 @@ Rust's type system is designed more like functional languages (like Haskell) rather than imperative languages (like Java and C++). As a result, Rust can turn -many kinds of programming problems into "static typing" problems. This is one -of the biggest wins of choosing a functional language, and is critical to many -of Rust's compile time guarantees. +many kinds of programming problems into "static typing" problems. This is one of +the biggest wins of choosing a functional language, and is critical to many of +Rust's compile time guarantees. A key part of this idea is the way generic types work. In C++ and Java, for example, generic types are a meta-programming construct for the compiler. @@ -16,8 +16,8 @@ different types filled in. In Rust, a generic type parameter creates what is known in functional languages as a "type class constraint", and each different parameter filled in by an end -user _actually changes the type_. In other words, `Vec` and `Vec` -_are two different types_, which are recognized as distinct by all parts of the +user *actually changes the type*. In other words, `Vec` and `Vec` +*are two different types*, which are recognized as distinct by all parts of the type system. This is called **monomorphization**, where different types are created from @@ -37,9 +37,9 @@ makes them more usable while remaining safe. ## Example -Suppose you are designing a storage server for a series of lab machines. -Because of the software involved, there are two different protocols you need -to support: BOOTP (for PXE network boot), and NFS (for remote mount storage). +Suppose you are designing a storage server for a series of lab machines. Because +of the software involved, there are two different protocols you need to support: +BOOTP (for PXE network boot), and NFS (for remote mount storage). Your goal is to have one program, written in Rust, which can handle both of them. It will have protocol handlers and listen for both kinds of requests. The @@ -63,10 +63,10 @@ struct FileDownloadRequest { } ``` -This design might work well enough. But now suppose you needed to support -adding metadata that was _protocol specific_. For example, with NFS, you -wanted to determine what their mount point was in order to enforce additional -security rules. +This design might work well enough. But now suppose you needed to support adding +metadata that was *protocol specific*. For example, with NFS, you wanted to +determine what their mount point was in order to enforce additional security +rules. The way the current struct is designed leaves the protocol decision until runtime. That means any method that applies to one protocol and not the other @@ -101,7 +101,7 @@ request types were confused. After all, the entire path of the user's code, including what functions from the library they use, will know whether a request is an NFS request or a BOOTP request. -In Rust, this is actually possible! The solution is to _add a generic type_ in +In Rust, this is actually possible! The solution is to *add a generic type* in order to split the API. Here is what that looks like: @@ -187,8 +187,7 @@ fn main() { } ``` -With this approach, if the user were to make a mistake and use the wrong -type; +With this approach, if the user were to make a mistake and use the wrong type; ```rust,ignore fn main() { @@ -230,8 +229,8 @@ improve in the future. initialization, consider the [Builder Pattern](../patterns/creational/builder.md) instead. -- If the API between types does not change -- only the behavior does -- then - the [Strategy Pattern](../patterns/behavioural/strategy.md) is better used +- If the API between types does not change -- only the behavior does -- then the + [Strategy Pattern](../patterns/behavioural/strategy.md) is better used instead. ## See also @@ -239,8 +238,8 @@ improve in the future. This pattern is used throughout the standard library: - `Vec` can be cast from a String, unlike every other type of `Vec`.[^1] -- They can also be cast into a binary heap, but only if they contain a type - that implements the `Ord` trait.[^2] +- They can also be cast into a binary heap, but only if they contain a type that + implements the `Ord` trait.[^2] - The `to_string` method was specialized for `Cow` only of type `str`.[^3] It is also used by several popular crates to allow API flexibility: @@ -248,8 +247,8 @@ It is also used by several popular crates to allow API flexibility: - The `embedded-hal` ecosystem used for embedded devices makes extensive use of this pattern. For example, it allows statically verifying the configuration of device registers used to control embedded pins. When a pin is put into a mode, - it returns a `Pin` struct, whose generic determines the functions - usable in that mode, which are not on the `Pin` itself. [^4] + it returns a `Pin` struct, whose generic determines the functions usable + in that mode, which are not on the `Pin` itself. [^4] - The `hyper` HTTP client library uses this to expose rich APIs for different pluggable requests. Clients with different connectors have different methods @@ -260,11 +259,14 @@ It is also used by several popular crates to allow API flexibility: internal state or invariant -- is implemented in Rust using the same basic concept, and a slightly different technique. [^6] -[^1]: See: [impl From\ for Vec\](https://doc.rust-lang.org/1.59.0/src/std/ffi/c_str.rs.html#803-811) +[^1]: See: +[impl From\ for Vec\](https://doc.rust-lang.org/1.59.0/src/std/ffi/c_str.rs.html#803-811) -[^2]: See: [impl\ From\\> for BinaryHeap\](https://doc.rust-lang.org/stable/src/alloc/collections/binary_heap.rs.html#1345-1354) +[^2]: See: +[impl\ FromIterator\ for BinaryHeap\](https://web.archive.org/web/20201030132806/https://doc.rust-lang.org/stable/src/alloc/collections/binary_heap.rs.html#1330-1335) -[^3]: See: [impl\<'\_\> ToString for Cow\<'\_, str>](https://doc.rust-lang.org/stable/src/alloc/string.rs.html#2235-2240) +[^3]: See: +[impl\<'\_\> ToString for Cow\<'\_, str>](https://doc.rust-lang.org/stable/src/alloc/string.rs.html#2235-2240) [^4]: Example: [https://docs.rs/stm32f30x-hal/0.1.0/stm32f30x_hal/gpio/gpioa/struct.PA0.html](https://docs.rs/stm32f30x-hal/0.1.0/stm32f30x_hal/gpio/gpioa/struct.PA0.html) diff --git a/src/functional/index.md b/src/functional/index.md index 8be691f..e9324b5 100644 --- a/src/functional/index.md +++ b/src/functional/index.md @@ -1,10 +1,11 @@ # Functional Usage of Rust Rust is an imperative language, but it follows many -[functional programming](https://en.wikipedia.org/wiki/Functional_programming) paradigms. +[functional programming](https://en.wikipedia.org/wiki/Functional_programming) +paradigms. -> In computer science, _functional programming_ is a programming paradigm where -> programs are constructed by applying and composing functions. -> It is a declarative programming paradigm in which function definitions are -> trees of expressions that each return a value, rather than a sequence of -> imperative statements which change the state of the program. +> In computer science, *functional programming* is a programming paradigm where +> programs are constructed by applying and composing functions. It is a +> declarative programming paradigm in which function definitions are trees of +> expressions that each return a value, rather than a sequence of imperative +> statements which change the state of the program. diff --git a/src/functional/lenses.md b/src/functional/lenses.md index 3b160df..d38354b 100644 --- a/src/functional/lenses.md +++ b/src/functional/lenses.md @@ -1,21 +1,20 @@ # Lenses and Prisms This is a pure functional concept that is not frequently used in Rust. -Nevertheless, exploring the concept may be helpful to understand other -patterns in Rust APIs, such as [visitors](../patterns/behavioural/visitor.md). -They also have niche use cases. +Nevertheless, exploring the concept may be helpful to understand other patterns +in Rust APIs, such as [visitors](../patterns/behavioural/visitor.md). They also +have niche use cases. ## Lenses: Uniform Access Across Types -A lens is a concept from functional programming languages that allows -accessing parts of a data type in an abstract, unified way.[^1] -In basic concept, it is similar to the way Rust traits work with type erasure, -but it has a bit more power and flexibility. +A lens is a concept from functional programming languages that allows accessing +parts of a data type in an abstract, unified way.[^1] In basic concept, it is +similar to the way Rust traits work with type erasure, but it has a bit more +power and flexibility. -For example, suppose a bank contains several JSON formats for customer -data. -This is because they come from different databases or legacy systems. -One database contains the data needed to perform credit checks: +For example, suppose a bank contains several JSON formats for customer data. +This is because they come from different databases or legacy systems. One +database contains the data needed to perform credit checks: ```json { "name": "Jane Doe", @@ -102,13 +101,13 @@ fn unique_ids_iter(iterator: I) -> HashSet ``` Lenses, however, allow the code supporting customer ID to be moved from the -_type_ to the _accessor function_. -Rather than implementing a trait on each type, all matching structures can -simply be accessed the same way. +*type* to the *accessor function*. Rather than implementing a trait on each +type, all matching structures can simply be accessed the same way. While the Rust language itself does not support this (type erasure is the -preferred solution to this problem), the [lens-rs crate](https://github.com/TOETOE55/lens-rs/blob/master/guide.md) allows code -that feels like this to be written with macros: +preferred solution to this problem), the +[lens-rs crate](https://github.com/TOETOE55/lens-rs/blob/master/guide.md) allows +code that feels like this to be written with macros: ```rust,ignore use std::collections::HashSet; @@ -146,44 +145,39 @@ where } ``` -The version of `unique_ids_lens` shown here allows any type to be in the iterator, -so long as it has an attribute called `customer_id` which can be accessed by -the function. -This is how most functional programming languages operate on lenses. +The version of `unique_ids_lens` shown here allows any type to be in the +iterator, so long as it has an attribute called `customer_id` which can be +accessed by the function. This is how most functional programming languages +operate on lenses. -Rather than macros, they achieve this with a technique known as "currying". -That is, they "partially construct" the function, leaving the type of the -final parameter (the value being operated on) unfilled until the function is -called. +Rather than macros, they achieve this with a technique known as "currying". That +is, they "partially construct" the function, leaving the type of the final +parameter (the value being operated on) unfilled until the function is called. Thus it can be called with different types dynamically even from one place in -the code. -That is what the `optics!` and `view_ref` in the example above simulates. +the code. That is what the `optics!` and `view_ref` in the example above +simulates. -The functional approach need not be restricted to accessing members. -More powerful lenses can be created which both _set_ and _get_ data in a -structure. +The functional approach need not be restricted to accessing members. More +powerful lenses can be created which both *set* and *get* data in a structure. But the concept really becomes interesting when used as a building block for -composition. -That is where the concept appears more clearly in Rust. +composition. That is where the concept appears more clearly in Rust. ## Prisms: A Higher-Order form of "Optics" -A simple function such as `unique_ids_lens` above operates on a single lens. -A _prism_ is a function that operates on a _family_ of lenses. -It is one conceptual level higher, using lenses as a building block, and -continuing the metaphor, is part of a family of "optics". -It is the main one that is useful in understanding Rust APIs, so will be the -focus here. +A simple function such as `unique_ids_lens` above operates on a single lens. A +*prism* is a function that operates on a *family* of lenses. It is one +conceptual level higher, using lenses as a building block, and continuing the +metaphor, is part of a family of "optics". It is the main one that is useful in +understanding Rust APIs, so will be the focus here. The same way that traits allow "lens-like" design with static polymorphism and dynamic dispatch, prism-like designs appear in Rust APIs which split problems -into multiple associated types to be composed. -A good example of this is the traits in the parsing crate _Serde_. +into multiple associated types to be composed. A good example of this is the +traits in the parsing crate *Serde*. -Trying to understand the way _Serde_ works by only reading the API is a -challenge, especially the first time. -Consider the `Deserializer` trait, implemented by some type in any library -which parses a new format: +Trying to understand the way *Serde* works by only reading the API is a +challenge, especially the first time. Consider the `Deserializer` trait, +implemented by some type in any library which parses a new format: ```rust,ignore pub trait Deserializer<'de>: Sized { @@ -206,8 +200,8 @@ value, this looks odd. Why are all the return types type erased? -To understand that, we need to keep the lens concept in mind and look at -the definition of the `Visitor` type that is passed in generically: +To understand that, we need to keep the lens concept in mind and look at the +definition of the `Visitor` type that is passed in generically: ```rust,ignore pub trait Visitor<'de>: Sized { @@ -229,50 +223,47 @@ pub trait Visitor<'de>: Sized { } ``` -The job of the `Visitor` type is to construct values in the _Serde_ data model, +The job of the `Visitor` type is to construct values in the *Serde* data model, which are represented by its associated `Value` type. -These values represent parts of the Rust value being deserialized. -If this fails, it returns an `Error` type - an error type determined by the +These values represent parts of the Rust value being deserialized. If this +fails, it returns an `Error` type - an error type determined by the `Deserializer` when its methods were called. This highlights that `Deserializer` is similar to `CustomerId` from earlier, allowing any format parser which implements it to create `Value`s based on what -it parsed. -The `Value` trait is acting like a lens in functional programming languages. +it parsed. The `Value` trait is acting like a lens in functional programming +languages. But unlike the `CustomerId` trait, the return types of `Visitor` methods are -_generic_, and the concrete `Value` type is _determined by the Visitor itself_. +*generic*, and the concrete `Value` type is *determined by the Visitor itself*. -Instead of acting as one lens, it effectively acts as a family of -lenses, one for each concrete type of `Visitor`. +Instead of acting as one lens, it effectively acts as a family of lenses, one +for each concrete type of `Visitor`. The `Deserializer` API is based on having a generic set of "lenses" work across -a set of other generic types for "observation". -It is a _prism_. +a set of other generic types for "observation". It is a *prism*. For example, consider the identity record from earlier but simplified: ```json -{ "name": "Jane Doe", - "customer_id": 1048576332, -} +{ "name": "Jane Doe", "customer_id": 1048576332 } ``` -How would the _Serde_ library deserialize this JSON into `struct CreditRecord`? +How would the *Serde* library deserialize this JSON into `struct CreditRecord`? 1. The user would call a library function to deserialize the data. This would create a `Deserializer` based on the JSON format. -1. Based on the fields in the struct, a `Visitor` would be created (more on - that in a moment) which knows how to create each type in a generic data - model that was needed to represent it: `u64` and `String`. +1. Based on the fields in the struct, a `Visitor` would be created (more on that + in a moment) which knows how to create each type in a generic data model that + was needed to represent it: `u64` and `String`. 1. The deserializer would make calls to the `Visitor` as it parsed items. 1. The `Visitor` would indicate if the items found were expected, and if not, raise an error to indicate deserialization has failed. For our very simple structure above, the expected pattern would be: -1. Visit a map (_Serde_'s equvialent to `HashMap` or JSON's dictionary). +1. Visit a map (*Serde*'s equvialent to `HashMap` or JSON's dictionary). 1. Visit a string key called "name". 1. Visit a string value, which will go into the `name` field. 1. Visit a string key called "customer_id". @@ -282,11 +273,11 @@ For our very simple structure above, the expected pattern would be: But what determines which "observation" pattern is expected? A functional programming language would be able to use currying to create -reflection of each type based on the type itself. -Rust does not support that, so every single type would need to have its own -code written based on its fields and their properties. +reflection of each type based on the type itself. Rust does not support that, so +every single type would need to have its own code written based on its fields +and their properties. -_Serde_ solves this usability challenge with a derive macro: +*Serde* solves this usability challenge with a derive macro: ```rust,ignore use serde::Deserialize; @@ -311,49 +302,48 @@ pub trait Deserialize<'de>: Sized { } ``` -This is the function that determines how to create the struct itself. -Code is generated based on the struct's fields. -When the parsing library is called - in our example, a JSON parsing library - -it creates a `Deserializer` and calls `Type::deserialize` with it as a -parameter. +This is the function that determines how to create the struct itself. Code is +generated based on the struct's fields. When the parsing library is called - in +our example, a JSON parsing library - it creates a `Deserializer` and calls +`Type::deserialize` with it as a parameter. The `deserialize` code will then create a `Visitor` which will have its calls -"refracted" by the `Deserializer`. -If everything goes well, eventually that `Visitor` will construct a value -corresponding to the type being parsed and return it. +"refracted" by the `Deserializer`. If everything goes well, eventually that +`Visitor` will construct a value corresponding to the type being parsed and +return it. -For a complete example, see the [_Serde_ documentation](https://serde.rs/deserialize-struct.html). +For a complete example, see the +[*Serde* documentation](https://serde.rs/deserialize-struct.html). -To wrap up, this is the power of _Serde_: +To wrap up, this is the power of *Serde*: -1. The structure being parsed is represented by an `impl` block for `Deserialize` +1. The structure being parsed is represented by an `impl` block for + `Deserialize` 1. The input data format (e.g. JSON) is represented by a `Deserializer` called by `Deserialize` 1. The `Deserializer` acts like a prism which "refracts" lens-like `Visitor` calls which actually build the data value The result is that types to be deserialized only implement the "top layer" of -the API, and file formats only need to implement the "bottom layer". -Each piece can then "just work" with the rest of the ecosystem, since generic -types will bridge them. +the API, and file formats only need to implement the "bottom layer". Each piece +can then "just work" with the rest of the ecosystem, since generic types will +bridge them. To emphasize, the only reason this model works on any format and any type is -because the `Deserializer` trait's output type **is specified by the -implementor of `Visitor` it is passed**, rather than being tied to one specific -type. -This was not true in the account example earlier. +because the `Deserializer` trait's output type **is specified by the implementor +of `Visitor` it is passed**, rather than being tied to one specific type. This +was not true in the account example earlier. -Rust's generic-inspired type system can bring it close to these concepts and -use their power, as shown in this API design. -But it may also need procedural macros to create bridges for its generics. +Rust's generic-inspired type system can bring it close to these concepts and use +their power, as shown in this API design. But it may also need procedural macros +to create bridges for its generics. ## See Also - [lens-rs crate](https://crates.io/crates/lens-rs) for a pre-built lenses implementation, with a cleaner interface than these examples -- [serde](https://serde.rs) itself, which makes these concepts intuitive for - end users (i.e. defining the structs) without needing to undestand the - details +- [serde](https://serde.rs) itself, which makes these concepts intuitive for end + users (i.e. defining the structs) without needing to undestand the details - [luminance](https://github.com/phaazon/luminance-rs) is a crate for drawing computer graphics that uses lens API design, including proceducal macros to create full prisms for buffers of different pixel types that remain generic diff --git a/src/functional/paradigms.md b/src/functional/paradigms.md index 099fd78..4f7c11b 100644 --- a/src/functional/paradigms.md +++ b/src/functional/paradigms.md @@ -1,9 +1,9 @@ # Programming paradigms -One of the biggest hurdles to understanding functional programs when coming -from an imperative background is the shift in thinking. Imperative programs -describe **how** to do something, whereas declarative programs describe -**what** to do. Let's sum the numbers from 1 to 10 to show this. +One of the biggest hurdles to understanding functional programs when coming from +an imperative background is the shift in thinking. Imperative programs describe +**how** to do something, whereas declarative programs describe **what** to do. +Let's sum the numbers from 1 to 10 to show this. ## Imperative @@ -16,9 +16,8 @@ println!("{}", sum); ``` With imperative programs, we have to play compiler to see what is happening. -Here, we start with a `sum` of `0`. -Next, we iterate through the range from 1 to 10. -Each time through the loop, we add the corresponding value in the range. +Here, we start with a `sum` of `0`. Next, we iterate through the range from 1 +to 10. Each time through the loop, we add the corresponding value in the range. Then we print it out. | `i` | `sum` | @@ -43,17 +42,17 @@ of steps. println!("{}", (1..11).fold(0, |a, b| a + b)); ``` -Whoa! This is really different! What's going on here? -Remember that with declarative programs we are describing **what** to do, -rather than **how** to do it. `fold` is a function that [composes](https://en.wikipedia.org/wiki/Function_composition) -functions. The name is a convention from Haskell. +Whoa! This is really different! What's going on here? Remember that with +declarative programs we are describing **what** to do, rather than **how** to do +it. `fold` is a function that +[composes](https://en.wikipedia.org/wiki/Function_composition) functions. The +name is a convention from Haskell. -Here, we are composing functions of addition (this closure: `|a, b| a + b`) -with a range from 1 to 10. The `0` is the starting point, so `a` is `0` at -first. `b` is the first element of the range, `1`. `0 + 1 = 1` is the result. -So now we `fold` again, with `a = 1`, `b = 2` and so `1 + 2 = 3` is the next -result. This process continues until we get to the last element in the range, -`10`. +Here, we are composing functions of addition (this closure: `|a, b| a + b`) with +a range from 1 to 10. The `0` is the starting point, so `a` is `0` at first. `b` +is the first element of the range, `1`. `0 + 1 = 1` is the result. So now we +`fold` again, with `a = 1`, `b = 2` and so `1 + 2 = 3` is the next result. This +process continues until we get to the last element in the range, `10`. | `a` | `b` | result | | :-: | :-: | :----: | diff --git a/src/idioms/coercion-arguments.md b/src/idioms/coercion-arguments.md index e37d63c..9291d90 100644 --- a/src/idioms/coercion-arguments.md +++ b/src/idioms/coercion-arguments.md @@ -3,12 +3,11 @@ ## Description Using a target of a deref coercion can increase the flexibility of your code -when you are deciding which argument type to use for a function argument. -In this way, the function will accept more input types. +when you are deciding which argument type to use for a function argument. In +this way, the function will accept more input types. -This is not limited to slice-able or fat pointer types. -In fact, you should always prefer using the **borrowed type** over -**borrowing the owned type**. +This is not limited to slice-able or fat pointer types. In fact, you should +always prefer using the **borrowed type** over **borrowing the owned type**. Such as `&str` over `&String`, `&[T]` over `&Vec`, or `&T` over `&Box`. Using borrowed types you can avoid layers of indirection for those instances @@ -59,10 +58,10 @@ fn main() { } ``` -This works fine because we are passing a `&String` type as a parameter. -If we remove the comments on the last two lines, the example will fail. This -is because a `&str` type will not coerce to a `&String` type. We can fix this -by simply modifying the type for our argument. +This works fine because we are passing a `&String` type as a parameter. If we +remove the comments on the last two lines, the example will fail. This is +because a `&str` type will not coerce to a `&String` type. We can fix this by +simply modifying the type for our argument. For instance, if we change our function declaration to: @@ -77,11 +76,11 @@ Ferris: false Curious: true ``` -But wait, that's not all! There is more to this story. -It's likely that you may say to yourself: that doesn't matter, I will never be -using a `&'static str` as an input anyways (as we did when we used `"Ferris"`). -Even ignoring this special example, you may still find that using `&str` will -give you more flexibility than using a `&String`. +But wait, that's not all! There is more to this story. It's likely that you may +say to yourself: that doesn't matter, I will never be using a `&'static str` as +an input anyways (as we did when we used `"Ferris"`). Even ignoring this special +example, you may still find that using `&str` will give you more flexibility +than using a `&String`. Let's now take an example where someone gives us a sentence, and we want to determine if any of the words in the sentence contain three consecutive vowels. @@ -128,7 +127,8 @@ curious has three consecutive vowels! However, this example will not run when our function is declared with an argument type `&String`. This is because string slices are a `&str` and not a `&String` which would require an allocation to be converted to `&String` which -is not implicit, whereas converting from `String` to `&str` is cheap and implicit. +is not implicit, whereas converting from `String` to `&str` is cheap and +implicit. ## See also diff --git a/src/idioms/concat-format.md b/src/idioms/concat-format.md index 372d86e..8053a9c 100644 --- a/src/idioms/concat-format.md +++ b/src/idioms/concat-format.md @@ -24,7 +24,8 @@ fn say_hello(name: &str) -> String { ## Advantages -Using `format!` is usually the most succinct and readable way to combine strings. +Using `format!` is usually the most succinct and readable way to combine +strings. ## Disadvantages diff --git a/src/idioms/ctor.md b/src/idioms/ctor.md index 13e0cc6..78bab8d 100644 --- a/src/idioms/ctor.md +++ b/src/idioms/ctor.md @@ -2,8 +2,9 @@ ## Description -Rust does not have constructors as a language construct. Instead, the -convention is to use an [associated function][associated function] `new` to create an object: +Rust does not have constructors as a language construct. Instead, the convention +is to use an [associated function][associated function] `new` to create an +object: ````rust /// Time in seconds. @@ -88,11 +89,10 @@ impl Second { } ```` -**Note:** It is common and expected for types to implement both -`Default` and an empty `new` constructor. `new` is the constructor -convention in Rust, and users expect it to exist, so if it is -reasonable for the basic constructor to take no arguments, then it -should, even if it is functionally identical to default. +**Note:** It is common and expected for types to implement both `Default` and an +empty `new` constructor. `new` is the constructor convention in Rust, and users +expect it to exist, so if it is reasonable for the basic constructor to take no +arguments, then it should, even if it is functionally identical to default. **Hint:** The advantage of implementing or deriving `Default` is that your type can now be used where a `Default` implementation is required, most prominently, diff --git a/src/idioms/default.md b/src/idioms/default.md index 3954b8b..f383df3 100644 --- a/src/idioms/default.md +++ b/src/idioms/default.md @@ -2,11 +2,11 @@ ## Description -Many types in Rust have a [constructor]. However, this is _specific_ to the -type; Rust cannot abstract over "everything that has a `new()` method". To -allow this, the [`Default`] trait was conceived, which can be used with -containers and other generic types (e.g. see [`Option::unwrap_or_default()`]). -Notably, some containers already implement it where applicable. +Many types in Rust have a [constructor]. However, this is *specific* to the +type; Rust cannot abstract over "everything that has a `new()` method". To allow +this, the [`Default`] trait was conceived, which can be used with containers and +other generic types (e.g. see [`Option::unwrap_or_default()`]). Notably, some +containers already implement it where applicable. Not only do one-element containers like `Cow`, `Box` or `Arc` implement `Default` for contained `Default` types, one can automatically diff --git a/src/idioms/deref.md b/src/idioms/deref.md index 0f6d9ae..e2f284e 100644 --- a/src/idioms/deref.md +++ b/src/idioms/deref.md @@ -2,9 +2,9 @@ ## Description -Use the [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html) -trait to treat collections like smart pointers, offering owning -and borrowed views of data. +Use the [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html) trait to +treat collections like smart pointers, offering owning and borrowed views of +data. ## Example @@ -36,9 +36,9 @@ Also `String` and `&str` have a similar relation. ## Motivation Ownership and borrowing are key aspects of the Rust language. Data structures -must account for these semantics properly to give a good user -experience. When implementing a data structure that owns its data, offering a -borrowed view of that data allows for more flexible APIs. +must account for these semantics properly to give a good user experience. When +implementing a data structure that owns its data, offering a borrowed view of +that data allows for more flexible APIs. ## Advantages diff --git a/src/idioms/dtor-finally.md b/src/idioms/dtor-finally.md index 4515290..f0f82b2 100644 --- a/src/idioms/dtor-finally.md +++ b/src/idioms/dtor-finally.md @@ -56,7 +56,8 @@ 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. +Requiring an object and `Drop` impl just for finalisation is heavy on +boilerplate. ## Discussion diff --git a/src/idioms/ffi/accepting-strings.md b/src/idioms/ffi/accepting-strings.md index 8e0ca84..f86873f 100644 --- a/src/idioms/ffi/accepting-strings.md +++ b/src/idioms/ffi/accepting-strings.md @@ -16,12 +16,12 @@ The strings used in C have different behaviours to those used in Rust, namely: - C strings are null-terminated while Rust strings store their length - C strings can contain any arbitrary non-zero byte while Rust strings must be UTF-8 -- C strings are accessed and manipulated using `unsafe` pointer operations - while interactions with Rust strings go through safe methods +- C strings are accessed and manipulated using `unsafe` pointer operations while + interactions with Rust strings go through safe methods The Rust standard library comes with C equivalents of Rust's `String` and `&str` -called `CString` and `&CStr`, that allow us to avoid a lot of the complexity -and `unsafe` code involved in converting between C strings and Rust strings. +called `CString` and `&CStr`, that allow us to avoid a lot of the complexity and +`unsafe` code involved in converting between C strings and Rust strings. The `&CStr` type also allows us to work with borrowed data, meaning passing strings between Rust and C is a zero-cost operation. @@ -70,8 +70,7 @@ pub mod unsafe_module { The example is is written to ensure that: 1. The `unsafe` block is as small as possible. -2. The pointer with an "untracked" lifetime becomes a "tracked" shared - reference +2. The pointer with an "untracked" lifetime becomes a "tracked" shared reference Consider an alternative, where the string is actually copied: @@ -121,22 +120,21 @@ This code in inferior to the original in two respects: 1. There is much more `unsafe` code, and more importantly, more invariants it must uphold. -2. Due to the extensive arithmetic required, there is a bug in this version - that cases Rust `undefined behaviour`. +2. Due to the extensive arithmetic required, there is a bug in this version that + cases Rust `undefined behaviour`. The bug here is a simple mistake in pointer arithmetic: the string was copied, all `msg_len` bytes of it. However, the `NUL` terminator at the end was not. -The Vector then had its size _set_ to the length of the _zero padded string_ -- -rather than _resized_ to it, which could have added a zero at the end. -As a result, the last byte in the Vector is uninitialized memory. -When the `CString` is created at the bottom of the block, its read of the -Vector will cause `undefined behaviour`! +The Vector then had its size *set* to the length of the *zero padded string* -- +rather than *resized* to it, which could have added a zero at the end. As a +result, the last byte in the Vector is uninitialized memory. When the `CString` +is created at the bottom of the block, its read of the Vector will cause +`undefined behaviour`! -Like many such issues, this would be difficult issue to track down. -Sometimes it would panic because the string was not `UTF-8`, sometimes it would -put a weird character at the end of the string, sometimes it would just -completely crash. +Like many such issues, this would be difficult issue to track down. Sometimes it +would panic because the string was not `UTF-8`, sometimes it would put a weird +character at the end of the string, sometimes it would just completely crash. ## Disadvantages diff --git a/src/idioms/ffi/errors.md b/src/idioms/ffi/errors.md index f254aea..5d5bed0 100644 --- a/src/idioms/ffi/errors.md +++ b/src/idioms/ffi/errors.md @@ -2,9 +2,9 @@ ## Description -In foreign languages like C, errors are represented by return codes. -However, Rust's type system allows much more rich error information to be -captured and propogated through a full type. +In foreign languages like C, errors are represented by return codes. However, +Rust's type system allows much more rich error information to be captured and +propogated through a full type. This best practice shows different kinds of error codes, and how to expose them in a usable way: @@ -135,5 +135,5 @@ while not compromising the Rust code's API at all. ## Disadvantages -It's a lot of typing, and some types may not be able to be converted easily -to C. +It's a lot of typing, and some types may not be able to be converted easily to +C. diff --git a/src/idioms/ffi/intro.md b/src/idioms/ffi/intro.md index 93b4862..e8bc4cd 100644 --- a/src/idioms/ffi/intro.md +++ b/src/idioms/ffi/intro.md @@ -1,8 +1,8 @@ # FFI Idioms -Writing FFI code is an entire course in itself. -However, there are several idioms here that can act as pointers, and avoid -traps for inexperienced users of `unsafe` Rust. +Writing FFI code is an entire course in itself. However, there are several +idioms here that can act as pointers, and avoid traps for inexperienced users of +`unsafe` Rust. This section contains idioms that may be useful when doing FFI. diff --git a/src/idioms/ffi/passing-strings.md b/src/idioms/ffi/passing-strings.md index cd54061..3b4056d 100644 --- a/src/idioms/ffi/passing-strings.md +++ b/src/idioms/ffi/passing-strings.md @@ -17,11 +17,11 @@ Rust has built-in support for C-style strings with its `CString` and `CStr` types. However, there are different approaches one can take with strings that are being sent to a foreign function call from a Rust function. -The best practice is simple: use `CString` in such a way as to minimize -`unsafe` code. However, a secondary caveat is that -_the object must live long enough_, meaning the lifetime should be maximized. -In addition, the documentation explains that "round-tripping" a `CString` after -modification is UB, so additional work is necessary in that case. +The best practice is simple: use `CString` in such a way as to minimize `unsafe` +code. However, a secondary caveat is that *the object must live long enough*, +meaning the lifetime should be maximized. In addition, the documentation +explains that "round-tripping" a `CString` after modification is UB, so +additional work is necessary in that case. ## Code Example diff --git a/src/idioms/index.md b/src/idioms/index.md index 22ebed6..014e5da 100644 --- a/src/idioms/index.md +++ b/src/idioms/index.md @@ -1,13 +1,11 @@ # Idioms [Idioms](https://en.wikipedia.org/wiki/Programming_idiom) are commonly used -styles, guidelines and patterns largely agreed upon by a community. -Writing idiomatic code allows other developers to understand better what is -happening. +styles, guidelines and patterns largely agreed upon by a community. Writing +idiomatic code allows other developers to understand better what is happening. -After all, the computer only cares about the machine code that is generated -by the compiler. -Instead, the source code is mainly beneficial to the developer. +After all, the computer only cares about the machine code that is generated by +the compiler. Instead, the source code is mainly beneficial to the developer. So, since we have this abstraction layer, why not make it more readable? Remember the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle): diff --git a/src/idioms/mem-replace.md b/src/idioms/mem-replace.md index 1771f2a..ab2d431 100644 --- a/src/idioms/mem-replace.md +++ b/src/idioms/mem-replace.md @@ -58,27 +58,29 @@ fn swizzle(e: &mut MultiVariateEnum) { When working with enums, we may want to change an enum value in place, perhaps to another variant. This is usually done in two phases to keep the borrow -checker happy. In the first phase, we observe the existing value and look at -its parts to decide what to do next. In the second phase we may conditionally -change the value (as in the example above). +checker happy. In the first phase, we observe the existing value and look at its +parts to decide what to do next. In the second phase we may conditionally change +the value (as in the example above). The borrow checker won't allow us to take out `name` of the enum (because -_something_ must be there.) We could of course `.clone()` name and put the clone -into our `MyEnum::B`, but that would be an instance of the [Clone to satisfy the borrow checker](../anti_patterns/borrow_clone.md) anti-pattern. Anyway, we -can avoid the extra allocation by changing `e` with only a mutable borrow. +*something* must be there.) We could of course `.clone()` name and put the clone +into our `MyEnum::B`, but that would be an instance of the +[Clone to satisfy the borrow checker](../anti_patterns/borrow_clone.md) +anti-pattern. Anyway, we can avoid the extra allocation by changing `e` with +only a mutable borrow. `mem::take` lets us swap out the value, replacing it with it's default value, and returning the previous value. For `String`, the default value is an empty `String`, which does not need to allocate. As a result, we get the original -`name` _as an owned value_. We can then wrap this in another enum. +`name` *as an owned value*. We can then wrap this in another enum. **NOTE:** `mem::replace` is very similar, but allows us to specify what to replace the value with. An equivalent to our `mem::take` line would be `mem::replace(name, String::new())`. -Note, however, that if we are using an `Option` and want to replace its -value with a `None`, `Option`’s `take()` method provides a shorter and -more idiomatic alternative. +Note, however, that if we are using an `Option` and want to replace its value +with a `None`, `Option`’s `take()` method provides a shorter and more idiomatic +alternative. ## Advantages @@ -86,13 +88,13 @@ Look ma, no allocation! Also you may feel like Indiana Jones while doing it. ## Disadvantages -This gets a bit wordy. Getting it wrong repeatedly will make you hate the -borrow checker. The compiler may fail to optimize away the double store, -resulting in reduced performance as opposed to what you'd do in unsafe -languages. +This gets a bit wordy. Getting it wrong repeatedly will make you hate the borrow +checker. The compiler may fail to optimize away the double store, resulting in +reduced performance as opposed to what you'd do in unsafe languages. -Furthermore, the type you are taking needs to implement the [`Default` trait](./default.md). However, if the type you're working with doesn't -implement this, you can instead use `mem::replace`. +Furthermore, the type you are taking needs to implement the +[`Default` trait](./default.md). However, if the type you're working with +doesn't implement this, you can instead use `mem::replace`. ## Discussion @@ -107,5 +109,6 @@ like Indiana Jones, replacing the artifact with a bag of sand. ## See also -This gets rid of the [Clone to satisfy the borrow checker](../anti_patterns/borrow_clone.md) +This gets rid of the +[Clone to satisfy the borrow checker](../anti_patterns/borrow_clone.md) anti-pattern in a specific case. diff --git a/src/idioms/on-stack-dyn-dispatch.md b/src/idioms/on-stack-dyn-dispatch.md index 5ecb31a..14a592b 100644 --- a/src/idioms/on-stack-dyn-dispatch.md +++ b/src/idioms/on-stack-dyn-dispatch.md @@ -2,8 +2,8 @@ ## Description -We can dynamically dispatch over multiple values, however, to do so, we need -to declare multiple variables to bind differently-typed objects. To extend the +We can dynamically dispatch over multiple values, however, to do so, we need to +declare multiple variables to bind differently-typed objects. To extend the lifetime as necessary, we can use deferred conditional initialization, as seen below: @@ -68,7 +68,7 @@ let readable: Box = if arg == "-" { ## Discussion Rust newcomers will usually learn that Rust requires all variables to be -initialized _before use_, so it's easy to overlook the fact that _unused_ +initialized *before use*, so it's easy to overlook the fact that *unused* variables may well be uninitialized. Rust works quite hard to ensure that this works out fine and only the initialized values are dropped at the end of their scope. @@ -77,14 +77,15 @@ The example meets all the constraints Rust places on us: - All variables are initialized before using (in this case borrowing) them - Each variable only holds values of a single type. In our example, `stdin` is - of type `Stdin`, `file` is of type `File` and `readable` is of type `&mut dyn Read` + of type `Stdin`, `file` is of type `File` and `readable` is of type + `&mut dyn Read` - Each borrowed value outlives all the references borrowed from it ## See also - [Finalisation in destructors](dtor-finally.md) and - [RAII guards](../patterns/behavioural/RAII.md) can benefit from tight control over - lifetimes. + [RAII guards](../patterns/behavioural/RAII.md) can benefit from tight control + over lifetimes. - For conditionally filled `Option<&T>`s of (mutable) references, one can initialize an `Option` directly and use its [`.as_ref()`] method to get an optional reference. diff --git a/src/idioms/option-iter.md b/src/idioms/option-iter.md index f8ac9f9..350fbb7 100644 --- a/src/idioms/option-iter.md +++ b/src/idioms/option-iter.md @@ -2,9 +2,9 @@ ## Description -`Option` can be viewed as a container that contains either zero or one -element. In particular, it implements the `IntoIterator` trait, and as such -can be used with generic code that needs such a type. +`Option` can be viewed as a container that contains either zero or one element. +In particular, it implements the `IntoIterator` trait, and as such can be used +with generic code that needs such a type. ## Examples @@ -23,8 +23,9 @@ if let Some(turing_inner) = turing { } ``` -If you need to tack an `Option` to the end of an existing iterator, you can -pass it to [`.chain()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.chain): +If you need to tack an `Option` to the end of an existing iterator, you can pass +it to +[`.chain()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.chain): ```rust let turing = Some("Turing"); @@ -39,8 +40,8 @@ Note that if the `Option` is always `Some`, then it is more idiomatic to use [`std::iter::once`](https://doc.rust-lang.org/std/iter/fn.once.html) on the element instead. -Also, since `Option` implements `IntoIterator`, it's possible to iterate over -it using a `for` loop. This is equivalent to matching it with `if let Some(..)`, +Also, since `Option` implements `IntoIterator`, it's possible to iterate over it +using a `for` loop. This is equivalent to matching it with `if let Some(..)`, and in most cases you should prefer the latter. ## See also @@ -50,7 +51,8 @@ and in most cases you should prefer the latter. `Some(foo).into_iter()`. - [`Iterator::filter_map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map) - is a version of [`Iterator::map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map), + is a version of + [`Iterator::map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map), specialized to mapping functions which return `Option`. - The [`ref_slice`](https://crates.io/crates/ref_slice) crate provides functions diff --git a/src/idioms/priv-extend.md b/src/idioms/priv-extend.md index 2a14fdb..452b661 100644 --- a/src/idioms/priv-extend.md +++ b/src/idioms/priv-extend.md @@ -8,9 +8,10 @@ compatibility. Rust offers two solutions to this problem: -- Use `#[non_exhaustive]` on `struct`s, `enum`s, and `enum` variants. - For extensive documentation on all the places where `#[non_exhaustive]` can be - used, see [the docs](https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute). +- Use `#[non_exhaustive]` on `struct`s, `enum`s, and `enum` variants. For + extensive documentation on all the places where `#[non_exhaustive]` can be + used, see + [the docs](https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute). - You may add a private field to a struct to prevent it from being directly instantiated or matched against (see Alternative) @@ -56,22 +57,21 @@ fn print_matched_variants(s: a::S) { ## Alternative: `Private fields` for structs -`#[non_exhaustive]` only works across crate boundaries. -Within a crate, the private field method may be used. +`#[non_exhaustive]` only works across crate boundaries. Within a crate, the +private field method may be used. -Adding a field to a struct is a mostly backwards compatible change. -However, if a client uses a pattern to deconstruct a struct instance, they -might name all the fields in the struct and adding a new one would break that -pattern. -The client could name some fields and use `..` in the pattern, in which case adding -another field is backwards compatible. -Making at least one of the struct's fields private forces clients to use the latter -form of patterns, ensuring that the struct is future-proof. +Adding a field to a struct is a mostly backwards compatible change. However, if +a client uses a pattern to deconstruct a struct instance, they might name all +the fields in the struct and adding a new one would break that pattern. The +client could name some fields and use `..` in the pattern, in which case adding +another field is backwards compatible. Making at least one of the struct's +fields private forces clients to use the latter form of patterns, ensuring that +the struct is future-proof. -The downside of this approach is that you might need to add an otherwise unneeded -field to the struct. -You can use the `()` type so that there is no runtime overhead and prepend `_` to -the field name to avoid the unused field warning. +The downside of this approach is that you might need to add an otherwise +unneeded field to the struct. You can use the `()` type so that there is no +runtime overhead and prepend `_` to the field name to avoid the unused field +warning. ```rust pub struct S { @@ -85,37 +85,33 @@ pub struct S { ## Discussion On `struct`s, `#[non_exhaustive]` allows adding additional fields in a backwards -compatible way. -It will also prevent clients from using the struct constructor, even if all the -fields are public. -This may be helpful, but it's worth considering if you _want_ an additional field -to be found by clients as a compiler error rather than something that may be silently -undiscovered. - -`#[non_exhaustive]` can be applied to enum variants as well. -A `#[non_exhaustive]` variant behaves in the same way as a `#[non_exhaustive]` struct. - -Use this deliberately and with caution: incrementing the major version when adding -fields or variants is often a better option. -`#[non_exhaustive]` may be appropriate in scenarios where you're modeling an external -resource that may change out-of-sync with your library, but is not a general purpose -tool. +compatible way. It will also prevent clients from using the struct constructor, +even if all the fields are public. This may be helpful, but it's worth +considering if you *want* an additional field to be found by clients as a +compiler error rather than something that may be silently undiscovered. + +`#[non_exhaustive]` can be applied to enum variants as well. A +`#[non_exhaustive]` variant behaves in the same way as a `#[non_exhaustive]` +struct. + +Use this deliberately and with caution: incrementing the major version when +adding fields or variants is often a better option. `#[non_exhaustive]` may be +appropriate in scenarios where you're modeling an external resource that may +change out-of-sync with your library, but is not a general purpose tool. ### Disadvantages -`#[non_exhaustive]` can make your code much less ergonomic to use, especially when -forced to handle unknown enum variants. -It should only be used when these sorts of evolutions are required **without** -incrementing the major version. +`#[non_exhaustive]` can make your code much less ergonomic to use, especially +when forced to handle unknown enum variants. It should only be used when these +sorts of evolutions are required **without** incrementing the major version. When `#[non_exhaustive]` is applied to `enum`s, it forces clients to handle a -wildcard variant. -If there is no sensible action to take in this case, this may lead to awkward -code and code paths that are only executed in extremely rare circumstances. -If a client decides to `panic!()` in this scenario, it may have been better to -expose this error at compile time. -In fact, `#[non_exhaustive]` forces clients to handle the "Something else" case; -there is rarely a sensible action to take in this scenario. +wildcard variant. If there is no sensible action to take in this case, this may +lead to awkward code and code paths that are only executed in extremely rare +circumstances. If a client decides to `panic!()` in this scenario, it may have +been better to expose this error at compile time. In fact, `#[non_exhaustive]` +forces clients to handle the "Something else" case; there is rarely a sensible +action to take in this scenario. ## See also diff --git a/src/idioms/return-consumed-arg-on-error.md b/src/idioms/return-consumed-arg-on-error.md index 6a0c471..75369d2 100644 --- a/src/idioms/return-consumed-arg-on-error.md +++ b/src/idioms/return-consumed-arg-on-error.md @@ -2,8 +2,8 @@ ## Description -If a fallible function consumes (moves) an argument, return that argument back inside -an error. +If a fallible function consumes (moves) an argument, return that argument back +inside an error. ## Example @@ -42,14 +42,12 @@ fn main() { ## Motivation -In case of error you may want to try some alternative way or to -retry action in case of non-deterministic function. But if the argument -is always consumed, you are forced to clone it on every call, which -is not very efficient. +In case of error you may want to try some alternative way or to retry action in +case of non-deterministic function. But if the argument is always consumed, you +are forced to clone it on every call, which is not very efficient. -The standard library uses this approach in e.g. `String::from_utf8` method. -When given a vector that doesn't contain valid UTF-8, a `FromUtf8Error` -is returned. +The standard library uses this approach in e.g. `String::from_utf8` method. When +given a vector that doesn't contain valid UTF-8, a `FromUtf8Error` is returned. You can get original vector back using `FromUtf8Error::into_bytes` method. ## Advantages diff --git a/src/idioms/rustdoc-init.md b/src/idioms/rustdoc-init.md index da0291a..d3847d2 100644 --- a/src/idioms/rustdoc-init.md +++ b/src/idioms/rustdoc-init.md @@ -80,9 +80,10 @@ This is much more concise and avoids repetitive code in examples. ## Disadvantages -As example is in a function, the code will not be tested. Though it will still be -checked to make sure it compiles when running a `cargo test`. So this pattern is -most useful when you need `no_run`. With this, you do not need to add `no_run`. +As example is in a function, the code will not be tested. Though it will still +be checked to make sure it compiles when running a `cargo test`. So this pattern +is most useful when you need `no_run`. With this, you do not need to add +`no_run`. ## Discussion diff --git a/src/idioms/temporary-mutability.md b/src/idioms/temporary-mutability.md index e630e10..f9cc9d3 100644 --- a/src/idioms/temporary-mutability.md +++ b/src/idioms/temporary-mutability.md @@ -3,8 +3,8 @@ ## Description Often it is necessary to prepare and process some data, but after that data are -only inspected and never modified. The intention can be made explicit by redefining -the mutable variable as immutable. +only inspected and never modified. The intention can be made explicit by +redefining the mutable variable as immutable. It can be done either by processing data within a nested block or by redefining the variable. @@ -41,5 +41,5 @@ Compiler ensures that you don't accidentally mutate data after some point. ## Disadvantages -Nested block requires additional indentation of block body. -One more line to return data from block or redefine variable. +Nested block requires additional indentation of block body. One more line to +return data from block or redefine variable. diff --git a/src/intro.md b/src/intro.md index 71c8f88..1f70e3d 100644 --- a/src/intro.md +++ b/src/intro.md @@ -7,34 +7,31 @@ If you are interested in contributing to this book, check out the ## Design patterns -In software development, we often come across problems that share -similarities regardless of the environment they appear in. Although the -implementation details are crucial to solve the task at hand, we may -abstract from these particularities to find the common practices that -are generically applicable. - -Design patterns are a collection of reusable and tested solutions to -recurring problems in engineering. They make our software more modular, -maintainable, and extensible. Moreover, these patterns provide a common -language for developers, making them an excellent tool for effective -communication when problem-solving in teams. +In software development, we often come across problems that share similarities +regardless of the environment they appear in. Although the implementation +details are crucial to solve the task at hand, we may abstract from these +particularities to find the common practices that are generically applicable. + +Design patterns are a collection of reusable and tested solutions to recurring +problems in engineering. They make our software more modular, maintainable, and +extensible. Moreover, these patterns provide a common language for developers, +making them an excellent tool for effective communication when problem-solving +in teams. ## Design patterns in Rust Rust is not object-oriented, and the combination of all its characteristics, -such as functional elements, a strong type system, and the borrow checker, -makes it unique. -Because of this, Rust design patterns vary with respect to other -traditional object-oriented programming languages. -That's why we decided to write this book. We hope you enjoy reading it! -The book is divided in three main chapters: - -- [Idioms](./idioms/index.md): guidelines to follow when coding. - They are the social norms of the community. - You should break them only if you have a good reason for it. -- [Design patterns](./patterns/index.md): methods to solve common problems - when coding. +such as functional elements, a strong type system, and the borrow checker, makes +it unique. Because of this, Rust design patterns vary with respect to other +traditional object-oriented programming languages. That's why we decided to +write this book. We hope you enjoy reading it! The book is divided in three main +chapters: + +- [Idioms](./idioms/index.md): guidelines to follow when coding. They are the + social norms of the community. You should break them only if you have a good + reason for it. +- [Design patterns](./patterns/index.md): methods to solve common problems when + coding. - [Anti-patterns](./anti_patterns/index.md): methods to solve common problems - when coding. - However, while design patterns give us benefits, - anti-patterns create more problems. + when coding. However, while design patterns give us benefits, anti-patterns + create more problems. diff --git a/src/patterns/behavioural/RAII.md b/src/patterns/behavioural/RAII.md index 4b3f440..6a24788 100644 --- a/src/patterns/behavioural/RAII.md +++ b/src/patterns/behavioural/RAII.md @@ -3,10 +3,11 @@ ## 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 a 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. +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 a 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 @@ -112,7 +113,8 @@ works just as well. [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), +RAII is a common pattern in C++: +[cppreference.com](http://en.cppreference.com/w/cpp/language/raii), [wikipedia][wikipedia]. [wikipedia]: https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization diff --git a/src/patterns/behavioural/command.md b/src/patterns/behavioural/command.md index 1e0ae26..969f584 100644 --- a/src/patterns/behavioural/command.md +++ b/src/patterns/behavioural/command.md @@ -9,11 +9,11 @@ objects and pass them as parameters. Suppose we have a sequence of actions or transactions encapsulated as objects. We want these actions or commands to be executed or invoked in some order later -at different time. These commands may also be triggered as a result of some event. -For example, when a user pushes a button, or on arrival of a data packet. +at different time. These commands may also be triggered as a result of some +event. For example, when a user pushes a button, or on arrival of a data packet. In addition, these commands might be undoable. This may come in useful for -operations of an editor. We might want to store logs of executed commands so that -we could reapply the changes later if the system crashes. +operations of an editor. We might want to store logs of executed commands so +that we could reapply the changes later if the system crashes. ## Example @@ -94,8 +94,8 @@ fn main() { ## Approach: Using function pointers -We could follow another approach by creating each individual command as -a different function and store function pointers to invoke these functions later +We could follow another approach by creating each individual command as a +different function and store function pointers to invoke these functions later at a different time. Since function pointers implement all three traits `Fn`, `FnMut`, and `FnOnce` we could as well pass and store closures instead of function pointers. @@ -149,8 +149,8 @@ fn main() { ## Approach: Using `Fn` trait objects -Finally, instead of defining a common command trait we could store -each command implementing the `Fn` trait separately in vectors. +Finally, instead of defining a common command trait we could store each command +implementing the `Fn` trait separately in vectors. ```rust type Migration<'a> = Box &'a str>; @@ -205,11 +205,11 @@ fn main() { If our commands are small and may be defined as functions or passed as a closure then using function pointers might be preferable since it does not exploit dynamic dispatch. But if our command is a whole struct with a bunch of functions -and variables defined as seperated module then using trait objects would be -more suitable. A case of application can be found in [`actix`](https://actix.rs/), -which uses trait objects when it registers a handler function for routes. -In case of using `Fn` trait objects we can create and use commands in the same -way as we used in case of function pointers. +and variables defined as seperated module then using trait objects would be more +suitable. A case of application can be found in [`actix`](https://actix.rs/), +which uses trait objects when it registers a handler function for routes. In +case of using `Fn` trait objects we can create and use commands in the same way +as we used in case of function pointers. As performance, there is always a trade-off between performance and code simplicity and organisation. Static dispatch gives faster performance, while diff --git a/src/patterns/behavioural/interpreter.md b/src/patterns/behavioural/interpreter.md index 854331b..b6803e3 100644 --- a/src/patterns/behavioural/interpreter.md +++ b/src/patterns/behavioural/interpreter.md @@ -9,23 +9,25 @@ simple language. Basically, for any kind of problems we define: -- A [domain specific language](https://en.wikipedia.org/wiki/Domain-specific_language), +- A + [domain specific language](https://en.wikipedia.org/wiki/Domain-specific_language), - A grammar for this language, - An interpreter that solves the problem instances. ## Motivation -Our goal is to translate simple mathematical expressions into postfix expressions -(or [Reverse Polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)) +Our goal is to translate simple mathematical expressions into postfix +expressions (or +[Reverse Polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)) For simplicity, our expressions consist of ten digits `0`, ..., `9` and two operations `+`, `-`. For example, the expression `2 + 4` is translated into `2 4 +`. ## Context Free Grammar for our problem -Our task is translating infix expressions into postfix ones. Let's define a context -free grammar for a set of infix expressions over `0`, ..., `9`, `+`, and `-`, -where: +Our task is translating infix expressions into postfix ones. Let's define a +context free grammar for a set of infix expressions over `0`, ..., `9`, `+`, and +`-`, where: - Terminal symbols: `0`, `...`, `9`, `+`, `-` - Non-terminal symbols: `exp`, `term` @@ -39,9 +41,10 @@ exp -> term term -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 ``` -**NOTE:** This grammar should be further transformed depending on what we are going -to do with it. For example, we might need to remove left recursion. For more -details please see [Compilers: Principles,Techniques, and Tools](https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools) +**NOTE:** This grammar should be further transformed depending on what we are +going to do with it. For example, we might need to remove left recursion. For +more details please see +[Compilers: Principles,Techniques, and Tools](https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools) (aka Dragon Book). ## Solution @@ -102,18 +105,18 @@ pub fn main() { ## Discussion -There may be a wrong perception that the Interpreter design pattern is about design -grammars for formal languages and implementation of parsers for these grammars. -In fact, this pattern is about expressing problem instances in a more specific -way and implementing functions/classes/structs that solve these problem instances. -Rust language has `macro_rules!` that allow us to define special syntax and rules -on how to expand this syntax into source code. +There may be a wrong perception that the Interpreter design pattern is about +design grammars for formal languages and implementation of parsers for these +grammars. In fact, this pattern is about expressing problem instances in a more +specific way and implementing functions/classes/structs that solve these problem +instances. Rust language has `macro_rules!` that allow us to define special +syntax and rules on how to expand this syntax into source code. In the following example we create a simple `macro_rules!` that computes [Euclidean length](https://en.wikipedia.org/wiki/Euclidean_distance) of `n` dimensional vectors. Writing `norm!(x,1,2)` might be easier to express and more -efficient than packing `x,1,2` into a `Vec` and calling a function computing -the length. +efficient than packing `x,1,2` into a `Vec` and calling a function computing the +length. ```rust macro_rules! norm { diff --git a/src/patterns/behavioural/intro.md b/src/patterns/behavioural/intro.md index 8ca43a0..3151247 100644 --- a/src/patterns/behavioural/intro.md +++ b/src/patterns/behavioural/intro.md @@ -2,5 +2,5 @@ From [Wikipedia](https://en.wikipedia.org/wiki/Behavioral_pattern): -> Design patterns that identify common communication patterns among objects. -> By doing so, these patterns increase flexibility in carrying out communication. +> Design patterns that identify common communication patterns among objects. By +> doing so, these patterns increase flexibility in carrying out communication. diff --git a/src/patterns/behavioural/newtype.md b/src/patterns/behavioural/newtype.md index d7b5a82..8d19f8f 100644 --- a/src/patterns/behavioural/newtype.md +++ b/src/patterns/behavioural/newtype.md @@ -1,14 +1,14 @@ # Newtype What if in some cases we want a type to behave similar to another type or -enforce some behaviour at compile time when using only type aliases would -not be enough? +enforce some behaviour at compile time when using only type aliases would not be +enough? For example, if we want to create a custom `Display` implementation for `String` due to security considerations (e.g. passwords). -For such cases we could use the `Newtype` pattern to provide **type safety** -and **encapsulation**. +For such cases we could use the `Newtype` pattern to provide **type safety** and +**encapsulation**. ## Description @@ -66,7 +66,7 @@ field is private, which it is by default). ## Disadvantages The downside of newtypes (especially compared with type aliases), is that there -is no special language support. This means there can be _a lot_ of boilerplate. +is no special language support. This means there can be *a lot* of boilerplate. You need a 'pass through' method for every method you want to expose on the wrapped type, and an impl for every trait you want to also be implemented for the wrapper type. @@ -76,7 +76,8 @@ the wrapper type. Newtypes are very common in Rust code. Abstraction or representing units are the most common uses, but they can be used for other reasons: -- restricting functionality (reduce the functions exposed or traits implemented), +- restricting functionality (reduce the functions exposed or traits + implemented), - making a type with copy semantics have move semantics, - abstraction by providing a more concrete type and thus hiding internal types, e.g., @@ -85,10 +86,10 @@ most common uses, but they can be used for other reasons: pub struct Foo(Bar); ``` -Here, `Bar` might be some public, generic type and `T1` and `T2` are some internal -types. Users of our module shouldn't know that we implement `Foo` by using a `Bar`, -but what we're really hiding here is the types `T1` and `T2`, and how they are used -with `Bar`. +Here, `Bar` might be some public, generic type and `T1` and `T2` are some +internal types. Users of our module shouldn't know that we implement `Foo` by +using a `Bar`, but what we're really hiding here is the types `T1` and `T2`, and +how they are used with `Bar`. ## See also diff --git a/src/patterns/behavioural/strategy.md b/src/patterns/behavioural/strategy.md index 60892f2..92c0a7a 100644 --- a/src/patterns/behavioural/strategy.md +++ b/src/patterns/behavioural/strategy.md @@ -2,33 +2,36 @@ ## Description -The [Strategy design pattern](https://en.wikipedia.org/wiki/Strategy_pattern) -is a technique that enables separation of concerns. -It also allows to decouple software modules through [Dependency Inversion](https://en.wikipedia.org/wiki/Dependency_inversion_principle). +The [Strategy design pattern](https://en.wikipedia.org/wiki/Strategy_pattern) is +a technique that enables separation of concerns. It also allows to decouple +software modules through +[Dependency Inversion](https://en.wikipedia.org/wiki/Dependency_inversion_principle). -The basic idea behind the Strategy pattern is that, given an algorithm solving -a particular problem, we define only the skeleton of the algorithm at an abstract -level, and we separate the specific algorithm’s implementation into different parts. +The basic idea behind the Strategy pattern is that, given an algorithm solving a +particular problem, we define only the skeleton of the algorithm at an abstract +level, and we separate the specific algorithm’s implementation into different +parts. In this way, a client using the algorithm may choose a specific implementation, -while the general algorithm workflow remains the same. In other words, the abstract -specification of the class does not depend on the specific implementation of the -derived class, but specific implementation must adhere to the abstract specification. -This is why we call it "Dependency Inversion". +while the general algorithm workflow remains the same. In other words, the +abstract specification of the class does not depend on the specific +implementation of the derived class, but specific implementation must adhere to +the abstract specification. This is why we call it "Dependency Inversion". ## Motivation -Imagine we are working on a project that generates reports every month. -We need the reports to be generated in different formats (strategies), e.g., -in `JSON` or `Plain Text` formats. -But things vary over time, and we don't know what kind of requirement we may get -in the future. For example, we may need to generate our report in a completely new -format, or just modify one of the existing formats. +Imagine we are working on a project that generates reports every month. We need +the reports to be generated in different formats (strategies), e.g., in `JSON` +or `Plain Text` formats. But things vary over time, and we don't know what kind +of requirement we may get in the future. For example, we may need to generate +our report in a completely new format, or just modify one of the existing +formats. ## Example -In this example our invariants (or abstractions) are `Formatter` and `Report`, while `Text` and `Json` are our strategy structs. These strategies -have to implement the `Formatter` trait. +In this example our invariants (or abstractions) are `Formatter` and `Report`, +while `Text` and `Json` are our strategy structs. These strategies have to +implement the `Formatter` trait. ```rust use std::collections::HashMap; @@ -97,37 +100,38 @@ fn main() { The main advantage is separation of concerns. For example, in this case `Report` does not know anything about specific implementations of `Json` and `Text`, whereas the output implementations does not care about how data is preprocessed, -stored, and fetched. The only thing they have to know is a specific -trait to implement and its method defining the concrete algorithm implementation processing -the result, i.e., `Formatter` and `format(...)`. +stored, and fetched. The only thing they have to know is a specific trait to +implement and its method defining the concrete algorithm implementation +processing the result, i.e., `Formatter` and `format(...)`. ## Disadvantages -For each strategy there must be implemented at least one module, so number of modules -increases with number of strategies. If there are many strategies to choose from -then users have to know how strategies differ from one another. +For each strategy there must be implemented at least one module, so number of +modules increases with number of strategies. If there are many strategies to +choose from then users have to know how strategies differ from one another. ## Discussion -In the previous example all strategies are implemented in a single file. -Ways of providing different strategies includes: +In the previous example all strategies are implemented in a single file. Ways of +providing different strategies includes: -- All in one file (as shown in this example, similar to being separated as modules) +- All in one file (as shown in this example, similar to being separated as + modules) - Separated as modules, E.g. `formatter::json` module, `formatter::text` module - Use compiler feature flags, E.g. `json` feature, `text` feature - Separated as crates, E.g. `json` crate, `text` crate Serde crate is a good example of the `Strategy` pattern in action. Serde allows -[full customization](https://serde.rs/custom-serialization.html) of the serialization -behavior by manually implementing `Serialize` and `Deserialize` traits for our -type. For example, we could easily swap `serde_json` with `serde_cbor` since they -expose similar methods. Having this makes the helper crate `serde_transcode` much -more useful and ergonomic. +[full customization](https://serde.rs/custom-serialization.html) of the +serialization behavior by manually implementing `Serialize` and `Deserialize` +traits for our type. For example, we could easily swap `serde_json` with +`serde_cbor` since they expose similar methods. Having this makes the helper +crate `serde_transcode` much more useful and ergonomic. However, we don't need to use traits in order to design this pattern in Rust. -The following toy example demonstrates the idea of the Strategy pattern using Rust -`closures`: +The following toy example demonstrates the idea of the Strategy pattern using +Rust `closures`: ```rust struct Adder; diff --git a/src/patterns/behavioural/visitor.md b/src/patterns/behavioural/visitor.md index 5587314..c164d2a 100644 --- a/src/patterns/behavioural/visitor.md +++ b/src/patterns/behavioural/visitor.md @@ -7,8 +7,8 @@ collection of objects. It allows multiple different algorithms to be written over the same data without having to modify the data (or their primary behaviour). -Furthermore, the visitor pattern allows separating the traversal of -a collection of objects from the operations performed on each object. +Furthermore, the visitor pattern allows separating the traversal of a collection +of objects from the operations performed on each object. ## Example @@ -72,9 +72,9 @@ to modify the AST data. ## Motivation The visitor pattern is useful anywhere that you want to apply an algorithm to -heterogeneous data. If data is homogeneous, you can use an iterator-like pattern. -Using a visitor object (rather than a functional approach) allows the visitor to -be stateful and thus communicate information between nodes. +heterogeneous data. If data is homogeneous, you can use an iterator-like +pattern. Using a visitor object (rather than a functional approach) allows the +visitor to be stateful and thus communicate information between nodes. ## Discussion @@ -109,5 +109,5 @@ The visitor pattern is a common pattern in most OO languages. [Wikipedia article](https://en.wikipedia.org/wiki/Visitor_pattern) -The [fold](../creational/fold.md) pattern is similar to visitor but produces -a new version of the visited data structure. +The [fold](../creational/fold.md) pattern is similar to visitor but produces a +new version of the visited data structure. diff --git a/src/patterns/creational/builder.md b/src/patterns/creational/builder.md index 211ef37..cd40836 100644 --- a/src/patterns/creational/builder.md +++ b/src/patterns/creational/builder.md @@ -62,8 +62,8 @@ fn builder_test() { ## Motivation -Useful when you would otherwise require many constructors or where -construction has side effects. +Useful when you would otherwise require many constructors or where construction +has side effects. ## Advantages @@ -88,8 +88,9 @@ Rust than in C++, Java, or others. This pattern is often used where the builder object is useful in its own right, rather than being just a builder. For example, see [`std::process::Command`](https://doc.rust-lang.org/std/process/struct.Command.html) -is a builder for [`Child`](https://doc.rust-lang.org/std/process/struct.Child.html) -(a process). In these cases, the `T` and `TBuilder` naming pattern is not used. +is a builder for +[`Child`](https://doc.rust-lang.org/std/process/struct.Child.html) (a process). +In these cases, the `T` and `TBuilder` naming pattern is not used. The example takes and returns the builder by value. It is often more ergonomic (and more efficient) to take and return the builder as a mutable reference. The @@ -108,8 +109,8 @@ as well as the `FooBuilder::new().a().b().build()` style. ## See also - [Description in the style guide](https://web.archive.org/web/20210104103100/https://doc.rust-lang.org/1.12.0/style/ownership/builders.html) -- [derive_builder](https://crates.io/crates/derive_builder), a crate for automatically - implementing this pattern while avoiding the boilerplate. +- [derive_builder](https://crates.io/crates/derive_builder), a crate for + automatically implementing this pattern while avoiding the boilerplate. - [Constructor pattern](../../idioms/ctor.md) for when construction is simpler. - [Builder pattern (wikipedia)](https://en.wikipedia.org/wiki/Builder_pattern) - [Construction of complex values](https://web.archive.org/web/20210104103000/https://rust-lang.github.io/api-guidelines/type-safety.html#c-builder) diff --git a/src/patterns/creational/fold.md b/src/patterns/creational/fold.md index 13eab83..2874563 100644 --- a/src/patterns/creational/fold.md +++ b/src/patterns/creational/fold.md @@ -5,9 +5,9 @@ Run an algorithm over each item in a collection of data to create a new item, thus creating a whole new collection. -The etymology here is unclear to me. The terms 'fold' and 'folder' are used -in the Rust compiler, although it appears to me to be more like a map than a -fold in the usual sense. See the discussion below for more details. +The etymology here is unclear to me. The terms 'fold' and 'folder' are used in +the Rust compiler, although it appears to me to be more like a map than a fold +in the usual sense. See the discussion below for more details. ## Example @@ -102,9 +102,9 @@ reused; however, a node must be cloned even if unchanged, which can be expensive. Using a reference counted pointer gives the best of both worlds - we can reuse -the original data structure, and we don't need to clone unchanged nodes. However, -they are less ergonomic to use and mean that the data structures cannot be -mutable. +the original data structure, and we don't need to clone unchanged nodes. +However, they are less ergonomic to use and mean that the data structures cannot +be mutable. ## See also diff --git a/src/patterns/creational/intro.md b/src/patterns/creational/intro.md index 782fdc8..318e2bf 100644 --- a/src/patterns/creational/intro.md +++ b/src/patterns/creational/intro.md @@ -2,7 +2,8 @@ From [Wikipedia](https://en.wikipedia.org/wiki/Creational_pattern): -> Design patterns that deal with object creation mechanisms, trying to create objects -> in a manner suitable to the situation. The basic form of object creation could -> result in design problems or in added complexity to the design. Creational design -> patterns solve this problem by somehow controlling this object creation. +> Design patterns that deal with object creation mechanisms, trying to create +> objects in a manner suitable to the situation. The basic form of object +> creation could result in design problems or in added complexity to the design. +> Creational design patterns solve this problem by somehow controlling this +> object creation. diff --git a/src/patterns/ffi/export.md b/src/patterns/ffi/export.md index f7d3260..a715e4b 100644 --- a/src/patterns/ffi/export.md +++ b/src/patterns/ffi/export.md @@ -5,46 +5,51 @@ When designing APIs in Rust which are exposed to other languages, there are some important design principles which are contrary to normal Rust API design: -1. All Encapsulated types should be _owned_ by Rust, _managed_ by the user, - and _opaque_. -2. All Transactional data types should be _owned_ by the user, and _transparent_. +1. All Encapsulated types should be *owned* by Rust, *managed* by the user, and + *opaque*. +2. All Transactional data types should be *owned* by the user, and + *transparent*. 3. All library behavior should be functions acting upon Encapsulated types. -4. All library behavior should be encapsulated into types not based on structure, - but _provenance/lifetime_. +4. All library behavior should be encapsulated into types not based on + structure, but *provenance/lifetime*. ## Motivation -Rust has built-in FFI support to other languages. -It does this by providing a way for crate authors to provide C-compatible APIs -through different ABIs (though that is unimportant to this practice). +Rust has built-in FFI support to other languages. It does this by providing a +way for crate authors to provide C-compatible APIs through different ABIs +(though that is unimportant to this practice). Well-designed Rust FFI follows C API design principles, while compromising the -design in Rust as little as possible. There are three goals with any foreign API: +design in Rust as little as possible. There are three goals with any foreign +API: 1. Make it easy to use in the target language. -2. Avoid the API dictating internal unsafety on the Rust side as much as possible. -3. Keep the potential for memory unsafety and Rust `undefined behaviour` as small - as possible. +2. Avoid the API dictating internal unsafety on the Rust side as much as + possible. +3. Keep the potential for memory unsafety and Rust `undefined behaviour` as + small as possible. Rust code must trust the memory safety of the foreign language beyond a certain -point. However, every bit of `unsafe` code on the Rust side is an opportunity for -bugs, or to exacerbate `undefined behaviour`. +point. However, every bit of `unsafe` code on the Rust side is an opportunity +for bugs, or to exacerbate `undefined behaviour`. For example, if a pointer provenance is wrong, that may be a segfault due to invalid memory access. But if it is manipulated by unsafe code, it could become full-blown heap corruption. -The Object-Based API design allows for writing shims that have good memory safety -characteristics, and a clean boundary of what is safe and what is `unsafe`. +The Object-Based API design allows for writing shims that have good memory +safety characteristics, and a clean boundary of what is safe and what is +`unsafe`. ## Code Example -The POSIX standard defines the API to access an on-file database, known as [DBM](https://web.archive.org/web/20210105035602/https://www.mankier.com/0p/ndbm.h). +The POSIX standard defines the API to access an on-file database, known as +[DBM](https://web.archive.org/web/20210105035602/https://www.mankier.com/0p/ndbm.h). It is an excellent example of an "object-based" API. Here is the definition in C, which hopefully should be easy to read for those -involved in FFI. The commentary below should help explain it for those who -miss the subtleties. +involved in FFI. The commentary below should help explain it for those who miss +the subtleties. ```C struct DBM; @@ -63,50 +68,47 @@ int dbm_store(DBM *, datum, datum, int); This API defines two types: `DBM` and `datum`. -The `DBM` type was called an "encapsulated" type above. -It is designed to contain internal state, and acts as an entry point for the -library's behavior. +The `DBM` type was called an "encapsulated" type above. It is designed to +contain internal state, and acts as an entry point for the library's behavior. It is completely opaque to the user, who cannot create a `DBM` themselves since they don't know its size or layout. Instead, they must call `dbm_open`, and that -only gives them _a pointer to one_. - -This means all `DBM`s are "owned" by the library in a Rust sense. -The internal state of unknown size is kept in memory controlled by the library, -not the user. The user can only manage its life cycle with `open` and `close`, -and perform operations on it with the other functions. - -The `datum` type was called a "transactional" type above. -It is designed to facilitate the exchange of information between the library and -its user. - -The database is designed to store "unstructured data", with no pre-defined length -or meaning. As a result, the `datum` is the C equivalent of a Rust slice: a bunch -of bytes, and a count of how many there are. The main difference is that there is -no type information, which is what `void` indicates. - -Keep in mind that this header is written from the library's point of view. -The user likely has some type they are using, which has a known size. -But the library does not care, and by the rules of C casting, any type behind a -pointer can be cast to `void`. - -As noted earlier, this type is _transparent_ to the user. But also, this type is -_owned_ by the user. -This has subtle ramifications, due to that pointer inside it. -The question is, who owns the memory that pointer points to? - -The answer for best memory safety is, "the user". -But in cases such as retrieving a value, the user does not know how to allocate -it correctly (since they don't know how long the value is). In this case, the library -code is expected to use the heap that the user has access to -- such as the C library -`malloc` and `free` -- and then _transfer ownership_ in the Rust sense. - -This may all seem speculative, but this is what a pointer means in C. -It means the same thing as Rust: "user defined lifetime." -The user of the library needs to read the documentation in order to use it correctly. -That said, there are some decisions that have fewer or greater consequences if users -do it wrong. Minimizing those are what this best practice is about, and the key -is to _transfer ownership of everything that is transparent_. +only gives them *a pointer to one*. + +This means all `DBM`s are "owned" by the library in a Rust sense. The internal +state of unknown size is kept in memory controlled by the library, not the user. +The user can only manage its life cycle with `open` and `close`, and perform +operations on it with the other functions. + +The `datum` type was called a "transactional" type above. It is designed to +facilitate the exchange of information between the library and its user. + +The database is designed to store "unstructured data", with no pre-defined +length or meaning. As a result, the `datum` is the C equivalent of a Rust slice: +a bunch of bytes, and a count of how many there are. The main difference is that +there is no type information, which is what `void` indicates. + +Keep in mind that this header is written from the library's point of view. The +user likely has some type they are using, which has a known size. But the +library does not care, and by the rules of C casting, any type behind a pointer +can be cast to `void`. + +As noted earlier, this type is *transparent* to the user. But also, this type is +*owned* by the user. This has subtle ramifications, due to that pointer inside +it. The question is, who owns the memory that pointer points to? + +The answer for best memory safety is, "the user". But in cases such as +retrieving a value, the user does not know how to allocate it correctly (since +they don't know how long the value is). In this case, the library code is +expected to use the heap that the user has access to -- such as the C library +`malloc` and `free` -- and then *transfer ownership* in the Rust sense. + +This may all seem speculative, but this is what a pointer means in C. It means +the same thing as Rust: "user defined lifetime." The user of the library needs +to read the documentation in order to use it correctly. That said, there are +some decisions that have fewer or greater consequences if users do it wrong. +Minimizing those are what this best practice is about, and the key is to +*transfer ownership of everything that is transparent*. ## Advantages @@ -119,12 +121,12 @@ relatively small number: 3. The `dptr` on any `datum` must be `NULL`, or point to a valid slice of memory at the advertised length. -In addition, it avoids a lot of pointer provenance issues. -To understand why, let us consider an alternative in some depth: key iteration. +In addition, it avoids a lot of pointer provenance issues. To understand why, +let us consider an alternative in some depth: key iteration. -Rust is well known for its iterators. -When implementing one, the programmer makes a separate type with a bounded lifetime -to its owner, and implements the `Iterator` trait. +Rust is well known for its iterators. When implementing one, the programmer +makes a separate type with a bounded lifetime to its owner, and implements the +`Iterator` trait. Here is how iteration would be done in Rust for `DBM`: @@ -144,8 +146,8 @@ struct DbmKeysIter<'it> { impl<'it> Iterator for DbmKeysIter<'it> { ... } ``` -This is clean, idiomatic, and safe. thanks to Rust's guarantees. -However, consider what a straightforward API translation would look like: +This is clean, idiomatic, and safe. thanks to Rust's guarantees. However, +consider what a straightforward API translation would look like: ```rust,ignore #[no_mangle] @@ -166,9 +168,9 @@ pub extern "C" fn dbm_iter_del(*mut DbmKeysIter) { ``` This API loses a key piece of information: the lifetime of the iterator must not -exceed the lifetime of the `Dbm` object that owns it. A user of the library could -use it in a way which causes the iterator to outlive the data it is iterating on, -resulting in reading uninitialized memory. +exceed the lifetime of the `Dbm` object that owns it. A user of the library +could use it in a way which causes the iterator to outlive the data it is +iterating on, resulting in reading uninitialized memory. This example written in C contains a bug that will be explained afterwards: @@ -206,23 +208,23 @@ end-of-iteration marker: 2. The length is incremented, in this case by zero. 3. The if statement is true, so the database is closed. There should be a break statement here. -4. The loop condition executes again, causing a `next` call on the closed object. +4. The loop condition executes again, causing a `next` call on the closed + object. -The worst part about this bug? -If the Rust implementation was careful, this code will work most of the time! -If the memory for the `Dbm` object is not immediately reused, an internal check -will almost certainly fail, resulting in the iterator returning a `-1` indicating -an error. But occasionally, it will cause a segmentation fault, or even worse, -nonsensical memory corruption! +The worst part about this bug? If the Rust implementation was careful, this code +will work most of the time! If the memory for the `Dbm` object is not +immediately reused, an internal check will almost certainly fail, resulting in +the iterator returning a `-1` indicating an error. But occasionally, it will +cause a segmentation fault, or even worse, nonsensical memory corruption! -None of this can be avoided by Rust. -From its perspective, it put those objects on its heap, returned pointers to them, -and gave up control of their lifetimes. The C code simply must "play nice". +None of this can be avoided by Rust. From its perspective, it put those objects +on its heap, returned pointers to them, and gave up control of their lifetimes. +The C code simply must "play nice". -The programmer must read and understand the API documentation. -While some consider that par for the course in C, a good API design can mitigate -this risk. The POSIX API for `DBM` did this by _consolidating the ownership_ of -the iterator with its parent: +The programmer must read and understand the API documentation. While some +consider that par for the course in C, a good API design can mitigate this risk. +The POSIX API for `DBM` did this by *consolidating the ownership* of the +iterator with its parent: ```C datum dbm_firstkey(DBM *); @@ -236,25 +238,25 @@ Thus, all the lifetimes were bound together, and such unsafety was prevented. However, this design choice also has a number of drawbacks, which should be considered as well. -First, the API itself becomes less expressive. -With POSIX DBM, there is only one iterator per object, and every call changes -its state. This is much more restrictive than iterators in almost any language, -even though it is safe. Perhaps with other related objects, whose lifetimes are -less hierarchical, this limitation is more of a cost than the safety. +First, the API itself becomes less expressive. With POSIX DBM, there is only one +iterator per object, and every call changes its state. This is much more +restrictive than iterators in almost any language, even though it is safe. +Perhaps with other related objects, whose lifetimes are less hierarchical, this +limitation is more of a cost than the safety. -Second, depending on the relationships of the API's parts, significant design effort -may be involved. Many of the easier design points have other patterns associated -with them: +Second, depending on the relationships of the API's parts, significant design +effort may be involved. Many of the easier design points have other patterns +associated with them: -- [Wrapper Type Consolidation](./wrappers.md) groups multiple Rust types together - into an opaque "object" +- [Wrapper Type Consolidation](./wrappers.md) groups multiple Rust types + together into an opaque "object" -- [FFI Error Passing](../../idioms/ffi/errors.md) explains error handling with integer - codes and sentinel return values (such as `NULL` pointers) +- [FFI Error Passing](../../idioms/ffi/errors.md) explains error handling with + integer codes and sentinel return values (such as `NULL` pointers) -- [Accepting Foreign Strings](../../idioms/ffi/accepting-strings.md) allows accepting - strings with minimal unsafe code, and is easier to get right than +- [Accepting Foreign Strings](../../idioms/ffi/accepting-strings.md) allows + accepting strings with minimal unsafe code, and is easier to get right than [Passing Strings to FFI](../../idioms/ffi/passing-strings.md) -However, not every API can be done this way. -It is up to the best judgement of the programmer as to who their audience is. +However, not every API can be done this way. It is up to the best judgement of +the programmer as to who their audience is. diff --git a/src/patterns/ffi/intro.md b/src/patterns/ffi/intro.md index a2e300f..bc9bd59 100644 --- a/src/patterns/ffi/intro.md +++ b/src/patterns/ffi/intro.md @@ -1,13 +1,13 @@ # FFI Patterns -Writing FFI code is an entire course in itself. -However, there are several idioms here that can act as pointers, and avoid traps -for inexperienced users of unsafe Rust. +Writing FFI code is an entire course in itself. However, there are several +idioms here that can act as pointers, and avoid traps for inexperienced users of +unsafe Rust. This section contains design patterns that may be useful when doing FFI. -1. [Object-Based API](./export.md) design that has good memory safety characteristics, - and a clean boundary of what is safe and what is unsafe +1. [Object-Based API](./export.md) design that has good memory safety + characteristics, and a clean boundary of what is safe and what is unsafe 2. [Type Consolidation into Wrappers](./wrappers.md) - group multiple Rust types together into an opaque "object" diff --git a/src/patterns/ffi/wrappers.md b/src/patterns/ffi/wrappers.md index 13393ed..68dadc3 100644 --- a/src/patterns/ffi/wrappers.md +++ b/src/patterns/ffi/wrappers.md @@ -5,33 +5,34 @@ This pattern is designed to allow gracefully handling multiple related types, while minimizing the surface area for memory unsafety. -One of the cornerstones of Rust's aliasing rules is lifetimes. -This ensures that many patterns of access between types can be memory safe, -data race safety included. +One of the cornerstones of Rust's aliasing rules is lifetimes. This ensures that +many patterns of access between types can be memory safe, data race safety +included. -However, when Rust types are exported to other languages, they are usually transformed -into pointers. In Rust, a pointer means "the user manages the lifetime of the pointee." -It is their responsibility to avoid memory unsafety. +However, when Rust types are exported to other languages, they are usually +transformed into pointers. In Rust, a pointer means "the user manages the +lifetime of the pointee." It is their responsibility to avoid memory unsafety. -Some level of trust in the user code is thus required, notably around use-after-free -which Rust can do nothing about. However, some API designs place higher burdens -than others on the code written in the other language. +Some level of trust in the user code is thus required, notably around +use-after-free which Rust can do nothing about. However, some API designs place +higher burdens than others on the code written in the other language. -The lowest risk API is the "consolidated wrapper", where all possible interactions -with an object are folded into a "wrapper type", while keeping the Rust API clean. +The lowest risk API is the "consolidated wrapper", where all possible +interactions with an object are folded into a "wrapper type", while keeping the +Rust API clean. ## Code Example -To understand this, let us look at a classic example of an API to export: iteration -through a collection. +To understand this, let us look at a classic example of an API to export: +iteration through a collection. That API looks like this: 1. The iterator is initialized with `first_key`. 2. Each call to `next_key` will advance the iterator. 3. Calls to `next_key` if the iterator is at the end will do nothing. -4. As noted above, the iterator is "wrapped into" the collection (unlike the native - Rust API). +4. As noted above, the iterator is "wrapped into" the collection (unlike the + native Rust API). If the iterator implements `nth()` efficiently, then it is possible to make it ephemeral to each function call: @@ -62,19 +63,19 @@ As a result, the wrapper is simple and contains no `unsafe` code. ## Advantages -This makes APIs safer to use, avoiding issues with lifetimes between types. -See [Object-Based APIs](./export.md) for more on the advantages and pitfalls -this avoids. +This makes APIs safer to use, avoiding issues with lifetimes between types. See +[Object-Based APIs](./export.md) for more on the advantages and pitfalls this +avoids. ## Disadvantages Often, wrapping types is quite difficult, and sometimes a Rust API compromise would make things easier. -As an example, consider an iterator which does not efficiently implement `nth()`. -It would definitely be worth putting in special logic to make the object handle -iteration internally, or to support a different access pattern efficiently that -only the Foreign Function API will use. +As an example, consider an iterator which does not efficiently implement +`nth()`. It would definitely be worth putting in special logic to make the +object handle iteration internally, or to support a different access pattern +efficiently that only the Foreign Function API will use. ### Trying to Wrap Iterators (and Failing) @@ -82,9 +83,9 @@ To wrap any type of iterator into the API correctly, the wrapper would need to do what a C version of the code would do: erase the lifetime of the iterator, and manage it manually. -Suffice it to say, this is _incredibly_ difficult. +Suffice it to say, this is *incredibly* difficult. -Here is an illustration of just _one_ pitfall. +Here is an illustration of just *one* pitfall. A first version of `MySetWrapper` would look like this: @@ -97,14 +98,13 @@ struct MySetWrapper { } ``` -With `transmute` being used to extend a lifetime, and a pointer to hide it, -it's ugly already. But it gets even worse: _any other operation can cause -Rust `undefined behaviour`_. +With `transmute` being used to extend a lifetime, and a pointer to hide it, it's +ugly already. But it gets even worse: *any other operation can cause Rust +`undefined behaviour`*. -Consider that the `MySet` in the wrapper could be manipulated by other -functions during iteration, such as storing a new value to the key it was -iterating over. The API doesn't discourage this, and in fact some similar C -libraries expect it. +Consider that the `MySet` in the wrapper could be manipulated by other functions +during iteration, such as storing a new value to the key it was iterating over. +The API doesn't discourage this, and in fact some similar C libraries expect it. A simple implementation of `myset_store` would be: @@ -134,29 +134,30 @@ pub mod unsafe_module { } ``` -If the iterator exists when this function is called, we have violated one of Rust's -aliasing rules. According to Rust, the mutable reference in this block must have -_exclusive_ access to the object. If the iterator simply exists, it's not exclusive, -so we have `undefined behaviour`! [^1] - -To avoid this, we must have a way of ensuring that mutable reference really is exclusive. -That basically means clearing out the iterator's shared reference while it exists, -and then reconstructing it. In most cases, that will still be less efficient than -the C version. - -Some may ask: how can C do this more efficiently? -The answer is, it cheats. Rust's aliasing rules are the problem, and C simply ignores -them for its pointers. In exchange, it is common to see code that is declared -in the manual as "not thread safe" under some or all circumstances. In fact, -the [GNU C library](https://manpages.debian.org/buster/manpages/attributes.7.en.html) +If the iterator exists when this function is called, we have violated one of +Rust's aliasing rules. According to Rust, the mutable reference in this block +must have *exclusive* access to the object. If the iterator simply exists, it's +not exclusive, so we have `undefined behaviour`! [^1] + +To avoid this, we must have a way of ensuring that mutable reference really is +exclusive. That basically means clearing out the iterator's shared reference +while it exists, and then reconstructing it. In most cases, that will still be +less efficient than the C version. + +Some may ask: how can C do this more efficiently? The answer is, it cheats. +Rust's aliasing rules are the problem, and C simply ignores them for its +pointers. In exchange, it is common to see code that is declared in the manual +as "not thread safe" under some or all circumstances. In fact, the +[GNU C library](https://manpages.debian.org/buster/manpages/attributes.7.en.html) has an entire lexicon dedicated to concurrent behavior! Rust would rather make everything memory safe all the time, for both safety and -optimizations that C code cannot attain. Being denied access to certain shortcuts -is the price Rust programmers need to pay. +optimizations that C code cannot attain. Being denied access to certain +shortcuts is the price Rust programmers need to pay. [^1]: For the C programmers out there scratching their heads, the iterator need -not be read _during_ this code cause the UB. The exclusivity rule also enables -compiler optimizations which may cause inconsistent observations by the iterator's -shared reference (e.g. stack spills or reordering instructions for efficiency). -These observations may happen _any time after_ the mutable reference is created. +not be read *during* this code cause the UB. The exclusivity rule also enables +compiler optimizations which may cause inconsistent observations by the +iterator's shared reference (e.g. stack spills or reordering instructions for +efficiency). These observations may happen *any time after* the mutable +reference is created. diff --git a/src/patterns/index.md b/src/patterns/index.md index 1718174..87c40c3 100644 --- a/src/patterns/index.md +++ b/src/patterns/index.md @@ -14,15 +14,17 @@ about a programming language. ## Design patterns in Rust Rust has many unique features. These features give us great benefit by removing -whole classes of problems. Some of them are also patterns that are _unique_ to Rust. +whole classes of problems. Some of them are also patterns that are *unique* to +Rust. ## YAGNI -YAGNI is an acronym that stands for `You Aren't Going to Need It`. -It's a vital software design principle to apply as you write code. +YAGNI is an acronym that stands for `You Aren't Going to Need It`. It's a vital +software design principle to apply as you write code. > The best code I ever wrote was code I never wrote. -If we apply YAGNI to design patterns, we see that the features of Rust allow us to -throw out many patterns. For instance, there is no need for the [strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern) -in Rust because we can just use [traits](https://doc.rust-lang.org/book/traits.html). +If we apply YAGNI to design patterns, we see that the features of Rust allow us +to throw out many patterns. For instance, there is no need for the +[strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern) in Rust +because we can just use [traits](https://doc.rust-lang.org/book/traits.html). diff --git a/src/patterns/structural/intro.md b/src/patterns/structural/intro.md index 34d9cc9..d0f0c60 100644 --- a/src/patterns/structural/intro.md +++ b/src/patterns/structural/intro.md @@ -2,5 +2,5 @@ From [Wikipedia](https://en.wikipedia.org/wiki/Structural_pattern): -> Design patterns that ease the design by identifying a simple way to realize relationships -> among entities. +> Design patterns that ease the design by identifying a simple way to realize +> relationships among entities. diff --git a/src/patterns/structural/small-crates.md b/src/patterns/structural/small-crates.md index c652fc2..a95eb0a 100644 --- a/src/patterns/structural/small-crates.md +++ b/src/patterns/structural/small-crates.md @@ -5,27 +5,27 @@ Prefer small crates that do one thing well. Cargo and crates.io make it easy to add third-party libraries, much more so than -in say C or C++. Moreover, since packages on crates.io cannot be edited or removed -after publication, any build that works now should continue to work in the future. -We should take advantage of this tooling, and use smaller, more fine-grained dependencies. +in say C or C++. Moreover, since packages on crates.io cannot be edited or +removed after publication, any build that works now should continue to work in +the future. We should take advantage of this tooling, and use smaller, more +fine-grained dependencies. ## Advantages - Small crates are easier to understand, and encourage more modular code. -- Crates allow for re-using code between projects. - For example, the `url` crate was developed as part of the Servo browser engine, - but has since found wide use outside the project. -- Since the compilation unit - of Rust is the crate, splitting a project into multiple crates can allow more of - the code to be built in parallel. +- Crates allow for re-using code between projects. For example, the `url` crate + was developed as part of the Servo browser engine, but has since found wide + use outside the project. +- Since the compilation unit of Rust is the crate, splitting a project into + multiple crates can allow more of the code to be built in parallel. ## Disadvantages -- This can lead to "dependency hell", when a project depends on multiple conflicting - versions of a crate at the same time. For example, the `url` crate has both versions - 1.0 and 0.5. Since the `Url` from `url:1.0` and the `Url` from `url:0.5` are - different types, an HTTP client that uses `url:0.5` would not accept `Url` values - from a web scraper that uses `url:1.0`. +- This can lead to "dependency hell", when a project depends on multiple + conflicting versions of a crate at the same time. For example, the `url` crate + has both versions 1.0 and 0.5. Since the `Url` from `url:1.0` and the `Url` + from `url:0.5` are different types, an HTTP client that uses `url:0.5` would + not accept `Url` values from a web scraper that uses `url:1.0`. - Packages on crates.io are not curated. A crate may be poorly written, have unhelpful documentation, or be outright malicious. - Two small crates may be less optimized than one large one, since the compiler diff --git a/src/patterns/structural/unsafe-mods.md b/src/patterns/structural/unsafe-mods.md index c59463b..76a7a66 100644 --- a/src/patterns/structural/unsafe-mods.md +++ b/src/patterns/structural/unsafe-mods.md @@ -6,7 +6,8 @@ If you have `unsafe` code, create the smallest possible module that can uphold the needed invariants to build a minimal safe interface upon the unsafety. Embed this into a larger module that contains only safe code and presents an ergonomic interface. Note that the outer module can contain unsafe functions and methods -that call directly into the unsafe code. Users may use this to gain speed benefits. +that call directly into the unsafe code. Users may use this to gain speed +benefits. ## Advantages @@ -21,13 +22,13 @@ that call directly into the unsafe code. Users may use this to gain speed benefi ## Examples -- The [`toolshed`](https://docs.rs/toolshed) crate contains its unsafe operations - in submodules, presenting a safe interface to users. +- The [`toolshed`](https://docs.rs/toolshed) crate contains its unsafe + operations in submodules, presenting a safe interface to users. - `std`'s `String` class is a wrapper over `Vec` with the added invariant that the contents must be valid UTF-8. The operations on `String` ensure this - behavior. - However, users have the option of using an `unsafe` method to create a `String`, - in which case the onus is on them to guarantee the validity of the contents. + behavior. However, users have the option of using an `unsafe` method to create + a `String`, in which case the onus is on them to guarantee the validity of the + contents. ## See also diff --git a/src/refactoring/index.md b/src/refactoring/index.md index 178a53b..63c893f 100644 --- a/src/refactoring/index.md +++ b/src/refactoring/index.md @@ -1,12 +1,13 @@ # Refactoring -Refactoring is very important in relation to these topics. -Just as important as the other topics covered here, is how to take good code and -turn it into great code. - -We can use [design patterns](../patterns/index.md) to [DRY] up code and generalize -abstractions. We must avoid [anti-patterns](../anti_patterns/index.md) while we -do this. While they may be tempting to employ, their costs outweigh their benefits. +Refactoring is very important in relation to these topics. Just as important as +the other topics covered here, is how to take good code and turn it into great +code. + +We can use [design patterns](../patterns/index.md) to [DRY] up code and +generalize abstractions. We must avoid +[anti-patterns](../anti_patterns/index.md) while we do this. While they may be +tempting to employ, their costs outweigh their benefits. > Shortcuts make for long days. diff --git a/src/translations.md b/src/translations.md index 8d5697b..786d618 100644 --- a/src/translations.md +++ b/src/translations.md @@ -1,7 +1,9 @@ # Translations -We are utilizing [mdbook-i18n-helper](https://github.com/google/mdbook-i18n-helpers). -Please read up on how to _add_ and _update_ translations in [their repository](https://github.com/google/mdbook-i18n-helpers#creating-and-updating-translations) +We are utilizing +[mdbook-i18n-helper](https://github.com/google/mdbook-i18n-helpers). Please read +up on how to *add* and *update* translations in +[their repository](https://github.com/google/mdbook-i18n-helpers#creating-and-updating-translations) ## External translations diff --git a/template.md b/template.md index f5f6501..098dda4 100644 --- a/template.md +++ b/template.md @@ -12,8 +12,8 @@ A short, prose description of the pattern. ``` When writing examples, please try to make them compile. This allows us to test -them. If you fail to write an example that is both complete and readable, -please at least mark your example code with `ignore` as in here: +them. If you fail to write an example that is both complete and readable, please +at least mark your example code with `ignore` as in here: ```rust,ignore // A non-runnable example of the pattern in action, should be mostly code, commented