Skip to content

Commit

Permalink
Auto merge of #27414 - Gankro:tarpl-fixes, r=alexcrichton
Browse files Browse the repository at this point in the history
This is *mostly* reducing *my* use of *italics* but there's some other misc changes interspersed as I went along.

This updates the italicizing alphabetically from `a` to `ra`.

r? @steveklabnik
  • Loading branch information
bors committed Jul 31, 2015
2 parents 7597262 + 554efc0 commit 0919f4a
Show file tree
Hide file tree
Showing 35 changed files with 237 additions and 229 deletions.
43 changes: 19 additions & 24 deletions src/doc/tarpl/README.md
Expand Up @@ -2,38 +2,33 @@

# NOTE: This is a draft document, and may contain serious errors

So you've played around with Rust a bit. You've written a few simple programs and
you think you grok the basics. Maybe you've even read through
*[The Rust Programming Language][trpl]*. Now you want to get neck-deep in all the
So you've played around with Rust a bit. You've written a few simple programs
and you think you grok the basics. Maybe you've even read through *[The Rust
Programming Language][trpl]* (TRPL). Now you want to get neck-deep in all the
nitty-gritty details of the language. You want to know those weird corner-cases.
You want to know what the heck `unsafe` really means, and how to properly use it.
This is the book for you.
You want to know what the heck `unsafe` really means, and how to properly use
it. This is the book for you.

To be clear, this book goes into *serious* detail. We're going to dig into
To be clear, this book goes into serious detail. We're going to dig into
exception-safety and pointer aliasing. We're going to talk about memory
models. We're even going to do some type-theory. This is stuff that you
absolutely *don't* need to know to write fast and safe Rust programs.
absolutely don't need to know to write fast and safe Rust programs.
You could probably close this book *right now* and still have a productive
and happy career in Rust.

However if you intend to write unsafe code -- or just *really* want to dig into
the guts of the language -- this book contains *invaluable* information.
However if you intend to write unsafe code -- or just really want to dig into
the guts of the language -- this book contains invaluable information.

Unlike *The Rust Programming Language* we *will* be assuming considerable prior
knowledge. In particular, you should be comfortable with:
Unlike TRPL we will be assuming considerable prior knowledge. In particular, you
should be comfortable with basic systems programming and basic Rust. If you
don't feel comfortable with these topics, you should consider [reading
TRPL][trpl], though we will not be assuming that you have. You can skip
straight to this book if you want; just know that we won't be explaining
everything from the ground up.

* Basic Systems Programming:
* Pointers
* [The stack and heap][]
* The memory hierarchy (caches)
* Threads

* [Basic Rust][]

Due to the nature of advanced Rust programming, we will be spending a lot of time
talking about *safety* and *guarantees*. In particular, a significant portion of
the book will be dedicated to correctly writing and understanding Unsafe Rust.
Due to the nature of advanced Rust programming, we will be spending a lot of
time talking about *safety* and *guarantees*. In particular, a significant
portion of the book will be dedicated to correctly writing and understanding
Unsafe Rust.

[trpl]: ../book/
[The stack and heap]: ../book/the-stack-and-the-heap.html
[Basic Rust]: ../book/syntax-and-semantics.html
2 changes: 1 addition & 1 deletion src/doc/tarpl/SUMMARY.md
Expand Up @@ -10,7 +10,7 @@
* [Ownership](ownership.md)
* [References](references.md)
* [Lifetimes](lifetimes.md)
* [Limits of lifetimes](lifetime-mismatch.md)
* [Limits of Lifetimes](lifetime-mismatch.md)
* [Lifetime Elision](lifetime-elision.md)
* [Unbounded Lifetimes](unbounded-lifetimes.md)
* [Higher-Rank Trait Bounds](hrtb.md)
Expand Down
61 changes: 33 additions & 28 deletions src/doc/tarpl/atomics.md
Expand Up @@ -17,7 +17,7 @@ face.
The C11 memory model is fundamentally about trying to bridge the gap between the
semantics we want, the optimizations compilers want, and the inconsistent chaos
our hardware wants. *We* would like to just write programs and have them do
exactly what we said but, you know, *fast*. Wouldn't that be great?
exactly what we said but, you know, fast. Wouldn't that be great?



Expand All @@ -35,20 +35,20 @@ y = 3;
x = 2;
```

The compiler may conclude that it would *really* be best if your program did
The compiler may conclude that it would be best if your program did

```rust,ignore
x = 2;
y = 3;
```

This has inverted the order of events *and* completely eliminated one event.
This has inverted the order of events and completely eliminated one event.
From a single-threaded perspective this is completely unobservable: after all
the statements have executed we are in exactly the same state. But if our
program is multi-threaded, we may have been relying on `x` to *actually* be
assigned to 1 before `y` was assigned. We would *really* like the compiler to be
program is multi-threaded, we may have been relying on `x` to actually be
assigned to 1 before `y` was assigned. We would like the compiler to be
able to make these kinds of optimizations, because they can seriously improve
performance. On the other hand, we'd really like to be able to depend on our
performance. On the other hand, we'd also like to be able to depend on our
program *doing the thing we said*.


Expand All @@ -57,15 +57,15 @@ program *doing the thing we said*.
# Hardware Reordering

On the other hand, even if the compiler totally understood what we wanted and
respected our wishes, our *hardware* might instead get us in trouble. Trouble
respected our wishes, our hardware might instead get us in trouble. Trouble
comes from CPUs in the form of memory hierarchies. There is indeed a global
shared memory space somewhere in your hardware, but from the perspective of each
CPU core it is *so very far away* and *so very slow*. Each CPU would rather work
with its local cache of the data and only go through all the *anguish* of
talking to shared memory *only* when it doesn't actually have that memory in
with its local cache of the data and only go through all the anguish of
talking to shared memory only when it doesn't actually have that memory in
cache.

After all, that's the whole *point* of the cache, right? If every read from the
After all, that's the whole point of the cache, right? If every read from the
cache had to run back to shared memory to double check that it hadn't changed,
what would the point be? The end result is that the hardware doesn't guarantee
that events that occur in the same order on *one* thread, occur in the same
Expand Down Expand Up @@ -99,13 +99,13 @@ provides weak ordering guarantees. This has two consequences for concurrent
programming:

* Asking for stronger guarantees on strongly-ordered hardware may be cheap or
even *free* because they already provide strong guarantees unconditionally.
even free because they already provide strong guarantees unconditionally.
Weaker guarantees may only yield performance wins on weakly-ordered hardware.

* Asking for guarantees that are *too* weak on strongly-ordered hardware is
* Asking for guarantees that are too weak on strongly-ordered hardware is
more likely to *happen* to work, even though your program is strictly
incorrect. If possible, concurrent algorithms should be tested on weakly-
ordered hardware.
incorrect. If possible, concurrent algorithms should be tested on
weakly-ordered hardware.



Expand All @@ -115,10 +115,10 @@ programming:

The C11 memory model attempts to bridge the gap by allowing us to talk about the
*causality* of our program. Generally, this is by establishing a *happens
before* relationships between parts of the program and the threads that are
before* relationship between parts of the program and the threads that are
running them. This gives the hardware and compiler room to optimize the program
more aggressively where a strict happens-before relationship isn't established,
but forces them to be more careful where one *is* established. The way we
but forces them to be more careful where one is established. The way we
communicate these relationships are through *data accesses* and *atomic
accesses*.

Expand All @@ -130,8 +130,10 @@ propagate the changes made in data accesses to other threads as lazily and
inconsistently as it wants. Mostly critically, data accesses are how data races
happen. Data accesses are very friendly to the hardware and compiler, but as
we've seen they offer *awful* semantics to try to write synchronized code with.
Actually, that's too weak. *It is literally impossible to write correct
synchronized code using only data accesses*.
Actually, that's too weak.

**It is literally impossible to write correct synchronized code using only data
accesses.**

Atomic accesses are how we tell the hardware and compiler that our program is
multi-threaded. Each atomic access can be marked with an *ordering* that
Expand All @@ -141,7 +143,10 @@ they *can't* do. For the compiler, this largely revolves around re-ordering of
instructions. For the hardware, this largely revolves around how writes are
propagated to other threads. The set of orderings Rust exposes are:

* Sequentially Consistent (SeqCst) Release Acquire Relaxed
* Sequentially Consistent (SeqCst)
* Release
* Acquire
* Relaxed

(Note: We explicitly do not expose the C11 *consume* ordering)

Expand All @@ -154,13 +159,13 @@ synchronize"

Sequentially Consistent is the most powerful of all, implying the restrictions
of all other orderings. Intuitively, a sequentially consistent operation
*cannot* be reordered: all accesses on one thread that happen before and after a
SeqCst access *stay* before and after it. A data-race-free program that uses
cannot be reordered: all accesses on one thread that happen before and after a
SeqCst access stay before and after it. A data-race-free program that uses
only sequentially consistent atomics and data accesses has the very nice
property that there is a single global execution of the program's instructions
that all threads agree on. This execution is also particularly nice to reason
about: it's just an interleaving of each thread's individual executions. This
*does not* hold if you start using the weaker atomic orderings.
does not hold if you start using the weaker atomic orderings.

The relative developer-friendliness of sequential consistency doesn't come for
free. Even on strongly-ordered platforms sequential consistency involves
Expand All @@ -170,8 +175,8 @@ In practice, sequential consistency is rarely necessary for program correctness.
However sequential consistency is definitely the right choice if you're not
confident about the other memory orders. Having your program run a bit slower
than it needs to is certainly better than it running incorrectly! It's also
*mechanically* trivial to downgrade atomic operations to have a weaker
consistency later on. Just change `SeqCst` to e.g. `Relaxed` and you're done! Of
mechanically trivial to downgrade atomic operations to have a weaker
consistency later on. Just change `SeqCst` to `Relaxed` and you're done! Of
course, proving that this transformation is *correct* is a whole other matter.


Expand All @@ -183,15 +188,15 @@ Acquire and Release are largely intended to be paired. Their names hint at their
use case: they're perfectly suited for acquiring and releasing locks, and
ensuring that critical sections don't overlap.

Intuitively, an acquire access ensures that every access after it *stays* after
Intuitively, an acquire access ensures that every access after it stays after
it. However operations that occur before an acquire are free to be reordered to
occur after it. Similarly, a release access ensures that every access before it
*stays* before it. However operations that occur after a release are free to be
stays before it. However operations that occur after a release are free to be
reordered to occur before it.

When thread A releases a location in memory and then thread B subsequently
acquires *the same* location in memory, causality is established. Every write
that happened *before* A's release will be observed by B *after* its release.
that happened before A's release will be observed by B after its release.
However no causality is established with any other threads. Similarly, no
causality is established if A and B access *different* locations in memory.

Expand Down Expand Up @@ -230,7 +235,7 @@ weakly-ordered platforms.
# Relaxed

Relaxed accesses are the absolute weakest. They can be freely re-ordered and
provide no happens-before relationship. Still, relaxed operations *are* still
provide no happens-before relationship. Still, relaxed operations are still
atomic. That is, they don't count as data accesses and any read-modify-write
operations done to them occur atomically. Relaxed operations are appropriate for
things that you definitely want to happen, but don't particularly otherwise care
Expand Down
10 changes: 5 additions & 5 deletions src/doc/tarpl/borrow-splitting.md
Expand Up @@ -2,7 +2,7 @@

The mutual exclusion property of mutable references can be very limiting when
working with a composite structure. The borrow checker understands some basic
stuff, but will fall over pretty easily. It *does* understand structs
stuff, but will fall over pretty easily. It does understand structs
sufficiently to know that it's possible to borrow disjoint fields of a struct
simultaneously. So this works today:

Expand Down Expand Up @@ -50,7 +50,7 @@ to the same value.

In order to "teach" borrowck that what we're doing is ok, we need to drop down
to unsafe code. For instance, mutable slices expose a `split_at_mut` function
that consumes the slice and returns *two* mutable slices. One for everything to
that consumes the slice and returns two mutable slices. One for everything to
the left of the index, and one for everything to the right. Intuitively we know
this is safe because the slices don't overlap, and therefore alias. However
the implementation requires some unsafety:
Expand Down Expand Up @@ -93,10 +93,10 @@ completely incompatible with this API, as it would produce multiple mutable
references to the same object!

However it actually *does* work, exactly because iterators are one-shot objects.
Everything an IterMut yields will be yielded *at most* once, so we don't
*actually* ever yield multiple mutable references to the same piece of data.
Everything an IterMut yields will be yielded at most once, so we don't
actually ever yield multiple mutable references to the same piece of data.

Perhaps surprisingly, mutable iterators *don't* require unsafe code to be
Perhaps surprisingly, mutable iterators don't require unsafe code to be
implemented for many types!

For instance here's a singly linked list:
Expand Down
4 changes: 2 additions & 2 deletions src/doc/tarpl/casts.md
@@ -1,13 +1,13 @@
% Casts

Casts are a superset of coercions: every coercion can be explicitly
invoked via a cast. However some conversions *require* a cast.
invoked via a cast. However some conversions require a cast.
While coercions are pervasive and largely harmless, these "true casts"
are rare and potentially dangerous. As such, casts must be explicitly invoked
using the `as` keyword: `expr as Type`.

True casts generally revolve around raw pointers and the primitive numeric
types. Even though they're dangerous, these casts are *infallible* at runtime.
types. Even though they're dangerous, these casts are infallible at runtime.
If a cast triggers some subtle corner case no indication will be given that
this occurred. The cast will simply succeed. That said, casts must be valid
at the type level, or else they will be prevented statically. For instance,
Expand Down
2 changes: 1 addition & 1 deletion src/doc/tarpl/checked-uninit.md
Expand Up @@ -80,7 +80,7 @@ loop {
// because it relies on actual values.
if true {
// But it does understand that it will only be taken once because
// we *do* unconditionally break out of it. Therefore `x` doesn't
// we unconditionally break out of it. Therefore `x` doesn't
// need to be marked as mutable.
x = 0;
break;
Expand Down
6 changes: 3 additions & 3 deletions src/doc/tarpl/concurrency.md
Expand Up @@ -2,12 +2,12 @@

Rust as a language doesn't *really* have an opinion on how to do concurrency or
parallelism. The standard library exposes OS threads and blocking sys-calls
because *everyone* has those, and they're uniform enough that you can provide
because everyone has those, and they're uniform enough that you can provide
an abstraction over them in a relatively uncontroversial way. Message passing,
green threads, and async APIs are all diverse enough that any abstraction over
them tends to involve trade-offs that we weren't willing to commit to for 1.0.

However the way Rust models concurrency makes it relatively easy design your own
concurrency paradigm as a library and have *everyone else's* code Just Work
concurrency paradigm as a library and have everyone else's code Just Work
with yours. Just require the right lifetimes and Send and Sync where appropriate
and you're off to the races. Or rather, off to the... not... having... races.
and you're off to the races. Or rather, off to the... not... having... races.
6 changes: 3 additions & 3 deletions src/doc/tarpl/constructors.md
Expand Up @@ -37,14 +37,14 @@ blindly memcopied to somewhere else in memory. This means pure on-the-stack-but-
still-movable intrusive linked lists are simply not happening in Rust (safely).

Assignment and copy constructors similarly don't exist because move semantics
are the *only* semantics in Rust. At most `x = y` just moves the bits of y into
the x variable. Rust *does* provide two facilities for providing C++'s copy-
are the only semantics in Rust. At most `x = y` just moves the bits of y into
the x variable. Rust does provide two facilities for providing C++'s copy-
oriented semantics: `Copy` and `Clone`. Clone is our moral equivalent of a copy
constructor, but it's never implicitly invoked. You have to explicitly call
`clone` on an element you want to be cloned. Copy is a special case of Clone
where the implementation is just "copy the bits". Copy types *are* implicitly
cloned whenever they're moved, but because of the definition of Copy this just
means *not* treating the old copy as uninitialized -- a no-op.
means not treating the old copy as uninitialized -- a no-op.

While Rust provides a `Default` trait for specifying the moral equivalent of a
default constructor, it's incredibly rare for this trait to be used. This is
Expand Down
2 changes: 1 addition & 1 deletion src/doc/tarpl/conversions.md
Expand Up @@ -8,7 +8,7 @@ a different type. Because Rust encourages encoding important properties in the
type system, these problems are incredibly pervasive. As such, Rust
consequently gives you several ways to solve them.

First we'll look at the ways that *Safe Rust* gives you to reinterpret values.
First we'll look at the ways that Safe Rust gives you to reinterpret values.
The most trivial way to do this is to just destructure a value into its
constituent parts and then build a new type out of them. e.g.

Expand Down
6 changes: 3 additions & 3 deletions src/doc/tarpl/data.md
@@ -1,5 +1,5 @@
% Data Representation in Rust

Low-level programming cares a lot about data layout. It's a big deal. It also pervasively
influences the rest of the language, so we're going to start by digging into how data is
represented in Rust.
Low-level programming cares a lot about data layout. It's a big deal. It also
pervasively influences the rest of the language, so we're going to start by
digging into how data is represented in Rust.

0 comments on commit 0919f4a

Please sign in to comment.