Shorten lines to line-length 80 (#208)

pull/100/head^2
simonsan 3 years ago committed by GitHub
parent 40d26477a7
commit 2cd70a552d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,7 +5,7 @@ MD003:
# Set maximum line length # Set maximum line length
MD013: MD013:
line_length: 150 line_length: 80
# Use `---` for horizontal rule # Use `---` for horizontal rule
MD035: MD035:

@ -2,66 +2,81 @@
## Introduction ## Introduction
This book is a catalogue of Rust programming techniques, (anti-)patterns, idioms and other explanations. This book is a catalogue of Rust programming techniques, (anti-)patterns,
It is a compilation of collective (sometimes implicit) knowledge as well as experiences that have emerged through collaborative work. 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. The patterns described here are __not rules__, but should be taken as
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. 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: If you want to be part of this effort here are some ways you can participate:
## Discussion board ## Discussion board
If you have a question or an idea regarding certain content but you want to have feedback of fellow community members If you have a question or an idea regarding certain content but you want to
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). 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).
## Writing a new article ## Writing a new article
Before writing a new article please check in one of the following resources if there is Before writing a new article please check in one of the following resources if
an existing discussion or if someone is already working on that topic: there is an existing discussion or if someone is already working on that topic:
- [Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116), - [Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116),
- [All issues](https://github.com/rust-unofficial/patterns/issues), - [All issues](https://github.com/rust-unofficial/patterns/issues),
- [Pull Requests](https://github.com/rust-unofficial/patterns/pulls) - [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) If you don't find an issue regarding your topic and you are sure it is not more
please open a new issue, so we can discuss about the ideas and future content of the article together and maybe feasible to open a thread in the [discussion board](https://github.com/rust-unofficial/patterns/discussions)
give some feedback/input on it. please open a new issue, so we can discuss about 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) into the When writing a new article it's recommended to copy the [pattern template](https://github.com/rust-unofficial/patterns/blob/master/template.md)
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. 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.
Consider writing your article in a way that has a low barrier of entry so also [Rustlings](https://github.com/rust-lang/rustlings) can follow Consider writing your article in a way that has a low barrier of entry so also
and understand the thought process behind it. So we can encourage people to use these patterns early on. [Rustlings](https://github.com/rust-lang/rustlings) can follow and understand
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 If you use links to blogposts or in general content that is not to be sure
with the [Wayback Machine](https://web.archive.org/) and use the link to that snapshot in your article. 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.
Don't forget to add your new article to the `SUMMARY.md` to let it be rendered to the book. Don't forget to add your new article to the `SUMMARY.md` to let it be rendered
to the book.
Please make `Draft Pull requests` early so we can follow your progress and can give early feedback (see the following section). Please make `Draft Pull requests` early so we can follow your progress and can
give early feedback (see the following section).
## Style guide ## Style guide
In order to have a consistent style across the book, we suggest to: 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 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: - 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<T>` instead of `Option`. - Prefer full types name. For example `Option<T>` instead of `Option`.
- Prefer line comments (`//`) over block comments (`/* */`) where applicable. - Prefer line comments (`//`) over block comments (`/* */`) where applicable.
## Check the article locally ## Check the article locally
Before submitting the PR launch the commands `mdbook build` to make sure that the book builds and `mdbook test` to make sure that Before submitting the PR launch the commands `mdbook build` to make sure that
code examples are correct. the book builds and `mdbook test` to make sure that code examples are correct.
### Markdown lint ### Markdown lint
To make sure the files comply with our Markdown style we use [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). 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 To spare you some manual work to get through the CI test you can use the
most of the emerging problems when writing Markdown files. following commands to automatically fix most of the emerging problems when
writing Markdown files.
- Install: - Install:
@ -81,13 +96,16 @@ most of the emerging problems when writing Markdown files.
"Release early and often!" also applies to pull requests! "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. Once your article has some visible work, create a `[WIP]` draft pull request
Early reviews of the community are not meant as an offense but to give feedback. 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." A good principle: "Work together, share ideas, teach others."
### Important Note ### Important Note
Please **don't force push** commits in your branch, in order to keep commit history and make it easier for us to see changes between reviews. Please **don't force push** commits in your branch, in order to keep commit
history and make it easier for us to see changes between reviews.
Make sure to `Allow edits of maintainers` (under the text box) in the PR so people can actually collaborate on things or fix smaller issues themselves. Make sure to `Allow edits of maintainers` (under the text box) in the PR so
people can actually collaborate on things or fix smaller issues themselves.

@ -5,28 +5,33 @@ language that you can read [here](https://rust-unofficial.github.io/patterns/).
## Contributing ## Contributing
You are missing content in this repository that can be helpful for others and you are eager to explain it? You are missing content in this repository that can be helpful for others and
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 You can check the [Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116)
patterns, anti-patterns, and idioms that could be added. 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 ## Building with mdbook
This book is built with [mdbook](https://rust-lang.github.io/mdBook/). You can install it by running `cargo install 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` - `mdbook build`
Builds static html pages as output and place them in the `/book` directory by default. Builds static html pages as output and place them in the `/book` directory by
default.
- `mdbook serve` - `mdbook serve`
Serves the book at `http://localhost:3000` (port is changeable, take a look at the terminal output Serves the book at `http://localhost:3000` (port is changeable, take a look at
to be sure) and reloads the browser when a change occurs. the terminal output to be sure) and reloads the browser when a change occurs.
## License ## License

@ -7,80 +7,89 @@
## [SOLID](https://en.wikipedia.org/wiki/SOLID) ## [SOLID](https://en.wikipedia.org/wiki/SOLID)
- [Single Responsibility Principle (SRP)](https://en.wikipedia.org/wiki/Single-responsibility_principle): - [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 A class should only have a single responsibility, that is, only changes to
specification should be able to affect the specification of the class. 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): - [Open/Closed Principle (OCP)](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle):
"Software entities ... should be open for extension, but closed for modification." "Software entities ... should be open for extension, but closed for
modification."
- [Liskov Substitution Principle (LSP)](https://en.wikipedia.org/wiki/Liskov_substitution_principle): - [Liskov Substitution Principle (LSP)](https://en.wikipedia.org/wiki/Liskov_substitution_principle):
"Objects in a program should be replaceable with instances of their subtypes without altering the correctness "Objects in a program should be replaceable with instances of their subtypes
of that program." without altering the correctness of that program."
- [Interface Segregation Principle (ISP)](https://en.wikipedia.org/wiki/Interface_segregation_principle): - [Interface Segregation Principle (ISP)](https://en.wikipedia.org/wiki/Interface_segregation_principle):
"Many client-specific interfaces are better than one general-purpose interface." "Many client-specific interfaces are better than one general-purpose
interface."
- [Dependency Inversion Principle (DIP)](https://en.wikipedia.org/wiki/Dependency_inversion_principle): - [Dependency Inversion Principle (DIP)](https://en.wikipedia.org/wiki/Dependency_inversion_principle):
One should "depend upon abstractions, [not] concretions." One should "depend upon abstractions, [not] concretions."
## [DRY (Dont Repeat Yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) ## [DRY (Dont Repeat Yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system" "Every piece of knowledge must have a single, unambiguous, authoritative
representation within a system"
## [KISS principle](https://en.wikipedia.org/wiki/KISS_principle) ## [KISS principle](https://en.wikipedia.org/wiki/KISS_principle)
most systems work best if they are kept simple rather than made complicated; therefore, most systems work best if they are kept simple rather than made complicated;
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) ## [Law of Demeter (LoD)](https://en.wikipedia.org/wiki/Law_of_Demeter)
a given object should assume as little as possible about the structure or properties of a given object should assume as little as possible about the structure or
anything else (including its subcomponents), in accordance with the principle of "information hiding" properties of anything else (including its subcomponents), in accordance with
the principle of "information hiding"
## [Design by contract (DbC)](https://en.wikipedia.org/wiki/Design_by_contract) ## [Design by contract (DbC)](https://en.wikipedia.org/wiki/Design_by_contract)
software designers should define formal, precise and verifiable interface specifications software designers should define formal, precise and verifiable interface
for software components, which extend the ordinary definition of abstract data types with specifications for software components, which extend the ordinary definition of
preconditions, postconditions and invariants abstract data types with preconditions, postconditions and invariants
## [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)) ## [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming))
bundling of data with the methods that operate on that data, or the restricting of bundling of data with the methods that operate on that data, or the restricting
direct access to some of an object's components. Encapsulation is used to hide the of direct access to some of an object's components. Encapsulation is used to
values or state of a structured data object inside a class, preventing unauthorized hide the values or state of a structured data object inside a class, preventing
parties' direct access to them. unauthorized parties' direct access to them.
## [Command-Query-Separation(CQS)](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation) ## [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 “Functions should not produce abstract side effects...only commands
be permitted to produce side effects.” - Bertrand Meyer: Object Oriented Software Construction (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) ## [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. a component of a system should behave in a way that most users will expect it
The behavior should not astonish or surprise users to behave. The behavior should not astonish or surprise users
## Linguistic-Modular-Units ## Linguistic-Modular-Units
“Modules must correspond to syntactic units in the language used.” - Bertrand Meyer: “Modules must correspond to syntactic units in the language used.” - Bertrand
Object Oriented Software Construction Meyer: Object Oriented Software Construction
## Self-Documentation ## Self-Documentation
“The designer of a module should strive to make all information about the module part of “The designer of a module should strive to make all information about the
the module itself.” - Bertrand Meyer: Object Oriented Software Construction module part of the module itself.” - Bertrand Meyer: Object Oriented Software
Construction
## Uniform-Access ## Uniform-Access
“All services offered by a module should be available through a uniform notation, “All services offered by a module should be available through a uniform
which does not betray whether they are implemented through storage or through computation.” - notation, which does not betray whether they are implemented through storage or
Bertrand Meyer: Object Oriented Software Construction through computation.” - Bertrand Meyer: Object Oriented Software Construction
## Single-Choice ## Single-Choice
“Whenever a software system must support a set of alternatives, one and only one module “Whenever a software system must support a set of alternatives, one and only
in the system should know their exhaustive list.” - Bertrand Meyer: Object Oriented one module in the system should know their exhaustive list.” - Bertrand Meyer:
Software Construction Object Oriented Software Construction
## Persistence-Closure ## Persistence-Closure
“Whenever a storage mechanism stores an object, it must store with it the dependents “Whenever a storage mechanism stores an object, it must store with it the
of that object. Whenever a retrieval mechanism retrieves a previously stored object, dependents of that object. Whenever a retrieval mechanism retrieves a
it must also retrieve any dependent of that object that has not yet been retrieved.” - previously stored object, it must also retrieve any dependent of that object
Bertrand Meyer: Object Oriented Software Construction that has not yet been retrieved.” - Bertrand Meyer: Object Oriented Software
Construction

@ -4,9 +4,12 @@ A collection of complementary helpful content
## Talks ## Talks
- [Design Patterns in Rust](https://www.youtube.com/watch?v=Pm_oO0N5B9k) by Nicholas Cameron at the PDRust (2016) - [Design Patterns in Rust](https://www.youtube.com/watch?v=Pm_oO0N5B9k) by
- [Writing Idiomatic Libraries in Rust](https://www.youtube.com/watch?v=0zOg8_B71gE) by Pascal Hertleif at RustFest (2017) Nicholas Cameron at the PDRust (2016)
- [Rust Programming Techniques](https://www.youtube.com/watch?v=vqavdUGKeb4) by Nicholas Cameron at LinuxConfAu (2018) - [Writing Idiomatic Libraries in Rust](https://www.youtube.com/watch?v=0zOg8_B71gE)
by Pascal Hertleif at RustFest (2017)
- [Rust Programming Techniques](https://www.youtube.com/watch?v=vqavdUGKeb4) by
Nicholas Cameron at LinuxConfAu (2018)
## Books (Online) ## Books (Online)

@ -1,7 +1,8 @@
# Anti-patterns # Anti-patterns
An [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern) is a solution to a An [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern) is a solution to
"recurring problem that is usually ineffective and risks being highly counterproductive". a "recurring problem that is usually ineffective and risks being highly
Just as valuable as knowing how to solve a problem, is knowing how _not_ to solve it. counterproductive". Just as valuable as knowing how to solve a problem, is
Anti-patterns give us great counter-examples to consider relative to design patterns. knowing how _not_ to solve it. Anti-patterns give us great counter-examples to
Anti-patterns are not confined to code. For example, a process can be an anti-pattern, too. consider relative to design patterns. Anti-patterns are not confined to code.
For example, a process can be an anti-pattern, too.

@ -1,8 +1,9 @@
# Programming paradigms # Programming paradigms
One of the biggest hurdles to understanding functional programs when coming from an imperative background is the shift in thinking. One of the biggest hurdles to understanding functional programs when coming
Imperative programs describe __how__ to do something, whereas declarative programs describe __what__ to do. from an imperative background is the shift in thinking. Imperative programs
Let's sum the numbers from 1 to 10 to show this. 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 ## Imperative
@ -33,7 +34,8 @@ Then we print it out.
| 9 | 45 | | 9 | 45 |
| 10 | 55 | | 10 | 55 |
This is how most of us start out programming. We learn that a program is a set of steps. This is how most of us start out programming. We learn that a program is a set
of steps.
## Declarative ## Declarative
@ -42,15 +44,16 @@ println!("{}", (1..11).fold(0, |a, b| a + b));
``` ```
Whoa! This is really different! What's going on here? 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. Remember that with declarative programs we are describing __what__ to do,
`fold` is a function that [composes](https://en.wikipedia.org/wiki/Function_composition) functions. rather than __how__ to do it. `fold` is a function that [composes](https://en.wikipedia.org/wiki/Function_composition)
The name is a convention from Haskell. 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. Here, we are composing functions of addition (this closure: `|a, b| a + b`)
The `0` is the starting point, so `a` is `0` at first. with a range from 1 to 10. The `0` is the starting point, so `a` is `0` at
`b` is the first element of the range, `1`. `0 + 1 = 1` is the result. 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. So now we `fold` again, with `a = 1`, `b = 2` and so `1 + 2 = 3` is the next
This process continues until we get to the last element in the range, `10`. result. This process continues until we get to the last element in the range,
`10`.
| `a` | `b` | result | | `a` | `b` | result |
|:---:|:---:|:------:| |:---:|:---:|:------:|

@ -2,24 +2,30 @@
## Description ## 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. 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. In this way, the function will accept more input types.
This is not limited to slice-able or fat pointer 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__. In fact you should always prefer using the __borrowed type__ over
__borrowing the owned type__.
Such as `&str` over `&String`, `&[T]` over `&Vec<T>`, or `&T` over `&Box<T>`. Such as `&str` over `&String`, `&[T]` over `&Vec<T>`, or `&T` over `&Box<T>`.
Using borrowed types you can avoid layers of indirection for those instances where the owned type already provides a layer of indirection. Using borrowed types you can avoid layers of indirection for those instances
For instance, a `String` has a layer of indirection, so a `&String` will have two layers of indrection. where the owned type already provides a layer of indirection. For instance, a
We can avoid this by using `&str` instead, and letting `&String` coerce to a `&str` whenever the function is invoked. `String` has a layer of indirection, so a `&String` will have two layers of
indrection. We can avoid this by using `&str` instead, and letting `&String`
coerce to a `&str` whenever the function is invoked.
## Example ## Example
For this example, we will illustrate some differences for using `&String` as a function argument versus using a `&str`, For this example, we will illustrate some differences for using `&String` as a
but the ideas apply as well to using `&Vec<T>` versus using a `&[T]` or using a `&T` versus a `&Box<T>`. function argument versus using a `&str`, but the ideas apply as well to using
`&Vec<T>` versus using a `&[T]` or using a `&T` versus a `&Box<T>`.
Consider an example where we wish to determine if a word contains three consecutive vowels. Consider an example where we wish to determine if a word contains three
We don't need to own the string to determine this, so we will take a reference. consecutive vowels. We don't need to own the string to determine this, so we
will take a reference.
The code might look something like this: The code might look something like this:
@ -54,8 +60,9 @@ fn main() {
``` ```
This works fine because we are passing a `&String` type as a parameter. This works fine because we are passing a `&String` type as a parameter.
If we comment in the last two lines this example fails because a `&str` type will not coerce to a `&String` type. If we comment in the last two lines this example fails because a `&str` type
We can fix this by simply modifying the type for our argument. 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: For instance, if we change our function declaration to:
@ -71,12 +78,16 @@ Curious: true
``` ```
But wait, that's not all! There is more to this story. 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"`). It's likely that you may say to yourself: that doesn't matter, I will never be
Even ignoring this special example, you may still find that using `&str` will give you more flexibility than using a `&String`. 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 Let's now take an example where someone gives us a sentence, and we want to
in the sentence has a word that contains three consecutive vowels. determine if any of the words in the sentence has a word that contains three
We probably should make use of the function we have already defined and simply feed in each word from the sentence. consecutive vowels.
We probably should make use of the function we have already defined and simply
feed in each word from the sentence.
An example of this could look like this: An example of this could look like this:
@ -108,15 +119,17 @@ fn main() {
} }
``` ```
Running this example using our function declared with an argument type `&str` will yield Running this example using our function declared with an argument type `&str`
will yield
```bash ```bash
curious has three consecutive vowels! curious has three consecutive vowels!
``` ```
However, this example will not run when our function is declared with an argument type `&String`. However, this example will not run when our function is declared with an
This is because string slices are a `&str` and not a `&String` which would require an allocation to be argument type `&String`. This is because string slices are a `&str` and not a
converted to `&String` which is not implicit, whereas converting from `String` to `&str` is cheap and implicit. `&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.
## See also ## See also

@ -2,8 +2,8 @@
## Description ## Description
Rust does not have constructors as a language construct. Instead, the convention Rust does not have constructors as a language construct. Instead, the
is to use a static `new` method to create an object. convention is to use a static `new` method to create an object.
## Example ## Example
@ -32,5 +32,5 @@ impl<T> Vec<T> {
## See also ## See also
The [builder pattern](../patterns/builder.md) for constructing objects where there are multiple The [builder pattern](../patterns/builder.md) for constructing objects where
configurations. there are multiple configurations.

@ -2,18 +2,21 @@
## Description ## Description
When accepting strings via FFI through pointers, there are two principles that should be followed: When accepting strings via FFI through pointers, there are two principles that
should be followed:
1. Keep foreign strings "borrowed", rather than copying them directly. 1. Keep foreign strings "borrowed", rather than copying them directly.
2. Minimize `unsafe` code during the conversion. 2. Minimize `unsafe` code during the conversion.
## Motivation ## Motivation
Rust has built-in support for C-style strings with its `CString` and `CStr` types. Rust has built-in support for C-style strings with its `CString` and `CStr`
However, there are different approaches one can take with strings that are being accepted from a foreign caller of a Rust function. types. However, there are different approaches one can take with strings that
are being accepted from a foreign caller of a Rust function.
The best practice is simple: use `CStr` in such a way as to minimize unsafe code, and create a borrowed slice. The best practice is simple: use `CStr` in such a way as to minimize unsafe
If an owned String is needed, call `to_string()` on the string slice. code, and create a borrowed slice. If an owned String is needed, call
`to_string()` on the string slice.
## Code Example ## Code Example
@ -49,7 +52,8 @@ pub mod unsafe_module {
The example is is written to ensure that: The example is is written to ensure that:
1. The `unsafe` block is as small as possible. 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: Consider an alternative, where the string is actually copied:
@ -59,7 +63,8 @@ pub mod unsafe_module {
// other module content // other module content
pub extern "C" fn mylib_log(msg: *const libc::c_char, level: libc::c_int) { pub extern "C" fn mylib_log(msg: *const libc::c_char, level: libc::c_int) {
/* DO NOT USE THIS CODE. IT IS UGLY, VERBOSE, AND CONTAINS A SUBTLE BUG. */ // DO NOT USE THIS CODE.
// IT IS UGLY, VERBOSE, AND CONTAINS A SUBTLE BUG.
let level: crate::LogLevel = match level { /* ... */ }; let level: crate::LogLevel = match level { /* ... */ };
@ -96,20 +101,24 @@ pub mod unsafe_module {
This code in inferior to the original in two respects: 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. 1. There is much more `unsafe` code, and more importantly, more invariants it
2. Due to the extensive arithmetic required, there is a bug in this version that cases Rust `undefined behaviour`. must uphold.
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. The bug here is a simple mistake in pointer arithmetic: the string was copied,
However, the `NUL` terminator at the end was not. 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* -- 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. 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. 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`! 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. 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 panic because the string was not `UTF-8`, sometimes it would
sometimes it would just completely crash. put a weird character at the end of the string, sometimes it would just
completely crash.
## Disadvantages ## Disadvantages

@ -3,12 +3,15 @@
## Description ## Description
In foreign languages like C, errors are represented by return codes. 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 a propogated through a full type. However, Rust's type system allows much more rich error information to be
captured a propogated through a full type.
This best practice shows different kinds of error codes, and how to expose them in a usable way: This best practice shows different kinds of error codes, and how to expose them
in a usable way:
1. Flat Enums should be converted to integers and returned as codes. 1. Flat Enums should be converted to integers and returned as codes.
2. Structured Enums should be converted to an integer code with a string error message for detail. 2. Structured Enums should be converted to an integer code with a string error
message for detail.
3. Custom Error Types should become "transparent", with a C representation. 3. Custom Error Types should become "transparent", with a C representation.
## Code Example ## Code Example
@ -59,7 +62,7 @@ pub mod c_api {
) -> *mut libc::c_char { ) -> *mut libc::c_char {
let error: &DatabaseError = unsafe { let error: &DatabaseError = unsafe {
/* SAFETY: pointer lifetime is greater than the current stack frame */ // SAFETY: pointer lifetime is greater than the current stack frame
&*e &*e
}; };
@ -127,8 +130,10 @@ impl From<ParseError> for parse_error {
## Advantages ## Advantages
This ensures that the foreign language has clear access to error information while not compromising the Rust code's API at all. This ensures that the foreign language has clear access to error information
while not compromising the Rust code's API at all.
## Disadvantages ## 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.

@ -1,11 +1,13 @@
# FFI Idioms # FFI Idioms
Writing FFI code is an entire course in itself. 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. 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. This section contains idioms that may be useful when doing FFI.
1. [Idiomatic Errors](./ffi-errors.md) - Error handling with integer codes and sentinel return values (such as `NULL` pointers) 1. [Idiomatic Errors](./ffi-errors.md) - Error handling with integer codes and
sentinel return values (such as `NULL` pointers)
2. [Accepting Strings](./ffi-accepting-strings.md) with minimal unsafe code 2. [Accepting Strings](./ffi-accepting-strings.md) with minimal unsafe code

@ -2,21 +2,26 @@
## Description ## Description
When passing strings to FFI functions, there are four principles that should be followed: When passing strings to FFI functions, there are four principles that should be
followed:
1. Make the lifetime of owned strings as long as possible. 1. Make the lifetime of owned strings as long as possible.
2. Minimize `unsafe` code during the conversion. 2. Minimize `unsafe` code during the conversion.
3. If the C code can modify the string data, use `Vec` instead of `CString`. 3. If the C code can modify the string data, use `Vec` instead of `CString`.
4. Unless the Foreign Function API requires it, the ownership of the string should not transfer to the callee. 4. Unless the Foreign Function API requires it, the ownership of the string
should not transfer to the callee.
## Motivation ## Motivation
Rust has built-in support for C-style strings with its `CString` and `CStr` types. Rust has built-in support for C-style strings with its `CString` and `CStr`
However, there are different approaches one can take with strings that are being sent to a foreign function call from a Rust function. 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. The best practice is simple: use `CString` in such a way as to minimize
However, a secondary caveat is that *the object must live long enough*, meaning the lifetime should be maximized. `unsafe` code. However, a secondary caveat is that
In addition, the documentation explains that "round-tripping" a `CString` after modification is UB, so additional work is necessary in that case. *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 ## Code Example
@ -68,7 +73,8 @@ The example is written in a way to ensure that:
2. The `CString` lives long enough. 2. The `CString` lives long enough.
3. Errors with typecasts are always propagated when possible. 3. Errors with typecasts are always propagated when possible.
A common mistake (so common it's in the documentation) is to not use the variable in the first block: A common mistake (so common it's in the documentation) is to not use the
variable in the first block:
```rust,ignore ```rust,ignore
pub mod unsafe_module { pub mod unsafe_module {
@ -85,12 +91,14 @@ pub mod unsafe_module {
} }
``` ```
This code will result in a dangling pointer, because the lifetime of the `CString` is not extended This code will result in a dangling pointer, because the lifetime of the
by the pointer creation, unlike if a reference were created. `CString` is not extended by the pointer creation, unlike if a reference were
created.
Another issue frequently raised is that the initialization of a 1k vector of zeroes is "slow". Another issue frequently raised is that the initialization of a 1k vector of
However, recent versions of Rust actually optimize that particular macro to a call to `zmalloc`, zeroes is "slow". However, recent versions of Rust actually optimize that
meaning it is as fast as the operating system's ability to return zeroed memory (which is quite fast). particular macro to a call to `zmalloc`, meaning it is as fast as the operating
system's ability to return zeroed memory (which is quite fast).
## Disadvantages ## Disadvantages

@ -1,15 +1,18 @@
# Idioms # Idioms
[Idioms](https://en.wikipedia.org/wiki/Programming_idiom) are commonly used styles and patterns largely agreed upon by a community. [Idioms](https://en.wikipedia.org/wiki/Programming_idiom) are commonly used
They are guidelines. styles and patterns largely agreed upon by a community. They are guidelines.
Writing idiomatic code allows other developers to understand what is happening because they are familiar with the form that it has. Writing idiomatic code allows other developers to understand what is happening
because they are familiar with the form that it has.
The computer understands the machine code that is generated by the compiler. The computer understands the machine code that is generated by the compiler.
The language is therefore mostly beneficial to the developer. The language is therefore mostly beneficial to the developer.
So, since we have this abstraction layer, why not put it to good use and make it simple? So, since we have this abstraction layer, why not put it to good use and make
it simple?
Remember the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle): "Keep It Simple, Stupid". Remember the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle):
It claims that "most systems work best if they are kept simple rather than made complicated; "Keep It Simple, Stupid". It claims that "most systems work best if they are
therefore, simplicity should be a key goal in design, and unnecessary complexity should be avoided". kept simple rather than made complicated; therefore, simplicity should be a key
goal in design, and unnecessary complexity should be avoided".
> Code is there for humans, not computers, to understand. > Code is there for humans, not computers, to understand.

@ -2,12 +2,14 @@
## Description ## Description
`Option` can be viewed as a container that contains either zero or one elements. `Option` can be viewed as a container that contains either zero or one
In particular, it implements the `IntoIterator` trait, and as such can be used with generic code that needs such a type. elements. In particular, it implements the `IntoIterator` trait, and as such
can be used with generic code that needs such a type.
## Examples ## Examples
Since `Option` implements `IntoIterator`, it can be used as an argument to [`.extend()`](https://doc.rust-lang.org/std/iter/trait.Extend.html#tymethod.extend): Since `Option` implements `IntoIterator`, it can be used as an argument to
[`.extend()`](https://doc.rust-lang.org/std/iter/trait.Extend.html#tymethod.extend):
```rust ```rust
let turing = Some("Turing"); let turing = Some("Turing");
@ -21,7 +23,8 @@ 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 ```rust
let turing = Some("Turing"); let turing = Some("Turing");
@ -32,21 +35,25 @@ for logician in logicians.iter().chain(turing.iter()) {
} }
``` ```
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) Note that if the `Option` is always `Some`, then it is more idiomatic to use
on the element instead. [`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. Also, since `Option` implements `IntoIterator`, it's possible to iterate over
This is equivalent to matching it with `if let Some(..)`, and in most cases you should prefer the latter. 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 ## See also
* [`std::iter::once`](https://doc.rust-lang.org/std/iter/fn.once.html) is an iterator which yields exactly one element. * [`std::iter::once`](https://doc.rust-lang.org/std/iter/fn.once.html) is an
It's a more readable alternative to `Some(foo).into_iter()`. iterator which yields exactly one element. It's a more readable alternative to
`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::filter_map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map)
[`Iterator::flat_map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flat_map), specialized to mapping functions which is a version of [`Iterator::flat_map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flat_map),
return `Option`. specialized to mapping functions which return `Option`.
* The [`ref_slice`](https://crates.io/crates/ref_slice) crate provides functions for converting an `Option` to a zero- or one-element slice. * The [`ref_slice`](https://crates.io/crates/ref_slice) crate provides functions
for converting an `Option` to a zero- or one-element slice.
* [Documentation for `Option<T>`](https://doc.rust-lang.org/std/option/enum.Option.html) * [Documentation for `Option<T>`](https://doc.rust-lang.org/std/option/enum.Option.html)

@ -2,9 +2,10 @@
## Description ## Description
By default, closures capture their environment by borrowing. Or you can use `move`-closure By default, closures capture their environment by borrowing. Or you can use
to move whole environment. However, often you want to move just some variables to closure, `move`-closure to move whole environment. However, often you want to move just
give it copy of some data, pass it by reference, or perform some other transformation. some variables to closure, give it copy of some data, pass it by reference, or
perform some other transformation.
Use variable rebinding in separate scope for that. Use variable rebinding in separate scope for that.
@ -46,10 +47,12 @@ let closure = move || {
## Advantages ## Advantages
Copied data are grouped together with closure definition, so their purpose is more clear Copied data are grouped together with closure definition, so their purpose is
and they will be dropped immediately even if they are not consumed by closure. more clear and they will be dropped immediately even if they are not consumed
by closure.
Closure uses same variable names as surrounding code whether data are copied or moved. Closure uses same variable names as surrounding code whether data are copied or
moved.
## Disadvantages ## Disadvantages

@ -28,15 +28,19 @@ fn main(s: a::S) {
## Discussion ## Discussion
Adding a field to a struct is a mostly backwards compatible change. 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 However, if a client uses a pattern to deconstruct a struct instance, they
and adding a new one would break that pattern. might name all the fields in the struct and adding a new one would break that
The client could name some of the fields and use `..` in the pattern, in which case adding another field is backwards compatible. pattern. The client could name some of the fields and use `..` in the pattern,
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. 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,
The downside of this approach is that you might need to add an otherwise unneeded field to the struct. ensuring that the struct is future-proof.
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
If Rust allowed private variants of enums, we could use the same trick to make adding a variant to an enum backwards compatible. field to the struct. You can use the `()` type so that there is no runtime overhead
The problem there is exhaustive match expressions. and prepend `_` to the field name to avoid the unused field warning.
A private variant would force clients to have a `_` wildcard pattern.
A common way to implement this instead is using the [#[non_exhaustive]](https://doc.rust-lang.org/reference/attributes/type_system.html) attribute. If Rust allowed private variants of enums, we could use the same trick to make
adding a variant to an enum backwards compatible. The problem there is exhaustive
match expressions. A private variant would force clients to have a `_` wildcard
pattern. A common way to implement this instead is using the [`#[non_exhaustive]`](<https://doc.rust-lang.org/reference/attributes/type_system.html>)
attribute.

@ -8,8 +8,8 @@ argument.
## Motivation ## Motivation
Sometimes there is a struct with multiple or complicated parameters and several methods. Sometimes there is a struct with multiple or complicated parameters and several
Each of these methods should have examples. methods. Each of these methods should have examples.
For example: For example:
@ -70,8 +70,9 @@ impl Connection {
} }
``` ```
**Note** in the above example the line `assert!(response.is_ok());` will not actually run while testing **Note** in the above example the line `assert!(response.is_ok());` will not
because it is inside of a function which is never invoked. actually run while testing because it is inside of a function which is never
invoked.
## Advantages ## Advantages
@ -79,8 +80,9 @@ This is much more concise and avoids repetitive code in examples.
## Disadvantages ## Disadvantages
As example is in a function, the code will not be tested. (Though it still will checked to make sure it compiles when running a `cargo test`) As example is in a function, the code will not be tested. Though it still will
So this pattern is most useful when need `no_run`. With this, you do not need to add `no_run`. checked to make sure it compiles when running a `cargo test`. So this pattern is
most useful when need `no_run`. With this, you do not need to add `no_run`.
## Discussion ## Discussion

@ -2,10 +2,12 @@
## Description ## Description
Often it is necessary to prepare and process some data, but after that data are only inspected Often it is necessary to prepare and process some data, but after that data are
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 nested block or by redefining variable. It can be done either by processing data within nested block or by redefining
variable.
## Example ## Example

@ -29,4 +29,5 @@ solved using the same fundamental methods:
They are social norms of the community. They are social norms of the community.
You can break them, but if you do you should have a good reason for it. You can break them, but if you do you should have a good reason for it.
TODO: Mention why Rust is a bit special - functional elements, type system, borrow checker TODO: Mention why Rust is a bit special - functional elements, type system,
borrow checker

@ -2,10 +2,10 @@
## Description ## Description
[RAII][wikipedia] stands for "Resource Acquisition is Initialisation" which is a terrible [RAII][wikipedia] stands for "Resource Acquisition is Initialisation" which is a
name. The essence of the pattern is that resource initialisation is done in the terrible name. The essence of the pattern is that resource initialisation is done
constructor of an object and finalisation in the destructor. This pattern is in the constructor of an object and finalisation in the destructor. This pattern
extended in Rust by using an RAII object as a guard of some resource and relying is extended in Rust by using an RAII object as a guard of some resource and relying
on the type system to ensure that access is always mediated by the guard object. on the type system to ensure that access is always mediated by the guard object.
## Example ## Example

@ -33,9 +33,11 @@ impl FooBuilder {
} }
// If we can get away with not consuming the Builder here, that is an // If we can get away with not consuming the Builder here, that is an
// advantage. It means we can use the FooBuilder as a template for constructing many Foos. // advantage. It means we can use the FooBuilder as a template for constructing
// many Foos.
pub fn build(self) -> Foo { pub fn build(self) -> Foo {
// Create a Foo from the FooBuilder, applying all settings in FooBuilder to Foo. // Create a Foo from the FooBuilder, applying all settings in FooBuilder
// to Foo.
Foo { bar: self.bar } Foo { bar: self.bar }
} }
} }
@ -99,7 +101,8 @@ as well as the `FooBuilder::new().a().b().build()` style.
## See also ## 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) - [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. - [Constructor pattern](../idioms/ctor.md) for when construction is simpler.
- [Builder pattern (wikipedia)](https://en.wikipedia.org/wiki/Builder_pattern) - [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) - [Construction of complex values](https://web.archive.org/web/20210104103000/https://rust-lang.github.io/api-guidelines/type-safety.html#c-builder)

@ -33,7 +33,8 @@ fn baz(a: &mut A) {
// The later usage of x causes a to be borrowed for the rest of the function. // The later usage of x causes a to be borrowed for the rest of the function.
let x = foo(a); let x = foo(a);
// Borrow checker error: // Borrow checker error:
// let y = bar(a); // ~ ERROR: cannot borrow `*a` as mutable more than once at a time // let y = bar(a); // ~ ERROR: cannot borrow `*a` as mutable more than once
// at a time
println!("{}", x); println!("{}", x);
} }
``` ```

@ -2,40 +2,49 @@
## Description ## Description
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: 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*. 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*. 2. All Transactional data types should be *owned* by the user, and *transparent*.
3. All library behavior should be functions acting upon Encapsulated types. 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 ## Motivation
Rust has built-in FFI support to other languages. 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). 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. Well-designed Rust FFI follows C API design principles, while compromising the
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. 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. 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. 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. Rust code must trust the memory safety of the foreign language beyond a certain
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. For example, if a pointer provenance is wrong, that may be a segfault due to
But if it is manipulated by unsafe code, it could become full-blown heap corruption. 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 ## 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. 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. Here is the definition in C, which hopefully should be easy to read for those
The commentary below should help explaining it for those who miss the subtleties. involved in FFI. The commentary below should help explaining it for those who
miss the subtleties.
```C ```C
struct DBM; struct DBM;
@ -55,54 +64,67 @@ int dbm_store(DBM *, datum, datum, int);
This API defines two types: `DBM` and `datum`. This API defines two types: `DBM` and `datum`.
The `DBM` type was called an "encapsulated" type above. 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 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. It is completely opaque to the user, who cannot create a `DBM` themselves since
Instead, they must call `dbm_open`, and that only gives them *a pointer to one*. 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. 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 internal state of unknown size is kept in memory controlled by the library,
The user can only manage its life cycle with `open` and `close`, and perform operations on it with the other functions. 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. The `datum` type was called a "transactional" type above.
It is designed to facilitate the exchange of information between the library and its user. 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. The database is designed to store "unstructured data", with no pre-defined length
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. or meaning. As a result, the `datum` is the C equivalent of a Rust slice: a bunch
The main difference is that there is no type information, which is what `void` indicates. 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. 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. 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`. 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. 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. This has subtle ramifications, due to that pointer inside it.
The question is, who owns the memory that pointer points to? The question is, who owns the memory that pointer points to?
The answer for best memory safety is, "the user". 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). But in cases such as retrieving a value, the user does not know how to allocate
In this case, the library code is expected to use the heap that the user has access to -- it correctly (since they don't know how long the value is). In this case, the library
such as the C library `malloc` and `free` -- and then *transfer ownership* in the Rust sense. 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. This may all seem speculative, but this is what a pointer means in C.
It means the same thing as Rust: "user defined lifetime." 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. 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. That said, there are some decisions that have fewer or greater consequences if users
Minimizing those is what this best practice is about, and the key is to *transfer ownership of everything that is transparent*. do it wrong. Minimizing those is what this best practice is about, and the key
is to *transfer ownership of everything that is transparent*.
## Advantages ## Advantages
This minimizes the number of memory safety guarantees the user must uphold to a relatively small number: This minimizes the number of memory safety guarantees the user must uphold to a
relatively small number:
1. Do not call any function with a pointer not returned by `dbm_open` (invalid access or corruption). 1. Do not call any function with a pointer not returned by `dbm_open` (invalid
access or corruption).
2. Do not call any function on a pointer after close (use after free). 2. Do not call any function on a pointer after close (use after free).
3. The `dptr` on any `datum` must be `NULL`, or point to a valid slice of memory at the advertised length. 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. In addition, it avoids a lot of pointer provenance issues.
To understand why, let us consider an alternative in some depth: key iteration. To understand why, let us consider an alternative in some depth: key iteration.
Rust is well known for its iterators. 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. 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`: Here is how iteration would be done in Rust for `DBM`:
@ -128,26 +150,31 @@ However, consider what a straightforward API translation would look like:
```rust,ignore ```rust,ignore
#[no_mangle] #[no_mangle]
pub extern "C" fn dbm_iter_new(owner: *const Dbm) -> *mut DbmKeysIter { pub extern "C" fn dbm_iter_new(owner: *const Dbm) -> *mut DbmKeysIter {
/* THIS API IS A BAD IDEA! For real applications, use object-based design instead. */ // THIS API IS A BAD IDEA! For real applications, use object-based design instead.
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn dbm_iter_next(iter: *mut DbmKeysIter, key_out: *const datum) -> libc::c_int { pub extern "C" fn dbm_iter_next(
/* THIS API IS A BAD IDEA! For real applications, use object-based design instead. */ iter: *mut DbmKeysIter,
key_out: *const datum
) -> libc::c_int {
// THIS API IS A BAD IDEA! For real applications, use object-based design instead.
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn dbm_iter_del(*mut DbmKeysIter) { pub extern "C" fn dbm_iter_del(*mut DbmKeysIter) {
/* THIS API IS A BAD IDEA! For real applications, use object-based design instead. */ // THIS API IS A BAD IDEA! For real applications, use object-based design instead.
} }
``` ```
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. This API loses a key piece of information: the lifetime of the iterator must not
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: This example written in C contains a bug that will be explained afterwards:
```C ```C
int count_key_sizes(DBM *db) { int count_key_sizes(DBM *db) {
/* DO NOT USE THIS FUNCTION. IT HAS A SUBTLE BUT SERIOUS BUG! */ // DO NOT USE THIS FUNCTION. IT HAS A SUBTLE BUT SERIOUS BUG!
datum key; datum key;
int len = 0; int len = 0;
@ -172,26 +199,30 @@ int count_key_sizes(DBM *db) {
} }
``` ```
This bug is a classic. Here's what happens when the iterator returns the end-of-iteration marker: This bug is a classic. Here's what happens when the iterator returns the
end-of-iteration marker:
1. The loop condition sets `l` to zero, and enters the loop because `0 >= 0`. 1. The loop condition sets `l` to zero, and enters the loop because `0 >= 0`.
2. The length is incremented, in this case by zero. 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. 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? The worst part about this bug?
If the Rust implementation was careful, this code will work most of the time! 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, If the memory for the `Dbm` object is not immediately reused, an internal check
resulting in the iterator returning a `-1` indicating an error. will almost certainly fail, resulting in the iterator returning a `-1` indicating
But occasionally, it will cause a segmentation fault, or even worse, nonsensical memory corruption! an error. But occasionally, it will cause a segmentation fault, or even worse,
nonsensical memory corruption!
None of this can be avoided by Rust. 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. From its perspective, it put those objects on its heap, returned pointers to them,
The C code simply must "play nice". and gave up control of their lifetimes. The C code simply must "play nice".
The programmer must read and understand the API documentation. 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. While some consider that par for the course in C, a good API design can mitigate
The POSIX API for `DBM` did this by *consolidating the ownership* of the iterator with its parent: this risk. The POSIX API for `DBM` did this by *consolidating the ownership* of
the iterator with its parent:
```C ```C
datum dbm_firstkey(DBM *); datum dbm_firstkey(DBM *);
@ -202,22 +233,28 @@ Thus, all of the lifetimes were bound together, and such unsafety was prevented.
## Disadvantages ## Disadvantages
However, this design choice also has a number of drawbacks, which should be considered as well. However, this design choice also has a number of drawbacks, which should be
considered as well.
First, the API itself becomes less expressive. First, the API itself becomes less expressive.
With POSIX DBM, there is only one iterator per object, and every call changes its state. With POSIX DBM, there is only one iterator per object, and every call changes
This is much more restrictive than iterators in almost any language, even though it is safe. its state. This is much more restrictive than iterators in almost any language,
Perhaps with other related objects, whose lifetimes are less hierarchical, this limitation is more of a cost than the safety. 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. Second, depending on the relationships of the API's parts, significant design effort
Many of the easier design points have other patterns associated with them: may be involved. Many of the easier design points have other patterns associated
with them:
- [Wrapper Type Consolidation](./ffi-wrappers.md) groups multiple Rust types together into an opaque "object" - [Wrapper Type Consolidation](./ffi-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, - [Accepting Foreign Strings](../idioms/ffi-accepting-strings.md) allows accepting
and is easier to get right than [Passing Strings to FFI](../idioms/ffi-passing-strings.md) 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. 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. It is up to the best judgement of the programmer as to who their audience is.

@ -1,10 +1,13 @@
# FFI Patterns # FFI Patterns
Writing FFI code is an entire course in itself. 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. 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. This section contains design patterns that may be useful when doing FFI.
1. [Object-Based API](./ffi-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](./ffi-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](./ffi-wrappers.md) - group multiple Rust types together into an opaque "object" 2. [Type Consolidation into Wrappers](./ffi-wrappers.md) - group multiple Rust types
together into an opaque "object"

@ -2,32 +2,39 @@
## Description ## Description
This pattern is designed to allow gracefully handling multiple related types, while minimizing the surface area for memory unsafety. 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. 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. 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. However, when Rust types are exported to other languages, they are usually transformed
In Rust, a pointer means "the user manages the lifetime of the pointee." It is their responsibility to avoid memory unsafety. 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. Some level of trust in the user code is thus required, notably around use-after-free
However, some API designs place higher burdens than others on the code written in the other language. 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 The lowest risk API is the "consolidated wrapper", where all possible interactions
are folded into a "wrapper type", while keeping the Rust API clean. with an object are folded into a "wrapper type", while keeping the Rust API clean.
## Code Example ## 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: That API looks like this:
1. The iterator is initialized with `first_key`. 1. The iterator is initialized with `first_key`.
2. Each call to `next_key` will advance the iterator. 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. 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: If the iterator implements `nth()` efficiently, then it is possible to make it
ephemeral to each function call:
```rust,ignore ```rust,ignore
struct MySetWrapper { struct MySetWrapper {
@ -56,20 +63,24 @@ As a result, the wrapper is simple and contains no `unsafe` code.
## Advantages ## Advantages
This makes APIs safer to use, avoiding issues with lifetimes between types. This makes APIs safer to use, avoiding issues with lifetimes between types.
See [Object-Based APIs](./ffi-export.md) for more on the advantages and pitfalls this avoids. See [Object-Based APIs](./ffi-export.md) for more on the advantages and pitfalls
this avoids.
## Disadvantages ## Disadvantages
Often, wrapping types is quite difficult, and sometimes a Rust API compromise would make things easier. 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()`. 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, It would definitely be worth putting in special logic to make the object handle
or to support a different access pattern efficiently that only the Foreign Function API will use. iteration internally, or to support a different access pattern efficiently that
only the Foreign Function API will use.
### Trying to Wrap Iterators (and Failing) ### Trying to Wrap Iterators (and Failing)
To wrap any type of iterator into the API correctly, the wrapper would need to do what a C version of To wrap any type of iterator into the API correctly, the wrapper would need to
the code would do: erase the lifetime of the iterator, and manage it manually. 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.
@ -86,12 +97,14 @@ struct MySetWrapper {
} }
``` ```
With `transmute` being used to extend a lifetime, and a pointer to hide it, it's ugly already. With `transmute` being used to extend a lifetime, and a pointer to hide it,
But it gets even worse: *any other operation can cause Rust `undefined behaviour`*. 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, Consider that the `MySet` in the wrapper could be manipulated by other
such as storing a new value to the key it was iterating over. functions during iteration, such as storing a new value to the key it was
The API doesn't discourage this, and in fact some similar C libraries expect it. 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: A simple implementation of `myset_store` would be:
@ -105,7 +118,7 @@ pub mod unsafe_module {
key: datum, key: datum,
value: datum) -> libc::c_int { value: datum) -> libc::c_int {
/* DO NOT USE THIS CODE. IT IS UNSAFE TO DEMONSTRATE A PROLBEM. */ // DO NOT USE THIS CODE. IT IS UNSAFE TO DEMONSTRATE A PROLBEM.
let myset: &mut MySet = unsafe { // SAFETY: whoops, UB occurs in here! let myset: &mut MySet = unsafe { // SAFETY: whoops, UB occurs in here!
&mut (*myset).myset &mut (*myset).myset
@ -121,23 +134,29 @@ pub mod unsafe_module {
} }
``` ```
If the iterator exists when this function is called, we have violated one of Rust's aliasing rules. If the iterator exists when this function is called, we have violated one of Rust's
According to Rust, the mutable reference in this block must have *exclusive* access to the object. aliasing rules. According to Rust, the mutable reference in this block must have
If the iterator simply exists, it's not exclusive, so we have `undefined behaviour`! [^1] *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. 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. That basically means clearing out the iterator's shared reference while it exists,
In most cases, that will still be less efficient than the C version. 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? 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. The answer is, it cheats. Rust's aliasing rules are the problem, and C simply ignores
In exchange, it is common to see code that is declared in the manual as "not thread safe" under some or all circumstances. them for its pointers. In exchange, it is common to see code that is declared
In fact, [The GNU C library has an entire lexicon dedicated to concurrent behavior!](https://manpages.debian.org/buster/manpages/attributes.7.en.html) 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)
Rust would rather make everything memory safe all the time, for both safety and optimizations that C code cannot attain. has an entire lexicon dedicated to concurrent behavior!
Being denied access to certain shortcuts is the price Rust programmers need to pay.
Rust would rather make everything memory safe all the time, for both safety and
[^1]: For the C programmers out there scratching their heads, the iterator need not be read *during* this code cause the UB. optimizations that C code cannot attain. Being denied access to certain shortcuts
The exclusivity rule also enables compiler optimizations which may cause inconsistent observations by the iterator's 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). shared reference (e.g. stack spills or reordering instructions for efficiency).
These observations may happen *any time after* the mutable reference is created. These observations may happen *any time after* the mutable reference is created.

@ -8,22 +8,24 @@ what is a pattern in one language may be unnecessary in another due to a
language feature, or impossible to express due to a missing feature. language feature, or impossible to express due to a missing feature.
If overused, design patterns can add unnecessary complexity to programs. If overused, design patterns can add unnecessary complexity to programs.
However, they are a great way to share intermediate and advanced level knowledge about a programming language. However, they are a great way to share intermediate and advanced level knowledge
about a programming language.
## Design patterns in Rust ## Design patterns in Rust
Rust has many very unique features. These features give us great benefit by removing whole classes of problems. Rust has many very unique features. These features give us great benefit by removing
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
If you're not familiar with it, YAGNI is an acronym that stands for `You Aren't Going to Need It`. If you're not familiar with it, YAGNI is an acronym that stands for
It's an important software design principle to apply as you write code. `You Aren't Going to Need It`. It's an important software design principle to apply
as you write code.
> The best code I ever wrote was code I never wrote. > 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. If we apply YAGNI to design patterns, we see that the features of Rust allow us to
For instance, there is no need for the [strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern) in Rust throw out many patterns. For instance, there is no need for the [strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern)
because we can just use [traits](https://doc.rust-lang.org/book/traits.html). in Rust because we can just use [traits](https://doc.rust-lang.org/book/traits.html).
TODO: Maybe include some code to illustrate the traits. TODO: Maybe include some code to illustrate the traits.

@ -7,7 +7,8 @@ not be enough?
For example, if we want to create a custom `Display` implementation for `String` For example, if we want to create a custom `Display` implementation for `String`
due to security considerations (e.g. passwords). 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 ## Description
@ -88,20 +89,23 @@ 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, - making a type with copy semantics have move semantics,
- abstraction by providing a more concrete type and thus hiding internal types, e.g., - abstraction by providing a more concrete type and thus hiding internal types,
e.g.,
```rust,ignore ```rust,ignore
pub struct Foo(Bar<T1, T2>); pub struct Foo(Bar<T1, T2>);
``` ```
Here, `Bar` might be some public, generic type and `T1` and `T2` are some internal types. Here, `Bar` might be some public, generic type and `T1` and `T2` are some internal
Users of our module shouldn't know that we implement `Foo` by using a `Bar`, but what we're types. Users of our module shouldn't know that we implement `Foo` by using a `Bar`,
really hiding here is the types `T1` and `T2`, and how they are used with `Bar`. but what we're really hiding here is the types `T1` and `T2`, and how they are used
with `Bar`.
## See also ## See also
- [Advanced Types in the book](https://doc.rust-lang.org/book/ch19-04-advanced-types.html?highlight=newtype#using-the-newtype-pattern-for-type-safety-and-abstraction) - [Advanced Types in the book](https://doc.rust-lang.org/book/ch19-04-advanced-types.html?highlight=newtype#using-the-newtype-pattern-for-type-safety-and-abstraction)
- [Newtypes in Haskell](https://wiki.haskell.org/Newtype) - [Newtypes in Haskell](https://wiki.haskell.org/Newtype)
- [Type aliases](https://doc.rust-lang.org/stable/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases) - [Type aliases](https://doc.rust-lang.org/stable/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases)
- [derive_more](https://crates.io/crates/derive_more), a crate for deriving many builtin traits on newtypes. - [derive_more](https://crates.io/crates/derive_more), a crate for deriving many
builtin traits on newtypes.
- [The Newtype Pattern In Rust](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html) - [The Newtype Pattern In Rust](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html)

@ -4,33 +4,42 @@
Prefer small crates that do one thing well. 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++. Cargo and crates.io make it easy to add third-party libraries, much more so than
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. 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. We should take advantage of this tooling, and use smaller, more fine-grained dependencies.
## Advantages ## Advantages
* Small crates are easier to understand, and encourage more modular code. * Small crates are easier to understand, and encourage more modular code.
* Crates allow for re-using code between projects. * 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. For example, the `url` crate was developed as part of the Servo browser engine,
* 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. 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 ## Disadvantages
* This can lead to "dependency hell", when a project depends on multiple conflicting versions of a crate at the same time. * This can lead to "dependency hell", when a project depends on multiple conflicting
For example, the `url` crate has both versions 1.0 and 0.5. versions of a crate at the same time. For example, the `url` crate has both versions
Since the `Url` from `url:1.0` and the `Url` from `url:0.5` are different types, 1.0 and 0.5. Since the `Url` from `url:1.0` and the `Url` from `url:0.5` are
an HTTP client that uses `url:0.5` would not accept `Url` values from a web scraper that uses `url:1.0`. different types, an HTTP client that uses `url:0.5` would not accept `Url` values
* Packages on crates.io are not curated. A crate may be poorly written, have unhelpful documentation, or be outright malicious. from a web scraper that uses `url:1.0`.
* Two small crates may be less optimized than one large one, since the compiler does not perform link-time optimization (LTO) by default. * 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
does not perform link-time optimization (LTO) by default.
## Examples ## Examples
The [`ref_slice`](https://crates.io/crates/ref_slice) crate provides functions for converting `&T` to `&[T]`. The [`ref_slice`](https://crates.io/crates/ref_slice) crate provides functions
for converting `&T` to `&[T]`.
The [`url`](https://crates.io/crates/url) crate provides tools for working with URLs. The [`url`](https://crates.io/crates/url) crate provides tools for working with
URLs.
The [`num_cpus`](https://crates.io/crates/num_cpus) crate provides a function to query the number of CPUs on a machine. The [`num_cpus`](https://crates.io/crates/num_cpus) crate provides a function to
query the number of CPUs on a machine.
## See also ## See also

@ -6,13 +6,14 @@ The [Strategy design pattern](https://en.wikipedia.org/wiki/Strategy_pattern)
is a technique that enables separation of concerns. 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). 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, The basic idea behind the Strategy pattern is that, given an algorithm solving
we define only the skeleton of the algorithm at an abstract level and a particular problem, we define only the skeleton of the algorithm at an abstract
we separate the specific algorithms implementation into different parts. level and we separate the specific algorithms 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 this way, a client using the algorithm may choose a specific implementation,
In other words, the abstract specification of the class does not depend on the specific implementation of the derived class, while the general algorithm workflow remains the same. In other words, the abstract
but specific implementation must adhere to the abstract specification. 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". This is why we call it "Dependency Inversion".
## Motivation ## Motivation
@ -20,15 +21,15 @@ This is why we call it "Dependency Inversion".
Imagine we are working on a project that generates reports every month. 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., We need the reports to be generated in different formats (strategies), e.g.,
in `JSON` or `Plain Text` formats. 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. But things vary over time and we don't know what kind of requirement we may get
For example, we may need to generate our report in a completly new format, in the future. For example, we may need to generate our report in a completly new
or just modify one of the existing formats. format, or just modify one of the existing formats.
## Example ## Example
In this example our invariants (or abstractions) are `Context`, `Formatter`, and `Report`, In this example our invariants (or abstractions) are `Context`, `Formatter`,
while `Text` and `Json` are our strategy structs. and `Report`, while `Text` and `Json` are our strategy structs. These strategies
These strategies have to implement the `Formatter` trait. have to implement the `Formatter` trait.
```rust ```rust
use std::collections::HashMap; use std::collections::HashMap;
@ -93,18 +94,17 @@ fn main() {
## Advantages ## Advantages
The main advantage is separation of concerns. For example, in this case `Report` does not know anything about specific The main advantage is separation of concerns. For example, in this case `Report`
implementations of `Json` and `Text`, whereas the output implementations does not care about how data is does not know anything about specific implementations of `Json` and `Text`,
preprocessed, stored, and fetched. whereas the output implementations does not care about how data is preprocessed,
The only thing they have to know is context and a specific trait and method to implement, stored, and fetched. The only thing they have to know is context and a specific
i.e,`Formatter` and `run`. trait and method to implement, i.e,`Formatter` and `run`.
## Disadvantages ## Disadvantages
For each strategy there must be implemented at least one module, so number of modules For each strategy there must be implemented at least one module, so number of modules
increases with number of strategies. increases with number of strategies. If there are many strategies to choose from
If there are many strategies to choose from then users have to know how strategies differ then users have to know how strategies differ from one another.
from one another.
## Discussion ## Discussion
@ -116,10 +116,12 @@ Ways of providing different strategies includes:
- Use compiler feature flags, E.g. `json` feature, `text` feature - Use compiler feature flags, E.g. `json` feature, `text` feature
- Separated as crates, E.g. `json` crate, `text` crate - 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) Serde crate is a good example of the `Strategy` pattern in action. Serde allows
of the serialization behavior by manually implementing `Serialize` and `Deserialize` traits for our type. [full customization](https://serde.rs/custom-serialization.html) of the serialization
For example, we could easily swap `serde_json` with `serde_cbor` since they expose similar methods. behavior by manually implementing `Serialize` and `Deserialize` traits for our
Having this makes the helper crate `serde_transcode` much more useful and ergonomic. 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. However, we don't need to use traits in order to design this pattern in Rust.

@ -2,15 +2,17 @@
## Description ## Description
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. If you have `unsafe` code, create the smallest possible module that can uphold
Embed this into a larger module that contains only safe code and presents an ergonomic interface. the needed invariants to build a minimal safe interface upon the unsafety. Embed
Note that the outer module can contain unsafe functions and methods that call directly into the unsafe code. this into a larger module that contains only safe code and presents an ergonomi
Users may use this to gain speed benefits. 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.
## Advantages ## Advantages
* This restricts the unsafe code that must be audited * This restricts the unsafe code that must be audited
* Writing the outer module is much easier, since you can count on the guarantees of the inner module * Writing the outer module is much easier, since you can count on the guarantees
of the inner module
## Disadvantages ## Disadvantages
@ -19,10 +21,12 @@ Users may use this to gain speed benefits.
## Examples ## 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
* `std`s `String` class is a wrapper over `Vec<u8>` with the added invariant that the contents must be valid UTF-8. in submodules, presenting a safe interface to users. * `std`s `String` class
The operations on `String` ensure this behavior. is a wrapper over `Vec<u8>` with the added invariant that the contents must be
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. 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.
## See also ## See also

@ -1,15 +1,17 @@
# Refactoring # Refactoring
Refactoring is very important in relation to these topics. 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. 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 can use [design patterns](../patterns/index.md) to [DRY] up code and generalize
We must avoid [anti-patterns](../anti_patterns/index.md) while we do this. abstractions. We must avoid [anti-patterns](../anti_patterns/index.md) while we
While they may be tempting to employ, their costs outweigh their benefits. do this. While they may be tempting to employ, their costs outweigh their benefits.
> Shortcuts make for long days. > Shortcuts make for long days.
We can also use [idioms](../idioms/index.md) to structure our code in a way that is understandable. We can also use [idioms](../idioms/index.md) to structure our code in a way that
is understandable.
## Tests ## Tests

Loading…
Cancel
Save