Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
Merge pull request #6 from nikomatsakis/separate-pages-for-mechanisms
Browse files Browse the repository at this point in the history
Separate pages for mechanism
  • Loading branch information
nikomatsakis authored Sep 8, 2021
2 parents 455e228 + 197e66e commit f6827a1
Show file tree
Hide file tree
Showing 15 changed files with 78 additions and 34 deletions.
5 changes: 5 additions & 0 deletions book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ language = "en"
multilingual = false
src = "src"
title = "Rustacean Principles"


[output.html.fold]
enable = true
level = 1
14 changes: 13 additions & 1 deletion src/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,19 @@ No -- right now, this is a work-in-progress being developed by [nikomatsakis], [

## Are these principles complete?

No! They are still in draft form. We are still iterating on them and are interested in hearing from people who are active in the Rust community about whether they reflect what you love about Rust (and, if not, what might be changed). Another great idea is to try and use these principles to [guide design discussions and questions of team membership](https://rustacean-principles.netlify.app/#how-can-the-principles-be-used) to evaluate how useful they are.
No! They are still in draft form. We are still iterating on them and are interested in hearing from people who are active in the Rust community about whether they reflect what you love about Rust (and, if not, what might be changed). That said, some aspects have gone through more iteration than others. Here is a list of some aspects of site and how well understood they are:

| Aspect | Confidence |
| ------------------------------------------------------ | ------------------------------------------- |
| The qualities listed in [How Rust empowers] | High: we've iterated quite a bit on these |
| Ordering of the qualities | Medium: might still change this a bit |
| Mechanisms (e.g., [type safety]) | Low: these have not seen a lot of iteration |
| The qualities listed in [How to Rustacean] | Medium: expect to tweak these |
| Examples in the qualities listed in [How to Rustacean] | Low: most of these aren't even written yet! |

[How Rust empowers]: ./how_rust_empowers.md
[How to Rustacean]: ./how_to_rustacean.md
[type safety]: ./how_rust_empowers/reliable/type_safety.md

## Why develop these principles?

Expand Down
9 changes: 9 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@
- [What is Rust?](./what_is_rust.md)
- [Rust empowers by being...](./how_rust_empowers.md)
- [⚙️ Reliable](./how_rust_empowers/reliable.md)
- [Type safety](./how_rust_empowers/reliable/type_safety.md)
- [Consider all cases](./how_rust_empowers/reliable/consider_all_cases.md)
- [🐎 Performant](./how_rust_empowers/performant.md)
- [Zero-cost abstractions](./how_rust_empowers/performant/zca.md)
- [Specify only what's necessary](./how_rust_empowers/performant/specify_only_what_is_necessary.md)
- [🥰 Supportive](./how_rust_empowers/supportive.md)
- [Polished developer experience](./how_rust_empowers/supportive/polished.md)
- [🧩 Productive](./how_rust_empowers/productive.md)
- [Ecosystem](./how_rust_empowers/productive/ecosystem.md)
- [Stability](./how_rust_empowers/productive/stability.md)
- [🔧 Transparent](./how_rust_empowers/transparent.md)
- [No global costs](./how_rust_empowers/transparent/no_global_costs.md)
- [🤸 Versatile](./how_rust_empowers/versatile.md)
- [Expose all system capabilities](./how_rust_empowers/productive/expose_capabilities.md)
- [How to Rustacean](./how_to_rustacean.md)
- [💖 Be kind and considerate](./how_to_rustacean/be_kind.md)
- [✨ Bring joy to the user](./how_to_rustacean/bring_joy.md)
Expand Down
8 changes: 5 additions & 3 deletions src/how_rust_empowers/performant.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ Rust iterators are a good example of something which meets our goal of being *pe

What are some of the ways that we make Rust feel **performant**?

### Zero-cost abstractions
### [Zero-cost abstractions](./performant/zca.md)

Rust borrowed the idea of zero-cost abstractions from the C++ community. Bjarne Stroustroup defined zero-cost abstractions as, "What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better." We design our abstractions with these goals in mind as well.
> "What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better." -- Bjarne Stroustroup
### Avoid overspecifying machine details
### [Specify only what's necessary](./specify_only_what_is_necessary.md)

> Specify the details that are needed to capture the programmer's intent, but not more.
Many details of Rust are left deliberately unspecified. For example, unless users manually add a `repr` attribute, Rust structs and enums can be laid out in memory however the compiler sees fit. This allows us make optimizations like reordering struct fields to eliminate padding and reduce size, or representing `Option<&T>` as a single, nullable pointer. (Of course, reserving this freedom works against [transparency] and [versatility], which is why we have the option to specify the `repr`.)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Specify only what's necessary
5 changes: 5 additions & 0 deletions src/how_rust_empowers/performant/zca.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Specify only what's necessary

Rust borrowed the idea of zero-cost abstractions from the C++ community. Bjarne Stroustroup defined zero-cost abstractions as, "What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better." We design our abstractions with these goals in mind as well.

TODO elaborate
1 change: 1 addition & 0 deletions src/how_rust_empowers/productive/ecosystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Upgrades are easy and painless
1 change: 1 addition & 0 deletions src/how_rust_empowers/productive/expose_capabilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Expose all system capabilities
1 change: 1 addition & 0 deletions src/how_rust_empowers/productive/stability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Stability
35 changes: 6 additions & 29 deletions src/how_rust_empowers/reliable.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,14 @@ Reliability can also be at odds with [versatility]. Our ability to make somethin

What are some of the ways that we make Rust feel **reliable**?

### Type safety (but with an unsafe escape hatch)
### [Type safety](./reliable/type_safety.md)

*Makes Rust code feel **reliable** but can work against Rust feeling [productive] and [supportive].*
> Safe Rust code is guaranteed to avoid undefined behavior.
Type safety is a key element to reliability. We ensure that safe Rust code is free of "undefined behavior", which is the term that compiler authors use to refer to things like segfaults, data races, and out-of-bounds memory accesses.
Rust programmers never have to worry about notorious, hard-to-diagnose, harder-to-fix bugs like segfaults and data races. Rust's exposure to security vulnerabilities is much reduced as a result. However, static safety comes at the cost of increased overall complexity (works against [supportive]). Figuring out the correct type annotations and other details can be difficult, working against [productivity].

Type safety is not a suggestion: we don't accept **almost safe** APIs, which are safe so long as you don't do "unreasonable" things. For example, we would not accept a safe API that avoids undefined behavior so long as no cycles exist between ref-counted objects. Reasonable people do "unreasonable" things in code all the time, either because they don't yet know what is reasonable or not, or because the complexity of the system gets away from them. The role of the compiler is to manage that for you.
### [Consider all cases](./reliable/consider_all_cases.md)

However, type safety carries a cost, too. It adds complexity, which can work against Rust feeling [productive]. It also makes Rust more complex and hard to learn, which can work against feeling [supportive] -- we work hard on our error messages and documentation precisely to help mitigate this cost. Precisely because of these costs, we have imposed some limits on what Rust's type system tries to achieve:

* We employ **runtime checks** for certain kinds of error conditions. For example, we do not try to prove that indexes are within bounds, but instead check an expression like `vec[i]` to ensure that `i < vec.len()`. Proving conditions about the specific value of a variable (in this case, `i` or the length of a vector) would be a "big step up" in complexity of the type system. Rust's choice not to do so decreases that feeling of reliability, but with massive benefits to [productivity].
* We permit **unsafe code**, which gives authors an escape hatch from the type system. This means that we can support many kinds of systems programming patterns while keeping the type system itself relatively simple. (For example, the system of ownership and borrowing cannot express doubly linked lists, but they can be implemented with unsafe code.)
* However, we do expect authors to **encapsulate** their unsafe code, presenting a safe interface to the world at large. This works against the feeling of [productivity] for unsafe code authors (it's more complex to think about how to encapsulate things) but with great benefits for reliability for the rest of the world.

### Consider all cases

Rust generally aims to make sure that you 'acknowledge' or consider all cases. You can see this in `match` statements, which require exhaustive matching (many other languages permit you to leave off match arms that you believe cannot occur); when matching, we also require that users list all fields in a struct, or acknowledge (with `..`) that they have not done so. Forcing users to consider all cases helps make Rust feel reliable, but it can come at the cost of feeling [productive]. This is why we frequently pay careful attention to the details of

A good case study for the tradeoffs involved is error handling. The traditional approach to error handling for a long time was exceptions. Exceptions are great for feeling [productive], as they allow errors to quietly pass through any amount of code. However, the result is a proliferation of hidden control-flow paths that are very hard to reason about. In practice, recovering from exceptions is fraught with error, and hence they work against the feeling of reliability. The problem is that typical mechanisms such as returning an error code are also not reliable; it's too easy to forget to check for an error.

Rust instead adopts the approach pioneered in functional languages of returning an enum. The `Result` enum allows one to signal an error in a way that *forces* the error to be considered. Still, the most common way to handle an error is to propagate it to your caller, and in order to feel productive, it's important that this operation be *concise*. Rust's `?` operator was added to restore productivity while ensuring that error paths are still visible to users and are not completely overlooked.

### Cargo's dependency system

Cargo's dependency system is geared towards reproducible builds. Once an application builds, the `Cargo.lock` file ensures that you don't get surprised by new versions of dependencies that you didn't request.

### Explicit error handling with `?`

Rust eschews exceptions in their traditional form, instead preferring to leverage `Result` types. The `?` operator is at once concise and visible, making it possible to see where errors occur without obscuring the "happy path" where the code is successful. The `?` operator also enables "error type adaptation", which is an example of [polish](./polish.md).

**Too little:** Exceptions that invisibly propagate across stack frames. Experience has shown that programmers have a hard time ensuring that the state of their program is consistent after exceptions are thrown (this is why Rust panics are reserved for irrecoverable error conditions).

**Too much:** Requiring a lot of ceremony on each error could make programs more reliable, but would work against [empowering](./empowering.md) or [polished](./polished.md).
> Rust doesn't hide error conditions and encourages listing all possibilities explicitly (or acknowledging that something is elided).
Rust uses a number of mechanisms to encourage code authors to list all possibilities. This thoroughness frequently helps identify bugs because it can highlight things that the programmer has forgotten about. However, it comes at a price in [productivity], since it can force the programmer to deal with details that they haven't figure out yet. As an example, consider exhaustive matches: they are frequently very useful, but when a new enum variant is added, all matches must be edited to account for it (or else they must include a `_` case).
9 changes: 9 additions & 0 deletions src/how_rust_empowers/reliable/consider_all_cases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Consider all cases

> Rust doesn't hide error conditions and encourages listing all possibilities explicitly (or acknowledging that something is elided).
Rust generally aims to make sure that you 'acknowledge' or consider all cases. You can see this in `match` statements, which require exhaustive matching (many other languages permit you to leave off match arms that you believe cannot occur); when matching, we also require that users list all fields in a struct, or acknowledge (with `..`) that they have not done so. Forcing users to consider all cases helps make Rust feel reliable, but it can come at the cost of feeling [productive]. This is why we frequently pay careful attention to the details of

A good case study for the tradeoffs involved is error handling. The traditional approach to error handling for a long time was exceptions. Exceptions are great for feeling [productive], as they allow errors to quietly pass through any amount of code. However, the result is a proliferation of hidden control-flow paths that are very hard to reason about. In practice, recovering from exceptions is fraught with error, and hence they work against the feeling of reliability. The problem is that typical mechanisms such as returning an error code are also not reliable; it's too easy to forget to check for an error.

Rust instead adopts the approach pioneered in functional languages of returning an enum. The `Result` enum allows one to signal an error in a way that *forces* the error to be considered. Still, the most common way to handle an error is to propagate it to your caller, and in order to feel productive, it's important that this operation be *concise*. Rust's `?` operator was added to restore productivity while ensuring that error paths are still visible to users and are not completely overlooked.
13 changes: 13 additions & 0 deletions src/how_rust_empowers/reliable/type_safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Type safety (but with an unsafe escape hatch)

> Safe Rust code is guaranteed to avoid undefined behavior.
Type safety is a key element to reliability. We ensure that safe Rust code is free of "undefined behavior", which is the term that compiler authors use to refer to things like segfaults, data races, and out-of-bounds memory accesses.

Type safety is not a suggestion: we don't accept **almost safe** APIs, which are safe so long as you don't do "unreasonable" things. For example, we would not accept a safe API that avoids undefined behavior so long as no cycles exist between ref-counted objects. Reasonable people do "unreasonable" things in code all the time, either because they don't yet know what is reasonable or not, or because the complexity of the system gets away from them. The role of the compiler is to manage that for you.

However, type safety carries a cost, too. It adds complexity, which can work against Rust feeling [productive]. It also makes Rust more complex and hard to learn, which can work against feeling [supportive] -- we work hard on our error messages and documentation precisely to help mitigate this cost. Precisely because of these costs, we have imposed some limits on what Rust's type system tries to achieve:

* We employ **runtime checks** for certain kinds of error conditions. For example, we do not try to prove that indexes are within bounds, but instead check an expression like `vec[i]` to ensure that `i < vec.len()`. Proving conditions about the specific value of a variable (in this case, `i` or the length of a vector) would be a "big step up" in complexity of the type system. Rust's choice not to do so decreases that feeling of reliability, but with massive benefits to [productivity].
* We permit **unsafe code**, which gives authors an escape hatch from the type system. This means that we can support many kinds of systems programming patterns while keeping the type system itself relatively simple. (For example, the system of ownership and borrowing cannot express doubly linked lists, but they can be implemented with unsafe code.)
* However, we do expect authors to **encapsulate** their unsafe code, presenting a safe interface to the world at large. This works against the feeling of [productivity] for unsafe code authors (it's more complex to think about how to encapsulate things) but with great benefits for reliability for the rest of the world.
4 changes: 3 additions & 1 deletion src/how_rust_empowers/supportive.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ Making Rust feel supportive can be in tension with Rust feeling [reliable]. It m

What are some of the ways that we make Rust feel **supportive**?

### Polished developer experience
### [Polished developer experience](./supportive/polished.md)

> The details count.
Rust tools strive to provide a polished, smooth experience for our developers. One example is how the compiler offers quality error messages that attempt to not only indicate an error, but to teach the user how the language works, and offer helpful suggestions for how to fix their code. For tools like cargo, this manifests in careful CLI design that makes the "easy things easy" (but of course, for Rust to feel [versatile], we have to go beyond that).

5 changes: 5 additions & 0 deletions src/how_rust_empowers/supportive/polished.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Polished developer experience

> The details count.
Rust tools strive to provide a polished, smooth experience for our developers. One example is how the compiler offers quality error messages that attempt to not only indicate an error, but to teach the user how the language works, and offer helpful suggestions for how to fix their code. For tools like cargo, this manifests in careful CLI design that makes the "easy things easy" (but of course, for Rust to feel [versatile], we have to go beyond that).
1 change: 1 addition & 0 deletions src/how_rust_empowers/transparent/no_global_costs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# No global costs

0 comments on commit f6827a1

Please sign in to comment.