Skip to content

Commit

Permalink
Merge branch 'master' into catch_panic_anti_pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
simonsan committed Jan 6, 2021
2 parents 8871ccb + a0e7651 commit b61bee8
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 74 deletions.
7 changes: 1 addition & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,10 @@ jobs:

- run: mdbook test

lint:
markdown-lint:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Lint all files in current directory
uses: avto-dev/markdown-lint@v1
with:
config: '.markdownlint.yaml'
args: '*.md'
- name: Lint all files recursively
uses: avto-dev/markdown-lint@v1
with:
Expand Down
4 changes: 2 additions & 2 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
MD004: false
MD010:
code_blocks: false
MD013: false
MD013:
line_length: 300
39 changes: 30 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ and you think it may not be appropriate to file an issue open a discussion in ou

## Writing a new article

Before writing a new article please check our [issues](https://github.com/rust-unofficial/patterns/issues) and
the [Pull Requests](https://github.com/rust-unofficial/patterns/pulls) if there are existing issues or someone
is working on that topic.
Before writing a new article please check in one of the following resources if there is an existing discussion or if someone is already working on that topic:

- [Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116),
- [All issues](https://github.com/rust-unofficial/patterns/issues),
- [Pull Requests](https://github.com/rust-unofficial/patterns/pulls)

If you don't find an issue regarding your topic and you are sure it is not more feasible to open a thread in the [discussion board](https://github.com/rust-unofficial/patterns/discussions)
please open a new issue, so we can discuss about the ideas and future content of the article together and maybe
Expand All @@ -40,6 +42,30 @@ Don't forget to add your new article to the `SUMMARY.md` to let it be rendered t

Please make `Draft Pull requests` early so we can follow your progress and can give early feedback (see the following section).

## 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
code examples are correct.

### Markdown lint

To make sure the files comply with our Markdown style we use [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli).
To spare you some manual work to get through the CI test you can use the following commands to automatically fix most of the emerging problems when writing Markdown files.

- Install:

```sh
npm install -g markdownlint-cli
```

- Check all markdown files:
- unix: `markdownlint '**/*.md'`
- windows: `markdownlint **/*.md`

- Automatically fix basic errors:
- unix: `markdownlint -f '**/*.md'`
- windows: `markdownlint -f **/*.md`

## Creating a Pull Request

"Release early and often!" also applies to pull requests!
Expand All @@ -49,13 +75,8 @@ Early reviews of the community are not meant as an offense but to give feedback.

A good principle: "Work together, share ideas, teach others."

### Test the book locally before submitting

Before submitting the PR launch the commands `mdbook build` to make sure that the book builds and `mdbook test` to make sure that
code examples are correct.

### Important Note

Please **don't force push** your branch to keep commit history and make it easier of 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.
38 changes: 3 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,14 @@
An open source book about design patterns and idioms in the Rust programming
language that you can read [here](https://rust-unofficial.github.io/patterns/).

## TODOs

### Idioms

* TODO stability for extensibility
* TODO trait to separate visibility of methods from visibility of data (<https://github.com/sfackler/rust-postgres/blob/v0.9.6/src/lib.rs#L1400>)
* TODO leak amplification ("Vec::drain sets the Vec's len to 0 prematurely so that mem::forgetting Drain "only" mem::forgets more stuff. instead of exposing uninitialized memory or having to update the len on every iteration")
* TODO interior mutability - UnsafeCell, Cell, RefCell

### Design patterns

* TODO iterators (to safely avoid bounds checks)
* TODO closures and lifetimes (coupling to lifetime)
* TODO platform-specific sub-modules (<https://github.com/rust-lang/rfcs/blob/master/text/0517-io-os-reform.md#platform-specific-opt-in>)
* TODO Module organisation (by looking at examples such as Rusts `libstd`, and how it integrated into the Rusts source code, lessons can be learned about ergonomic project management and API design. Closely assosciated with platform-specific sub-modules)
* [Entry API](patterns/entry.md) (Currently just a boilerplate)
* TODO extension traits
* TODO destructor bombs (ensure linear typing dynamically, e.g., <https://github.com/Munksgaard/session-types/commit/0f25ccb7c3bc9f65fa8eaf538233e8fe344a189a>)
* TODO convertible to Foo trait for more generic generics (e.g., <http://static.rust-lang.org/doc/master/std/fs/struct.File.html#method.open>)
* [Late bound bounds](patterns/late-bounds.md) (Currently just a boilerplate)
* TODO 'shadow' borrowed version of struct - e.g., double buffering, Niko's parser generator
* TODO composition of structs to please the borrow checker
* TODO `Error` traits and `Result` forwarding
* TODO graphs

### Anti-patterns

* [catch_unwind for exceptions](anti_patterns/catch_panic.md)
* TODO Clone to satisfy the borrow checker
* TODO Matching all fields of a struct (back compat)
* TODO wildcard matches
* TODO taking an enum rather than having multiple functions
* TODO `unwrap()`ing every `Result` instead of forwarding it

## Contributing

You are missing content in this repository that can be helpful for others and you are eager to explain it?
Awesome! We are always happy about new contributions (e.g. elaboration or corrections on certain topics) to this project.

We suggest reading our [Contribution guide](./CONTRIBUTING.md) to get more information on how it works.
You can check the [Umbrella issue](https://github.com/rust-unofficial/patterns/issues/116) for all the patterns, anti-patterns, and idioms that could be added.

We suggest reading our [Contribution guide](./CONTRIBUTING.md) to get more information on how contributing to this repository works.

## Building with mdbook

Expand Down
2 changes: 2 additions & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@
- [Functional Programming](./functional/index.md)

- [Additional Resources](./additional_resources.md)

- [Design principles](./design-principles.md)
68 changes: 68 additions & 0 deletions design-principles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Design principles

## A brief overview over common design principles

---

## [SOLID](https://en.wikipedia.org/wiki/SOLID)

- [Single Responsibility Principle (SRP)](https://en.wikipedia.org/wiki/Single-responsibility_principle):
A class should only have a single responsibility, that is, only changes to one part of the software's
specification should be able to affect the specification of the class.
- [Open/Closed Principle (OCP)](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle):
"Software entities ... should be open for extension, but closed for modification."
- [Liscov 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
of that program."
- [Interface Segregation Principle (ISP)](https://en.wikipedia.org/wiki/Interface_segregation_principle):
"Many client-specific interfaces are better than one general-purpose interface."
- [Dependency Inversion Principle (DIP)](https://en.wikipedia.org/wiki/Dependency_inversion_principle):
One should "depend upon abstractions, [not] concretions."

## [DRY (Don’t 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"

## [KISS principle](https://en.wikipedia.org/wiki/KISS_principle)

most systems work best if they are kept simple rather than made complicated; therefore, simplicity should be a key goal in design, and unnecessary complexity should be avoided

## [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 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)

software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants

## [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming))

bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object's components. Encapsulation is used to hide the values or state of a structured data object inside a class, preventing unauthorized parties' direct access to them.

## [Command-Query-Separation(CQS)](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation)

“Functions should not produce abstract side effects...only commands (procedures) will be permitted to produce side effects.” - Bertrand Meyer: Object Oriented Software Construction

## [Principle of least astonishment (POLA)](https://en.wikipedia.org/wiki/Principle_of_least_astonishment)

a component of a system should behave in a way that most users will expect it to behave. The behavior should not astonish or surprise users

## Linguistic-Modular-Units

“Modules must correspond to syntactic units in the language used.” - Bertrand Meyer: Object Oriented Software Construction

## Self-Documentation

“The designer of a module should strive to make all information about the module part of the module itself.” - Bertrand Meyer: Object Oriented Software Construction

## Uniform-Access

“All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.” - Bertrand Meyer: Object Oriented Software Construction

## Single-Choice

“Whenever a software system must support a set of alternatives, one and only one module in the system should know their exhaustive list.” - Bertrand Meyer: Object Oriented Software Construction

## Persistence-Closure

“Whenever a storage mechanism stores an object, it must store with it the dependents of that object. Whenever a retrieval mechanism retrieves a previously stored object, it must also retrieve any dependent of that object that has not yet been retrieved.” - Bertrand Meyer: Object Oriented Software Construction
22 changes: 17 additions & 5 deletions functional/index.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
# Functional Usage of Rust

Rust is an imperative language, but it follows many functional programming paradigms. One of the biggest hurdles to understanding functional programs when coming from an imperative background is the shift in thinking. Imperative programs describe __how__ to do something, whereas declarative programs describe __what__ to do. Let's sum the numbers from 1 to 10 to show this.
Rust is an imperative language, but it follows many functional programming paradigms.
One of the biggest hurdles to understanding functional programs when coming from an imperative background is the shift in thinking.
Imperative programs describe __how__ to do something, whereas declarative programs describe __what__ to do.
Let's sum the numbers from 1 to 10 to show this.

## Imperative

```rust
let mut sum = 0;
for i in 1..11 {
sum += i;
sum += i;
}
println!("{}", sum);
```

With imperative programs, we have to play compiler to see what is happening. Here, we start with a `sum` of `0`. Next, we iterate through the range from 1 to 10. Each time through the loop, we add the corresponding value in the range. Then we print it out.
With imperative programs, we have to play compiler to see what is happening.
Here, we start with a `sum` of `0`.
Next, we iterate through the range from 1 to 10.
Each time through the loop, we add the corresponding value in the range.
Then we print it out.

| `i` | `sum` |
|:---:|:-----:|
Expand All @@ -35,9 +42,14 @@ This is how most of us start out programming. We learn that a program is a set o
println!("{}", (1..11).fold(0, |a, b| a + b));
```

Whoa! This is really different! What's going on here? Remember that with declarative programs we are describing __what__ to do, rather than __how__ to do it. `fold` is a function that [composes](https://en.wikipedia.org/wiki/Function_composition) functions. The name is a convention from Haskell.
Whoa! This is really different! What's going on here? Remember that with declarative programs we are describing __what__ to do, rather than __how__ to do it.
`fold` is a function that [composes](https://en.wikipedia.org/wiki/Function_composition) functions. The name is a convention from Haskell.

Here, we are composing functions of addition (this closure: `|a, b| a + b)`) with a range from 1 to 10. The `0` is the starting point, so `a` is `0` at first. `b` is the first element of the range, `1`. `0 + 1 = 1` is the result. So now we `fold` again, with `a = 1`, `b = 2` and so `1 + 2 = 3` is the next result. This process continues until we get to the last element in the range, `10`.
Here, we are composing functions of addition (this closure: `|a, b| a + b)`) with a range from 1 to 10.
The `0` is the starting point, so `a` is `0` at first.
`b` is the first element of the range, `1`. `0 + 1 = 1` is the result.
So now we `fold` again, with `a = 1`, `b = 2` and so `1 + 2 = 3` is the next result.
This process continues until we get to the last element in the range, `10`.

| `a` | `b` | result |
|:---:|:---:|:------:|
Expand Down
2 changes: 1 addition & 1 deletion idioms/pass-var-to-closure.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ let num3 = Rc::new(3);
let num2_cloned = num2.clone();
let num3_borrowed = num3.as_ref();
let closure = move || {
*num1 + *num2_cloned + *num3_borrowed;
*num1 + *num2_cloned + *num3_borrowed;
};
```

Expand Down
17 changes: 12 additions & 5 deletions idioms/priv-extend.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ fn main(s: a::S) {

## Discussion

Adding a field to a struct is a mostly backwards compatible change. However, if a client uses a pattern to deconstruct a struct instance, they might name all the fields in the struct and adding a new one would break that pattern. The client could name some of the fields and use `..` in the pattern, in which case adding another field is backwards compatible. Making at least one of the struct's fields private forces clients to use the latter form of patterns, ensuring that the struct is future-proof.

The downside of this approach is that you might need to add an otherwise unneeded field to the struct. You can use the `()` type so that there is no runtime overhead and prepend `_` to the field name to avoid the unused field warning.

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.
Adding a field to a struct is a mostly backwards compatible change.
However, if a client uses a pattern to deconstruct a struct instance, they might name all the fields in the struct and adding a new one would break that pattern.
The client could name some of the fields and use `..` in the pattern, in which case adding another field is backwards compatible.
Making at least one of the struct's fields private forces clients to use the latter form of patterns, ensuring that the struct is future-proof.

The downside of this approach is that you might need to add an otherwise unneeded field to the struct.
You can use the `()` type so that there is no runtime overhead and prepend `_` to the field name to avoid the unused field warning.

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.
6 changes: 3 additions & 3 deletions idioms/temporary-mutability.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ Using nested block:

```rust,ignore
let data = {
let mut data = get_vec();
data.sort();
data
let mut data = get_vec();
data.sort();
data
};
// Here `data` is immutable.
Expand Down
6 changes: 3 additions & 3 deletions patterns/newtype.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ the wrapper type.
Newtypes are very common in Rust code. Abstraction or representing units are the
most common uses, but they can be used for other reasons:

* restricting functionality (reduce the functions exposed or traits implemented),
* making a type with copy semantics have move semantics,
* abstraction by providing a more concrete type and thus hiding internal types, e.g.,
- restricting functionality (reduce the functions exposed or traits implemented),
- making a type with copy semantics have move semantics,
- abstraction by providing a more concrete type and thus hiding internal types, e.g.,

```rust,ignore
pub struct Foo(Bar<T1, T2>);
Expand Down
11 changes: 8 additions & 3 deletions patterns/small-crates.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@

Prefer small crates that do one thing well.

Cargo and crates.io make it easy to add third-party libraries, much more so than in say C or C++. Moreover, since packages on crates.io cannot be edited or removed after publication, any build that works now should continue to work in the future. We should take advantage of this tooling, and use smaller, more fine-grained dependencies.
Cargo and crates.io make it easy to add third-party libraries, much more so than in say C or C++.
Moreover, since packages on crates.io cannot be edited or removed after publication, any build that works now should continue to work in the future.
We should take advantage of this tooling, and use smaller, more fine-grained dependencies.

## Advantages

* Small crates are easier to understand, and encourage more modular code.
* Crates allow for re-using code between projects. For example, the `url` crate was developed as part of the Servo browser engine, but has since found wide use outside the project.
* Crates allow for re-using code between projects.
For example, the `url` crate was developed as part of the Servo browser engine, but has since found wide use outside the project.
* Since the compilation unit of Rust is the crate, splitting a project into multiple crates can allow more of the code to be built in parallel.

## Disadvantages

* This can lead to "dependency hell", when a project depends on multiple conflicting versions of a crate at the same time. For example, the `url` crate has both versions 1.0 and 0.5. Since the `Url` from `url:1.0` and the `Url` from `url:0.5` are different types, an HTTP client that uses `url:0.5` would not accept `Url` values from a web scraper that uses `url:1.0`.
* This can lead to "dependency hell", when a project depends on multiple conflicting versions of a crate at the same time.
For example, the `url` crate has both versions 1.0 and 0.5.
Since the `Url` from `url:1.0` and the `Url` from `url:0.5` are different types, an HTTP client that uses `url:0.5` would not accept `Url` values from a web scraper that uses `url:1.0`.
* Packages on crates.io are not curated. A crate may be poorly written, have unhelpful documentation, or be outright malicious.
* Two small crates may be less optimized than one large one, since the compiler does not perform link-time optimization (LTO) by default.

Expand Down

0 comments on commit b61bee8

Please sign in to comment.