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

Commit

Permalink
work
Browse files Browse the repository at this point in the history
  • Loading branch information
nikomatsakis committed Sep 7, 2021
1 parent be749f3 commit 51685ca
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 33 deletions.
14 changes: 7 additions & 7 deletions src/how_rust_empowers.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# Rust empowers by being...

Rust's [overall goal](./goals.md) is to "empower everyone to build reliable and efficient software". But how do we do that? This section identifies the various things that Rust tries to be in order to empower its users.
Rust's [overall goal](./goals.md) is to "empower everyone to build reliable and efficient software". But how do we do that? This section identifies the various things that Rust tries to be in order to empower its users. If you click on a particular entry, you will find more notes, along with a list of mechanisms that we use (for example, reliability is enhanced by type safety).

**Note that these goals are often in tension.** For any given design, we always want to achieve (or at least be neutral) all of these feelings. In some cases, though, we may be forced to decide between slightly penalizing one goal or another. In that case, we tend to support those goals that come earlier in the list over those that come later (but every case is different). See the [case studies](./case_studies.md) for examples.

## [⚙️ Reliable](./how_rust_feels/reliable.md): "if it compiles, it works"
## [⚙️ Reliable](./how_rust_empowers/reliable.md): "if it compiles, it works"

> One of the great things about Rust is the feeling of "it if compiles, it works". This is what allows you to do a giant refactoring and find that the code runs on the first try. It is what lets you deploy code that uses parallelism or other fancy features without exhausting fuzz testing and worry about every possible corner case.
## [🐎 Performant](./how_rust_feels/performant.md): "idiomatic code runs efficiently"
## [🐎 Performant](./how_rust_empowers/performant.md): "idiomatic code runs efficiently"

> In Rust, the fastest code is often the most high-level: convenient features like closures, iterators, or async-await map down to code that is at once efficient and which uses minimal memory.
## [🧩 Productive](./how_rust_feels/productive.md): "a little effort does a lot of work"
## [🧩 Productive](./how_rust_empowers/productive.md): "a little effort does a lot of work"

> Rust and its ecosystem offer a wide array of powerful building blocks that can be combined and recombined. The result is that standing up a quality system can be done in record time.
## [🥰 Supportive](./how_rust_feels/supportive.md): "the language, tools, and community are here to help"
## [🥰 Supportive](./how_rust_empowers/supportive.md): "the language, tools, and community are here to help"

> We strive to make our tools polished and friendly, and we look for every opportunity to guide people towards success. Part of that is building a community that eagerly shares its knowledge in a welcoming and inclusive way.
## [🔧 Transparent](./how_rust_feels/transparent.md): "predict and control low-level details"
## [🔧 Transparent](./how_rust_empowers/transparent.md): "predict and control low-level details"

> The translation from Rust to underlying machine code is straightforward and predictable. If needed, you have options to control the low-level details of how your application works.
## [🤸 Versatile](./how_rust_feels/versatile.md): "you can do anything with Rust"
## [🤸 Versatile](./how_rust_empowers/versatile.md): "you can do anything with Rust"

> Rust scales well both up and down: it is well-suited to building everything from simple scripts to web servers to WebAssembly programs to kernel extensions and embedded systems. It is usable on both common and esoteric platforms.
27 changes: 22 additions & 5 deletions src/how_rust_empowers/performant.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
# 🏇 Performant: "ran well right out of the box"
# 🏇 Performant: "idiomatic code runs efficiently"

> > In Rust, the fastest code is often the most high-level: convenient features like closures, iterators, or async-await map down to code that is at once efficient and which uses minimal memory.
> In Rust, the fastest code is often the most high-level: convenient features like closures, iterators, or async-await map down to code that is at once efficient and which uses minimal memory.
## In tension with

Making Rust feel performant can be in tension with Rust feeling [supportive], and in some cases [productive]. The problem is that making abstractions efficient may mean that they aren't able to provide some of the niceties that users are accustomed to from other languages. Less obvious is that feeling performant can be in tension with feeling [transparent] or [versatile]; this is because many optimizations rely on a lack of transparency about the precise order or way in which things happen, and having more visibility, or more choices, can make those optimizations harder to do.

[supportive]: ./supportive.md
[productive]: ./productive.md
[versatile]: ./versatile.md
[transparent]: ./transparent.md

## Examples

### Iterators

Rust iterators are a good example of something which meets our goal of being *performant*. Code that uses iterators not only feels higher-level, it often compiles down to loops which are more efficient than if you wrote the loops with explicit iterators. This is because iterators are able to skip bounds checks on vectors.
Rust iterators are a good example of something which meets our goal of being *performant*. Code that uses iterators not only feels higher-level, it often compiles down to loops which are more efficient than if you wrote the loops with explicit iterators. This is because iterators are able to skip bounds checks on vectors or take other shortcuts.

## Mechanisms

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

### Zero-cost abstractions

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.

### Async-await
### Avoid overspecifying machine details

The goal with async-await syntax is to make a convenient, accessible way to write code which compiles down to the same sort of state machines that C programmers have been writing by hand to create event-driven architectures.
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`.)
3 changes: 0 additions & 3 deletions src/how_rust_empowers/polished.md

This file was deleted.

22 changes: 17 additions & 5 deletions src/how_rust_empowers/productive.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
# 🧩 Productive
# 🧩 Productive: "a little effort does a lot of work"

> Rust and its ecosystem offer a wide array of powerful building blocks that can be combined and recombined. The result is that standing up quality code can be done in record time.
## Examples
## In tension with

### Cargo and crates.io
Making Rust feel productive can be in tension with Rust feeling [reliable] and, in some cases, [performant].

[reliable]: ./reliable.md
[performant]: ./performant.md

## Mechanisms

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

### Enable a flourishing ecosystem

### Stability; editions

Rust has a [commitment to stability](https://blog.rust-lang.org/2014/10/30/Stability.html) across versions. This is because stability is a key enabler of productivity: without stability across versions, you are forced to spend time resolving build failures instead of building the functionality you want to build.

Stability however can work against any of the various feelings that we strive for by giving us less "degrees of freedom" in our designs. The goal of Rust's editions system is too help finesse that tradeoff. Rust Editions permit us to make what would otherwise be breaking changes; because using the new edition is opt-in, and editions are interoperable, people can choose to adopt the newer behavior on their own schedule, thus avoiding the hit to productivity.

### rustfmt

### Stability without stagnation and editions

One of Rust's [core deliverables](https://blog.rust-lang.org/2014/10/30/Stability.html)

### Portability

Expand Down
34 changes: 26 additions & 8 deletions src/how_rust_empowers/reliable.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,39 @@

> One of the great things about Rust is the feeling of "it if compiles, it works". This is what allows you to do a giant refactoring and find that the code runs on the first try. It is what lets you deploy code that uses parallelism or other fancy features without exhausting fuzz testing and worry about every possible corner case.
## Examples
[productive]: ./productive.md
[productivity]: ./productive.md
[supportive]: ./supportive.md

### Type safety, but with an unsafe escape hatch
## In tension with

All Rust code follows a strict safety guarantee: no undefined behavior without using unsafe code. This provision aims to strike a balance. Rust's type system is ultimately fairly simple and limited, but the "unsafe" escape hatch allows us to build all manner of safe abstractions.
Making Rust reliable is most often at odds with making Rust feel [productive] and [supportive]. Reliability is often achieved by "cross checking", where various parts of the system check one another to ensure overall consistency. Cross checks can make it harder to make changes, since the various parts of the system must be brought in sync with one another.

**Too little ("almost safe" APIs):** APIs that are safe so long as you don't do "unreasonable" things, such as an API that would be unsafe if a cycle between ref-counting objects was created. Plenty of people do unreasonable things in code, 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.
## Mechanisms

**Too much (static bounds checking):** Requiring the user to statically prove for every `vec[i]` expression that `i` is in bounds; or to show that `map[key]` is in bounds. This requires another level of "smarts" from the compiler and complexity from the type system. *However,* this form of safety is definitely appropriate for some users and some domains, and we may wish to extend Rust with tooling that allows you to "opt-in", but offering it by default would be "too much".
What are some of the ways that we make Rust feel **reliable**?

In general, moving safety checks to runtime can be a useful way to strike a balance between safety and complexity.
### Type safety (but with an unsafe escape hatch)

### Exhausting matching
*Makes Rust code feel **reliable** but can work against Rust feeling [productive] and [supportive].*

Rust code requires exhaustive matching, with explicit `_` cases used to handle additional items. This is different from many languages, such as Scala or Ocaml, which make it a runtime panic to elide cases. This encorages programmers to consider all the cases, or to be explicit when they consider a case impossible, or when they are choosing not to handle a case for the time being.
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.

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

Expand Down
20 changes: 19 additions & 1 deletion src/how_rust_empowers/supportive.md
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
# 🥰 Supportive
# 🥰 Supportive: "the language, tools, and community are here to help"

> We strive to make our tools polished and friendly, and we look for every opportunity to guide people towards success. Part of that is building a community that eagerly shares its knowledge in a welcoming and inclusive way.
## In tension with

Making Rust feel supportive can be in tension with Rust feeling [reliable]. It may also be in tension with Rust feeling [versatile]: often the most supportive path is to offer a simple, streamlined set of choices, but that may be too limiting.

[reliable]: ./reliable.md
[versatile]: ./versatile.md

## Mechanisms

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

### Polished developer experience

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).

18 changes: 16 additions & 2 deletions src/how_rust_empowers/transparent.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
# 🔧 Transparent
# 🔧 Transparent: "predict and control low-level details"

> The translation from Rust to underlying machine code is predictable. If needed, you have options to control the low-level details of how your application works.
> The translation from Rust to underlying machine code is straightforward and predictable. If needed, you have options to control the low-level details of how your application works.
## In tension with

Feeling transparent can be in tension with [supportive] or [productive], because transparency often requires exposing details.

[supportive]: ./supportive.md
[productive]: ./productive.md

## Mechanisms

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

### No global costs

Rust strives to avoid features that impose global costs (that is, impose a runtime cost even on projects that don't use them). This is a key part of Stroustroup's definition of [zero-cost abstractions](./transparent.md#zero-cost-abstractions), "What you don't use, you don't pay for". This is the mechanism that encourages us to use ownership instead of a garbage collector, for example, since a garbage collector
21 changes: 19 additions & 2 deletions src/how_rust_empowers/versatile.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# 🤸 Versatile
# 🤸 Versatile: "you can do anything with Rust"

> Rust can be used to build everything from simple scripts to web servers to WebAssembly programs to kernel extensions and embedded systems. Rust gives you the ability to control low-level details and exposes the capabilities of the underlying system.
> Rust scales well both up and down: it is well-suited to building everything from simple scripts to web servers to WebAssembly programs to kernel extensions and embedded systems. It is usable on both common and esoteric platforms.
## In tension with...

Feeling versatile can be in tension with feeling [supportive] and [productive].

[supportive]: ./supportive.md
[productive]: ./productive.md

## Mechanisms

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

### Expose all system capabilities

We aim to expose all the core system capabilities to Rust programs in some way, even if accessing them or using them correctly can be difficult. We don't want Rust users to feel they have to "drop down" to C or some other language; they should be able to use unsafe Rust to get their job done. Features like "inline assembly" are following in this vein.

An example of this mechanism in action is `repr(C)`. Although our default struct layout is not defined ()

0 comments on commit 51685ca

Please sign in to comment.