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:
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).
- 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.
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.
`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
@ -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
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:
/* 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)
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)
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.