Skip to content

Commit

Permalink
Merge branch 'master' into subtyping-rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
conradludgate committed Jun 4, 2022
2 parents 9fb3a36 + 3a43983 commit 0e9a7a6
Show file tree
Hide file tree
Showing 24 changed files with 409 additions and 107 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Update rustup
run: rustup self update
- name: Install Rust
Expand Down
8 changes: 4 additions & 4 deletions src/borrow-splitting.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Splitting Borrows

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
sufficiently to know that it's possible to borrow disjoint fields of a struct
simultaneously. So this works today:
working with a composite structure. The borrow checker (a.k.a. borrowck)
understands some basic 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:

```rust
struct Foo {
Expand Down
5 changes: 4 additions & 1 deletion src/checked-uninit.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ fn main() {
```

This is based off of a basic branch analysis: every branch must assign a value
to `x` before it is first used. Interestingly, Rust doesn't require the variable
to `x` before it is first used. For short, we also say that "`x` is init" or
"`x` is uninit".

Interestingly, Rust doesn't require the variable
to be mutable to perform a delayed initialization if every branch assigns
exactly once. However the analysis does not take advantage of constant analysis
or anything like that. So this compiles:
Expand Down
2 changes: 1 addition & 1 deletion src/destructors.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Next variant.

In general this works really nicely because you don't need to worry about
adding/removing drops when you refactor your data layout. Still there's
certainly many valid usecases for needing to do trickier things with
certainly many valid use cases for needing to do trickier things with
destructors.

The classic safe solution to overriding recursive drop and allowing moving out
Expand Down
6 changes: 3 additions & 3 deletions src/dropck.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ Interestingly, only generic types need to worry about this. If they aren't
generic, then the only lifetimes they can harbor are `'static`, which will truly
live _forever_. This is why this problem is referred to as _sound generic drop_.
Sound generic drop is enforced by the _drop checker_. As of this writing, some
of the finer details of how the drop checker validates types is totally up in
the air. However The Big Rule is the subtlety that we have focused on this whole
section:
of the finer details of how the drop checker (also called dropck) validates
types is totally up in the air. However The Big Rule is the subtlety that we
have focused on this whole section:

**For a generic type to soundly implement drop, its generics arguments must
strictly outlive it.**
Expand Down
2 changes: 1 addition & 1 deletion src/exception-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ The basic idea is simple: if the comparison panics, we just toss the loose
element in the logically uninitialized index and bail out. Anyone who observes
the heap will see a potentially *inconsistent* heap, but at least it won't
cause any double-drops! If the algorithm terminates normally, then this
operation happens to coincide precisely with the how we finish up regardless.
operation happens to coincide precisely with how we finish up regardless.

Sadly, Rust has no such construct, so we're going to need to roll our own! The
way to do this is to store the algorithm's state in a separate struct with a
Expand Down
8 changes: 4 additions & 4 deletions src/exotic-sizes.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ In principle, Rust can do some interesting analyses and optimizations based
on this fact. For instance, `Result<T, Void>` is represented as just `T`,
because the `Err` case doesn't actually exist (strictly speaking, this is only
an optimization that is not guaranteed, so for example transmuting one into the
other is still UB).
other is still Undefined Behavior).

The following *could* also compile:

Expand All @@ -157,15 +157,15 @@ because that wouldn't make sense.
We recommend against modelling C's `void*` type with `*const Void`.
A lot of people started doing that but quickly ran into trouble because
Rust doesn't really have any safety guards against trying to instantiate
empty types with unsafe code, and if you do it, it's Undefined Behaviour.
empty types with unsafe code, and if you do it, it's Undefined Behavior.
This was especially problematic because developers had a habit of converting
raw pointers to references and `&Void` is *also* Undefined Behaviour to
raw pointers to references and `&Void` is *also* Undefined Behavior to
construct.

`*const ()` (or equivalent) works reasonably well for `void*`, and can be made
into a reference without any safety problems. It still doesn't prevent you from
trying to read or write values, but at least it compiles to a no-op instead
of UB.
of Undefined Behavior.

## Extern Types

Expand Down
115 changes: 109 additions & 6 deletions src/ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ We'll create a C file to call the `hello_from_rust` function and compile it by `
C file should look like:

```c
extern void hello_from_rust();

int main(void) {
hello_from_rust();
return 0;
Expand Down Expand Up @@ -716,17 +718,118 @@ void register(int (*f)(int (*)(int), int)) {
No `transmute` required!
## FFI and panics
## FFI and unwinding
It’s important to be mindful of unwinding when working with FFI. Each
non-`Rust` ABI comes in two variants, one with `-unwind` suffix and one without. If
you expect Rust `panic`s or foreign (e.g. C++) exceptions to cross an FFI
boundary, that boundary must use the appropriate `-unwind` ABI string (note
that compiling with `panic=abort` will still cause `panic!` to immediately
abort the process, regardless of which ABI is specified by the function that
`panic`s).
Conversely, if you do not expect unwinding to cross an ABI boundary, use one of
the non-`unwind` ABI strings (other than `Rust`, which always permits
unwinding). If an unwinding operation does encounter an ABI boundary that is
not permitted to unwind, the behavior depends on the source of the unwinding
(Rust `panic` or a foreign exception):
* `panic` will cause the process to safely abort.
* A foreign exception entering Rust will cause undefined behavior.
Note that the interaction of `catch_unwind` with foreign exceptions **is
undefined**, as is the interaction of `panic` with foreign exception-catching
mechanisms (notably C++'s `try`/`catch`).
### Rust `panic` with `"C-unwind"`
<!-- ignore: using unstable feature -->
```rust,ignore
#[no_mangle]
extern "C-unwind" fn example() {
panic!("Uh oh");
}
```

This function (when compiled with `panic=unwind`) is permitted to unwind C++
stack frames.

```text
[Rust function with `catch_unwind`, which stops the unwinding]
|
...
|
[C++ frames]
| ^
| (calls) | (unwinding
v | goes this
[Rust function `example`] | way)
| |
+--- rust function panics --+
```

If the C++ frames have objects, their destructors will be called.

### C++ `throw` with `"C-unwind"`

<!-- ignore: using unstable feature -->
```rust,ignore
#[link(...)]
extern "C-unwind" {
// A C++ function that may throw an exception
fn may_throw();
}
#[no_mangle]
extern "C-unwind" fn rust_passthrough() {
let b = Box::new(5);
unsafe { may_throw(); }
println!("{:?}", &b);
}
```

A C++ function with a `try` block may invoke `rust_passthrough` and `catch` an
exception thrown by `may_throw`.

```text
[C++ function with `try` block that invokes `rust_passthrough`]
|
...
|
[Rust function `rust_passthrough`]
| ^
| (calls) | (unwinding
v | goes this
[C++ function `may_throw`] | way)
| |
+--- C++ function throws ----+
```

If `may_throw` does throw an exception, `b` will be dropped. Otherwise, `5`
will be printed.

### `panic` can be stopped at an ABI boundary

```rust
#[no_mangle]
extern "C" fn assert_nonzero(input: u32) {
assert!(input != 0)
}
```

If `assert_nonzero` is called with the argument `0`, the runtime is guaranteed
to (safely) abort the process, whether or not compiled with `panic=abort`.

### Catching `panic` preemptively

It’s important to be mindful of `panic!`s when working with FFI. A `panic!`
across an FFI boundary is undefined behavior. If you’re writing code that may
panic, you should run it in a closure with [`catch_unwind`]:
If you are writing Rust code that may panic, and you don't wish to abort the
process if it panics, you must use [`catch_unwind`]:

```rust
use std::panic::catch_unwind;

#[no_mangle]
pub extern fn oh_no() -> i32 {
pub extern "C" fn oh_no() -> i32 {
let result = catch_unwind(|| {
panic!("Oops!");
});
Expand All @@ -740,7 +843,7 @@ fn main() {}
```

Please note that [`catch_unwind`] will only catch unwinding panics, not
those who abort the process. See the documentation of [`catch_unwind`]
those that abort the process. See the documentation of [`catch_unwind`]
for more information.

[`catch_unwind`]: ../std/panic/fn.catch_unwind.html
Expand Down
2 changes: 1 addition & 1 deletion src/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Where The Reference exists to detail the syntax and semantics of every part of t

The Reference will tell you the syntax and semantics of references, destructors, and unwinding, but it won't tell you how combining them can lead to exception-safety issues, or how to deal with those issues.

It should be noted that we haven't synced The Rustnomicon and The Reference well, so they may have a duplicate content.
It should be noted that we haven't synced The Rustnomicon and The Reference well, so they may have duplicate content.
In general, if the two documents disagree, The Reference should be assumed to be correct (it isn't yet considered normative, it's just better maintained).

Topics that are within the scope of this book include: the meaning of (un)safety, unsafe primitives provided by the language and standard library, techniques for creating safe abstractions with those unsafe primitives, subtyping and variance, exception-safety (panic/unwind-safety), working with uninitialized memory, type punning, concurrency, interoperating with other languages (FFI), optimization tricks, how constructs lower to compiler/OS/hardware primitives, how to **not** make the memory model people angry, how you're **going** to make the memory model people angry, and more.
Expand Down
4 changes: 2 additions & 2 deletions src/leaking.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ horribly degenerate. Also *oh my gosh* it's such a ridiculous corner case.
> Note: This API has already been removed from std, for more information
> you may refer [issue #24292](https://github.com/rust-lang/rust/issues/24292).
>
> We still remain this chapter here because we think this example is still
> important, regardless of if it is still in std.
> This section remains here because we think this example is still
> important, regardless of whether it is part of std or not.
The thread::scoped API intended to allow threads to be spawned that reference
data on their parent's stack without any synchronization over that data by
Expand Down
7 changes: 4 additions & 3 deletions src/lifetime-mismatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ care about, but the lifetime system is too coarse-grained to handle that.

## Improperly reduced borrows

The following code fails to compile, because Rust doesn't understand that the borrow
is no longer needed and conservatively falls back to using a whole scope for it.
This will eventually get fixed.
The following code fails to compile, because Rust sees that a variable, `map`,
is borrowed twice, and can not infer that the first borrow stops to be needed
before the second one occurs. This is caused by Rust conservatively falling back
to using a whole scope for the first borow. This will eventually get fixed.

```rust,compile_fail
# use std::collections::HashMap;
Expand Down
15 changes: 8 additions & 7 deletions src/lifetimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ z = y;
'b: {
let z: &'b i32;
'c: {
// Must use 'b here because this reference is
// being passed to that scope.
// Must use 'b here because the reference to x is
// being passed to the scope 'b.
let y: &'b i32 = &'b x;
z = y;
}
Expand Down Expand Up @@ -208,11 +208,12 @@ violate the *second* rule of references.

However this is *not at all* how Rust reasons that this program is bad. Rust
doesn't understand that `x` is a reference to a subpath of `data`. It doesn't
understand `Vec` at all. What it *does* see is that `x` has to live for `'b` to
be printed. The signature of `Index::index` subsequently demands that the
reference we take to `data` has to survive for `'b`. When we try to call `push`,
it then sees us try to make an `&'c mut data`. Rust knows that `'c` is contained
within `'b`, and rejects our program because the `&'b data` must still be alive!
understand `Vec` at all. What it *does* see is that `x` has to live for `'b` in
order to be printed. The signature of `Index::index` subsequently demands that
the reference we take to `data` has to survive for `'b`. When we try to call
`push`, it then sees us try to make an `&'c mut data`. Rust knows that `'c` is
contained within `'b`, and rejects our program because the `&'b data` must still
be alive!

Here we see that the lifetime system is much more coarse than the reference
semantics we're actually interested in preserving. For the most part, *that's
Expand Down
2 changes: 1 addition & 1 deletion src/meet-safe-and-unsafe.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ do, but we'll do anyway.
Safe Rust is the *true* Rust programming language. If all you do is write Safe
Rust, you will never have to worry about type-safety or memory-safety. You will
never endure a dangling pointer, a use-after-free, or any other kind of
Undefined Behavior.
Undefined Behavior (a.k.a. UB).

The standard library also gives you enough utilities out of the box that you'll
be able to write high-performance applications and libraries in pure idiomatic
Expand Down
Loading

0 comments on commit 0e9a7a6

Please sign in to comment.