Skip to content

Commit

Permalink
last of the emphasis cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Gankra committed Jul 31, 2015
1 parent 7b33a1e commit 554efc0
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 84 deletions.
16 changes: 9 additions & 7 deletions src/doc/tarpl/races.md
Expand Up @@ -12,11 +12,13 @@ it's impossible to alias a mutable reference, so it's impossible to perform a
data race. Interior mutability makes this more complicated, which is largely why
we have the Send and Sync traits (see below).

However Rust *does not* prevent general race conditions. This is
pretty fundamentally impossible, and probably honestly undesirable. Your hardware
is racy, your OS is racy, the other programs on your computer are racy, and the
world this all runs in is racy. Any system that could genuinely claim to prevent
*all* race conditions would be pretty awful to use, if not just incorrect.
**However Rust does not prevent general race conditions.**

This is pretty fundamentally impossible, and probably honestly undesirable. Your
hardware is racy, your OS is racy, the other programs on your computer are racy,
and the world this all runs in is racy. Any system that could genuinely claim to
prevent *all* race conditions would be pretty awful to use, if not just
incorrect.

So it's perfectly "fine" for a Safe Rust program to get deadlocked or do
something incredibly stupid with incorrect synchronization. Obviously such a
Expand Down Expand Up @@ -46,7 +48,7 @@ thread::spawn(move || {
});
// Index with the value loaded from the atomic. This is safe because we
// read the atomic memory only once, and then pass a *copy* of that value
// read the atomic memory only once, and then pass a copy of that value
// to the Vec's indexing implementation. This indexing will be correctly
// bounds checked, and there's no chance of the value getting changed
// in the middle. However our program may panic if the thread we spawned
Expand Down Expand Up @@ -75,7 +77,7 @@ thread::spawn(move || {
if idx.load(Ordering::SeqCst) < data.len() {
unsafe {
// Incorrectly loading the idx *after* we did the bounds check.
// Incorrectly loading the idx after we did the bounds check.
// It could have changed. This is a race condition, *and dangerous*
// because we decided to do `get_unchecked`, which is `unsafe`.
println!("{}", data.get_unchecked(idx.load(Ordering::SeqCst)));
Expand Down
14 changes: 7 additions & 7 deletions src/doc/tarpl/repr-rust.md
Expand Up @@ -70,7 +70,7 @@ struct B {
Rust *does* guarantee that two instances of A have their data laid out in
exactly the same way. However Rust *does not* guarantee that an instance of A
has the same field ordering or padding as an instance of B (in practice there's
no *particular* reason why they wouldn't, other than that its not currently
no particular reason why they wouldn't, other than that its not currently
guaranteed).

With A and B as written, this is basically nonsensical, but several other
Expand All @@ -88,9 +88,9 @@ struct Foo<T, U> {
```

Now consider the monomorphizations of `Foo<u32, u16>` and `Foo<u16, u32>`. If
Rust lays out the fields in the order specified, we expect it to *pad* the
values in the struct to satisfy their *alignment* requirements. So if Rust
didn't reorder fields, we would expect Rust to produce the following:
Rust lays out the fields in the order specified, we expect it to pad the
values in the struct to satisfy their alignment requirements. So if Rust
didn't reorder fields, we would expect it to produce the following:

```rust,ignore
struct Foo<u16, u32> {
Expand All @@ -112,7 +112,7 @@ The latter case quite simply wastes space. An optimal use of space therefore
requires different monomorphizations to have *different field orderings*.

**Note: this is a hypothetical optimization that is not yet implemented in Rust
**1.0
1.0**

Enums make this consideration even more complicated. Naively, an enum such as:

Expand All @@ -128,8 +128,8 @@ would be laid out as:

```rust
struct FooRepr {
data: u64, // this is *really* either a u64, u32, or u8 based on `tag`
tag: u8, // 0 = A, 1 = B, 2 = C
data: u64, // this is either a u64, u32, or u8 based on `tag`
tag: u8, // 0 = A, 1 = B, 2 = C
}
```

Expand Down
50 changes: 25 additions & 25 deletions src/doc/tarpl/safe-unsafe-meaning.md
Expand Up @@ -5,7 +5,7 @@ So what's the relationship between Safe and Unsafe Rust? How do they interact?
Rust models the separation between Safe and Unsafe Rust with the `unsafe`
keyword, which can be thought as a sort of *foreign function interface* (FFI)
between Safe and Unsafe Rust. This is the magic behind why we can say Safe Rust
is a safe language: all the scary unsafe bits are relegated *exclusively* to FFI
is a safe language: all the scary unsafe bits are relegated exclusively to FFI
*just like every other safe language*.

However because one language is a subset of the other, the two can be cleanly
Expand Down Expand Up @@ -61,50 +61,50 @@ The need for unsafe traits boils down to the fundamental property of safe code:
**No matter how completely awful Safe code is, it can't cause Undefined
Behaviour.**

This means that Unsafe, **the royal vanguard of Undefined Behaviour**, has to be
*super paranoid* about generic safe code. Unsafe is free to trust *specific* safe
code (or else you would degenerate into infinite spirals of paranoid despair).
It is generally regarded as ok to trust the standard library to be correct, as
`std` is effectively an extension of the language (and you *really* just have
to trust the language). If `std` fails to uphold the guarantees it declares,
then it's basically a language bug.
This means that Unsafe Rust, **the royal vanguard of Undefined Behaviour**, has to be
*super paranoid* about generic safe code. To be clear, Unsafe Rust is totally free to trust
specific safe code. Anything else would degenerate into infinite spirals of
paranoid despair. In particular it's generally regarded as ok to trust the standard library
to be correct. `std` is effectively an extension of the language, and you
really just have to trust the language. If `std` fails to uphold the
guarantees it declares, then it's basically a language bug.

That said, it would be best to minimize *needlessly* relying on properties of
concrete safe code. Bugs happen! Of course, I must reinforce that this is only
a concern for Unsafe code. Safe code can blindly trust anyone and everyone
as far as basic memory-safety is concerned.

On the other hand, safe traits are free to declare arbitrary contracts, but because
implementing them is Safe, Unsafe can't trust those contracts to actually
implementing them is safe, unsafe code can't trust those contracts to actually
be upheld. This is different from the concrete case because *anyone* can
randomly implement the interface. There is something fundamentally different
about trusting a *particular* piece of code to be correct, and trusting *all the
about trusting a particular piece of code to be correct, and trusting *all the
code that will ever be written* to be correct.

For instance Rust has `PartialOrd` and `Ord` traits to try to differentiate
between types which can "just" be compared, and those that actually implement a
*total* ordering. Pretty much every API that wants to work with data that can be
compared *really* wants Ord data. For instance, a sorted map like BTreeMap
total ordering. Pretty much every API that wants to work with data that can be
compared wants Ord data. For instance, a sorted map like BTreeMap
*doesn't even make sense* for partially ordered types. If you claim to implement
Ord for a type, but don't actually provide a proper total ordering, BTreeMap will
get *really confused* and start making a total mess of itself. Data that is
inserted may be impossible to find!

But that's okay. BTreeMap is safe, so it guarantees that even if you give it a
*completely* garbage Ord implementation, it will still do something *safe*. You
won't start reading uninitialized memory or unallocated memory. In fact, BTreeMap
completely garbage Ord implementation, it will still do something *safe*. You
won't start reading uninitialized or unallocated memory. In fact, BTreeMap
manages to not actually lose any of your data. When the map is dropped, all the
destructors will be successfully called! Hooray!

However BTreeMap is implemented using a modest spoonful of Unsafe (most collections
are). That means that it is not necessarily *trivially true* that a bad Ord
implementation will make BTreeMap behave safely. Unsafe must be sure not to rely
on Ord *where safety is at stake*. Ord is provided by Safe, and safety is not
Safe's responsibility to uphold.
However BTreeMap is implemented using a modest spoonful of Unsafe Rust (most collections
are). That means that it's not necessarily *trivially true* that a bad Ord
implementation will make BTreeMap behave safely. BTreeMap must be sure not to rely
on Ord *where safety is at stake*. Ord is provided by safe code, and safety is not
safe code's responsibility to uphold.

But wouldn't it be grand if there was some way for Unsafe to trust *some* trait
But wouldn't it be grand if there was some way for Unsafe to trust some trait
contracts *somewhere*? This is the problem that unsafe traits tackle: by marking
*the trait itself* as unsafe *to implement*, Unsafe can trust the implementation
*the trait itself* as unsafe to implement, unsafe code can trust the implementation
to uphold the trait's contract. Although the trait implementation may be
incorrect in arbitrary other ways.

Expand All @@ -126,7 +126,7 @@ But it's probably not the implementation you want.

Rust has traditionally avoided making traits unsafe because it makes Unsafe
pervasive, which is not desirable. Send and Sync are unsafe is because thread
safety is a *fundamental property* that Unsafe cannot possibly hope to defend
safety is a *fundamental property* that unsafe code cannot possibly hope to defend
against in the same way it would defend against a bad Ord implementation. The
only way to possibly defend against thread-unsafety would be to *not use
threading at all*. Making every load and store atomic isn't even sufficient,
Expand All @@ -135,10 +135,10 @@ in memory. For instance, the pointer and capacity of a Vec must be in sync.

Even concurrent paradigms that are traditionally regarded as Totally Safe like
message passing implicitly rely on some notion of thread safety -- are you
really message-passing if you pass a *pointer*? Send and Sync therefore require
some *fundamental* level of trust that Safe code can't provide, so they must be
really message-passing if you pass a pointer? Send and Sync therefore require
some fundamental level of trust that Safe code can't provide, so they must be
unsafe to implement. To help obviate the pervasive unsafety that this would
introduce, Send (resp. Sync) is *automatically* derived for all types composed only
introduce, Send (resp. Sync) is automatically derived for all types composed only
of Send (resp. Sync) values. 99% of types are Send and Sync, and 99% of those
never actually say it (the remaining 1% is overwhelmingly synchronization
primitives).
Expand Down
29 changes: 15 additions & 14 deletions src/doc/tarpl/send-and-sync.md
Expand Up @@ -8,20 +8,19 @@ captures this with through the `Send` and `Sync` traits.
* A type is Send if it is safe to send it to another thread. A type is Sync if
* it is safe to share between threads (`&T` is Send).

Send and Sync are *very* fundamental to Rust's concurrency story. As such, a
Send and Sync are fundamental to Rust's concurrency story. As such, a
substantial amount of special tooling exists to make them work right. First and
foremost, they're *unsafe traits*. This means that they are unsafe *to
implement*, and other unsafe code can *trust* that they are correctly
foremost, they're [unsafe traits][]. This means that they are unsafe to
implement, and other unsafe code can that they are correctly
implemented. Since they're *marker traits* (they have no associated items like
methods), correctly implemented simply means that they have the intrinsic
properties an implementor should have. Incorrectly implementing Send or Sync can
cause Undefined Behaviour.

Send and Sync are also what Rust calls *opt-in builtin traits*. This means that,
unlike every other trait, they are *automatically* derived: if a type is
composed entirely of Send or Sync types, then it is Send or Sync. Almost all
primitives are Send and Sync, and as a consequence pretty much all types you'll
ever interact with are Send and Sync.
Send and Sync are also automatically derived traits. This means that, unlike
every other trait, if a type is composed entirely of Send or Sync types, then it
is Send or Sync. Almost all primitives are Send and Sync, and as a consequence
pretty much all types you'll ever interact with are Send and Sync.

Major exceptions include:

Expand All @@ -37,13 +36,12 @@ sense, one could argue that it would be "fine" for them to be marked as thread
safe.

However it's important that they aren't thread safe to prevent types that
*contain them* from being automatically marked as thread safe. These types have
contain them from being automatically marked as thread safe. These types have
non-trivial untracked ownership, and it's unlikely that their author was
necessarily thinking hard about thread safety. In the case of Rc, we have a nice
example of a type that contains a `*mut` that is *definitely* not thread safe.
example of a type that contains a `*mut` that is definitely not thread safe.

Types that aren't automatically derived can *opt-in* to Send and Sync by simply
implementing them:
Types that aren't automatically derived can simply implement them if desired:

```rust
struct MyBox(*mut u8);
Expand All @@ -52,12 +50,13 @@ unsafe impl Send for MyBox {}
unsafe impl Sync for MyBox {}
```

In the *incredibly rare* case that a type is *inappropriately* automatically
derived to be Send or Sync, then one can also *unimplement* Send and Sync:
In the *incredibly rare* case that a type is inappropriately automatically
derived to be Send or Sync, then one can also unimplement Send and Sync:

```rust
#![feature(optin_builtin_traits)]

// I have some magic semantics for some synchronization primitive!
struct SpecialThreadToken(u8);

impl !Send for SpecialThreadToken {}
Expand All @@ -77,3 +76,5 @@ largely behave like an `&` or `&mut` into the collection.

TODO: better explain what can or can't be Send or Sync. Sufficient to appeal
only to data races?

[unsafe traits]: safe-unsafe-meaning.html
26 changes: 14 additions & 12 deletions src/doc/tarpl/subtyping.md
@@ -1,14 +1,14 @@
% Subtyping and Variance

Although Rust doesn't have any notion of structural inheritance, it *does*
include subtyping. In Rust, subtyping derives entirely from *lifetimes*. Since
include subtyping. In Rust, subtyping derives entirely from lifetimes. Since
lifetimes are scopes, we can partially order them based on the *contains*
(outlives) relationship. We can even express this as a generic bound.

Subtyping on lifetimes in terms of that relationship: if `'a: 'b` ("a contains
Subtyping on lifetimes is in terms of that relationship: if `'a: 'b` ("a contains
b" or "a outlives b"), then `'a` is a subtype of `'b`. This is a large source of
confusion, because it seems intuitively backwards to many: the bigger scope is a
*sub type* of the smaller scope.
*subtype* of the smaller scope.

This does in fact make sense, though. The intuitive reason for this is that if
you expect an `&'a u8`, then it's totally fine for me to hand you an `&'static
Expand Down Expand Up @@ -72,7 +72,7 @@ to be able to pass `&&'static str` where an `&&'a str` is expected. The
additional level of indirection does not change the desire to be able to pass
longer lived things where shorted lived things are expected.

However this logic *does not* apply to `&mut`. To see why `&mut` should
However this logic doesn't apply to `&mut`. To see why `&mut` should
be invariant over T, consider the following code:

```rust,ignore
Expand Down Expand Up @@ -109,7 +109,7 @@ between `'a` and T is that `'a` is a property of the reference itself,
while T is something the reference is borrowing. If you change T's type, then
the source still remembers the original type. However if you change the
lifetime's type, no one but the reference knows this information, so it's fine.
Put another way, `&'a mut T` owns `'a`, but only *borrows* T.
Put another way: `&'a mut T` owns `'a`, but only *borrows* T.

`Box` and `Vec` are interesting cases because they're variant, but you can
definitely store values in them! This is where Rust gets really clever: it's
Expand All @@ -118,15 +118,15 @@ in them *via a mutable reference*! The mutable reference makes the whole type
invariant, and therefore prevents you from smuggling a short-lived type into
them.

Being variant *does* allows `Box` and `Vec` to be weakened when shared
Being variant allows `Box` and `Vec` to be weakened when shared
immutably. So you can pass a `&Box<&'static str>` where a `&Box<&'a str>` is
expected.

However what should happen when passing *by-value* is less obvious. It turns out
that, yes, you can use subtyping when passing by-value. That is, this works:

```rust
fn get_box<'a>(str: &'a u8) -> Box<&'a str> {
fn get_box<'a>(str: &'a str) -> Box<&'a str> {
// string literals are `&'static str`s
Box::new("hello")
}
Expand All @@ -150,7 +150,7 @@ signature:
fn foo(&'a str) -> usize;
```

This signature claims that it can handle any `&str` that lives *at least* as
This signature claims that it can handle any `&str` that lives at least as
long as `'a`. Now if this signature was variant over `&'a str`, that
would mean

Expand All @@ -159,10 +159,12 @@ fn foo(&'static str) -> usize;
```

could be provided in its place, as it would be a subtype. However this function
has a *stronger* requirement: it says that it can *only* handle `&'static str`s,
and nothing else. Therefore functions are not variant over their arguments.
has a stronger requirement: it says that it can only handle `&'static str`s,
and nothing else. Giving `&'a str`s to it would be unsound, as it's free to
assume that what it's given lives forever. Therefore functions are not variant
over their arguments.

To see why `Fn(T) -> U` should be *variant* over U, consider the following
To see why `Fn(T) -> U` should be variant over U, consider the following
function signature:

```rust,ignore
Expand All @@ -177,7 +179,7 @@ therefore completely reasonable to provide
fn foo(usize) -> &'static str;
```

in its place. Therefore functions *are* variant over their return type.
in its place. Therefore functions are variant over their return type.

`*const` has the exact same semantics as `&`, so variance follows. `*mut` on the
other hand can dereference to an `&mut` whether shared or not, so it is marked
Expand Down
4 changes: 2 additions & 2 deletions src/doc/tarpl/unwinding.md
Expand Up @@ -31,12 +31,12 @@ panics can only be caught by the parent thread. This means catching a panic
requires spinning up an entire OS thread! This unfortunately stands in conflict
to Rust's philosophy of zero-cost abstractions.

There is an *unstable* API called `catch_panic` that enables catching a panic
There is an unstable API called `catch_panic` that enables catching a panic
without spawning a thread. Still, we would encourage you to only do this
sparingly. In particular, Rust's current unwinding implementation is heavily
optimized for the "doesn't unwind" case. If a program doesn't unwind, there
should be no runtime cost for the program being *ready* to unwind. As a
consequence, *actually* unwinding will be more expensive than in e.g. Java.
consequence, actually unwinding will be more expensive than in e.g. Java.
Don't build your programs to unwind under normal circumstances. Ideally, you
should only panic for programming errors or *extreme* problems.

Expand Down

0 comments on commit 554efc0

Please sign in to comment.