Merge pull request #288 from JohnTitor/audit-ignore

Audit `ignore` annotations
pull/291/head
Eric Huss 3 years ago committed by GitHub
commit 5023ba0a23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -56,6 +56,7 @@ and `output` overlap, such as `compute(&x, &mut x)`.
With that input, we could get this execution: With that input, we could get this execution:
<!-- ignore: expanded code -->
```rust,ignore ```rust,ignore
// input == output == 0xabad1dea // input == output == 0xabad1dea
// *input == *output == 20 // *input == *output == 20

@ -10,6 +10,7 @@ We'll first need a way to construct an `Arc<T>`.
This is pretty simple, as we just need to box the `ArcInner<T>` and get a This is pretty simple, as we just need to box the `ArcInner<T>` and get a
`NonNull<T>` pointer to it. `NonNull<T>` pointer to it.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> Arc<T> { impl<T> Arc<T> {
pub fn new(data: T) -> Arc<T> { pub fn new(data: T) -> Arc<T> {
@ -41,6 +42,7 @@ This is okay because:
if it is the only `Arc` referencing that data (which only happens in `Drop`) if it is the only `Arc` referencing that data (which only happens in `Drop`)
* We use atomics for the shared mutable reference counting * We use atomics for the shared mutable reference counting
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
unsafe impl<T: Sync + Send> Send for Arc<T> {} unsafe impl<T: Sync + Send> Send for Arc<T> {}
unsafe impl<T: Sync + Send> Sync for Arc<T> {} unsafe impl<T: Sync + Send> Sync for Arc<T> {}
@ -61,6 +63,8 @@ as `Rc` is not thread-safe.
To dereference the `NonNull<T>` pointer into a `&T`, we can call To dereference the `NonNull<T>` pointer into a `&T`, we can call
`NonNull::as_ref`. This is unsafe, unlike the typical `as_ref` function, so we `NonNull::as_ref`. This is unsafe, unlike the typical `as_ref` function, so we
must call it like this: must call it like this:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
unsafe { self.ptr.as_ref() } unsafe { self.ptr.as_ref() }
``` ```
@ -79,11 +83,15 @@ to the data inside?
What we need now is an implementation of `Deref`. What we need now is an implementation of `Deref`.
We'll need to import the trait: We'll need to import the trait:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
use std::ops::Deref; use std::ops::Deref;
``` ```
And here's the implementation: And here's the implementation:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> Deref for Arc<T> { impl<T> Deref for Arc<T> {
type Target = T; type Target = T;
@ -101,6 +109,8 @@ Pretty simple, eh? This simply dereferences the `NonNull` pointer to the
## Code ## Code
Here's all the code from this section: Here's all the code from this section:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
use std::ops::Deref; use std::ops::Deref;

@ -9,12 +9,14 @@ Basically, we need to:
First, we need to get access to the `ArcInner`: First, we need to get access to the `ArcInner`:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let inner = unsafe { self.ptr.as_ref() }; let inner = unsafe { self.ptr.as_ref() };
``` ```
We can update the atomic reference count as follows: We can update the atomic reference count as follows:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let old_rc = inner.rc.fetch_add(1, Ordering::???); let old_rc = inner.rc.fetch_add(1, Ordering::???);
``` ```
@ -30,13 +32,14 @@ ordering, see [the section on atomics](../atomics.md).
Thus, the code becomes this: Thus, the code becomes this:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed); let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed);
``` ```
We'll need to add another import to use `Ordering`: We'll need to add another import to use `Ordering`:
```rust,ignore ```rust
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
``` ```
@ -61,6 +64,7 @@ machines) incrementing the reference count at once. This is what we'll do.
It's pretty simple to implement this behavior: It's pretty simple to implement this behavior:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
if old_rc >= isize::MAX as usize { if old_rc >= isize::MAX as usize {
std::process::abort(); std::process::abort();
@ -69,6 +73,7 @@ if old_rc >= isize::MAX as usize {
Then, we need to return a new instance of the `Arc`: Then, we need to return a new instance of the `Arc`:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
Self { Self {
ptr: self.ptr, ptr: self.ptr,
@ -78,6 +83,7 @@ Self {
Now, let's wrap this all up inside the `Clone` implementation: Now, let's wrap this all up inside the `Clone` implementation:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;

@ -15,6 +15,7 @@ Basically, we need to:
First, we'll need to get access to the `ArcInner`: First, we'll need to get access to the `ArcInner`:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let inner = unsafe { self.ptr.as_ref() }; let inner = unsafe { self.ptr.as_ref() };
``` ```
@ -24,6 +25,7 @@ also return if the returned value from `fetch_sub` (the value of the reference
count before decrementing it) is not equal to `1` (which happens when we are not count before decrementing it) is not equal to `1` (which happens when we are not
the last reference to the data). the last reference to the data).
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
if inner.rc.fetch_sub(1, Ordering::Relaxed) != 1 { if inner.rc.fetch_sub(1, Ordering::Relaxed) != 1 {
return; return;
@ -63,20 +65,17 @@ implementation of `Arc`][3]:
To do this, we do the following: To do this, we do the following:
```rust,ignore ```rust
atomic::fence(Ordering::Acquire); # use std::sync::atomic::Ordering;
```
We'll need to import `std::sync::atomic` itself:
```rust,ignore
use std::sync::atomic; use std::sync::atomic;
atomic::fence(Ordering::Acquire);
``` ```
Finally, we can drop the data itself. We use `Box::from_raw` to drop the boxed Finally, we can drop the data itself. We use `Box::from_raw` to drop the boxed
`ArcInner<T>` and its data. This takes a `*mut T` and not a `NonNull<T>`, so we `ArcInner<T>` and its data. This takes a `*mut T` and not a `NonNull<T>`, so we
must convert using `NonNull::as_ptr`. must convert using `NonNull::as_ptr`.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
unsafe { Box::from_raw(self.ptr.as_ptr()); } unsafe { Box::from_raw(self.ptr.as_ptr()); }
``` ```
@ -86,6 +85,7 @@ pointer is valid.
Now, let's wrap this all up inside the `Drop` implementation: Now, let's wrap this all up inside the `Drop` implementation:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> Drop for Arc<T> { impl<T> Drop for Arc<T> {
fn drop(&mut self) { fn drop(&mut self) {

@ -22,7 +22,7 @@ same allocation.
Naively, it would look something like this: Naively, it would look something like this:
```rust,ignore ```rust
use std::sync::atomic; use std::sync::atomic;
pub struct Arc<T> { pub struct Arc<T> {
@ -31,7 +31,7 @@ pub struct Arc<T> {
pub struct ArcInner<T> { pub struct ArcInner<T> {
rc: atomic::AtomicUsize, rc: atomic::AtomicUsize,
data: T data: T,
} }
``` ```
@ -56,18 +56,18 @@ ownership of a value of `ArcInner<T>` (which itself contains some `T`).
With these changes we get our final structure: With these changes we get our final structure:
```rust,ignore ```rust
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ptr::NonNull; use std::ptr::NonNull;
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
pub struct Arc<T> { pub struct Arc<T> {
ptr: NonNull<ArcInner<T>>, ptr: NonNull<ArcInner<T>>,
phantom: PhantomData<ArcInner<T>> phantom: PhantomData<ArcInner<T>>,
} }
pub struct ArcInner<T> { pub struct ArcInner<T> {
rc: AtomicUsize, rc: AtomicUsize,
data: T data: T,
} }
``` ```

@ -27,16 +27,18 @@ exactly what we said but, you know, fast. Wouldn't that be great?
Compilers fundamentally want to be able to do all sorts of complicated Compilers fundamentally want to be able to do all sorts of complicated
transformations to reduce data dependencies and eliminate dead code. In transformations to reduce data dependencies and eliminate dead code. In
particular, they may radically change the actual order of events, or make events particular, they may radically change the actual order of events, or make events
never occur! If we write something like never occur! If we write something like:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
x = 1; x = 1;
y = 3; y = 3;
x = 2; x = 2;
``` ```
The compiler may conclude that it would be best if your program did The compiler may conclude that it would be best if your program did:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
x = 2; x = 2;
y = 3; y = 3;

@ -3,6 +3,7 @@
What the language *does* provide is full-blown automatic destructors through the What the language *does* provide is full-blown automatic destructors through the
`Drop` trait, which provides the following method: `Drop` trait, which provides the following method:
<!-- ignore: function header -->
```rust,ignore ```rust,ignore
fn drop(&mut self); fn drop(&mut self);
``` ```

@ -8,11 +8,15 @@ when we talked about `'a: 'b`, it was ok for `'a` to live _exactly_ as long as
gets dropped at the same time as another, right? This is why we used the gets dropped at the same time as another, right? This is why we used the
following desugaring of `let` statements: following desugaring of `let` statements:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let x; let x;
let y; let y;
``` ```
desugaring to:
<!-- ignore: desugared code -->
```rust,ignore ```rust,ignore
{ {
let x; let x;
@ -29,6 +33,7 @@ definition. There are some more details about order of drop in [RFC 1857][rfc185
Let's do this: Let's do this:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let tuple = (vec![], vec![]); let tuple = (vec![], vec![]);
``` ```
@ -259,7 +264,8 @@ lifetime `'b` and that the only uses of `T` will be moves or drops, but omit
the attribute from `'a` and `U`, because we do access data with that lifetime the attribute from `'a` and `U`, because we do access data with that lifetime
and that type: and that type:
```rust,ignore ```rust
#![feature(dropck_eyepatch)]
use std::fmt::Display; use std::fmt::Display;
struct Inspector<'a, 'b, T, U: Display>(&'a u8, &'b u8, T, U); struct Inspector<'a, 'b, T, U: Display>(&'a u8, &'b u8, T, U);
@ -283,7 +289,7 @@ other avenues for such indirect access.)
Here is an example of invoking a callback: Here is an example of invoking a callback:
```rust,ignore ```rust
struct Inspector<T>(T, &'static str, Box<for <'r> fn(&'r T) -> String>); struct Inspector<T>(T, &'static str, Box<for <'r> fn(&'r T) -> String>);
impl<T> Drop for Inspector<T> { impl<T> Drop for Inspector<T> {
@ -297,7 +303,7 @@ impl<T> Drop for Inspector<T> {
Here is an example of a trait method call: Here is an example of a trait method call:
```rust,ignore ```rust
use std::fmt; use std::fmt;
struct Inspector<T: fmt::Display>(T, &'static str); struct Inspector<T: fmt::Display>(T, &'static str);

@ -35,6 +35,7 @@ needs to be careful and consider exception safety.
`Vec::push_all` is a temporary hack to get extending a Vec by a slice reliably `Vec::push_all` is a temporary hack to get extending a Vec by a slice reliably
efficient without specialization. Here's a simple implementation: efficient without specialization. Here's a simple implementation:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T: Clone> Vec<T> { impl<T: Clone> Vec<T> {
fn push_all(&mut self, to_push: &[T]) { fn push_all(&mut self, to_push: &[T]) {
@ -75,7 +76,6 @@ bubble_up(heap, index):
while index != 0 && heap[index] < heap[parent(index)]: while index != 0 && heap[index] < heap[parent(index)]:
heap.swap(index, parent(index)) heap.swap(index, parent(index))
index = parent(index) index = parent(index)
``` ```
A literal transcription of this code to Rust is totally fine, but has an annoying A literal transcription of this code to Rust is totally fine, but has an annoying
@ -147,6 +147,7 @@ way to do this is to store the algorithm's state in a separate struct with a
destructor for the "finally" logic. Whether we panic or not, that destructor destructor for the "finally" logic. Whether we panic or not, that destructor
will run and clean up after us. will run and clean up after us.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
struct Hole<'a, T: 'a> { struct Hole<'a, T: 'a> {
data: &'a mut [T], data: &'a mut [T],

@ -139,7 +139,7 @@ other is still UB).
The following *could* also compile: The following *could* also compile:
```rust,ignore ```rust,compile_fail
enum Void {} enum Void {}
let res: Result<u32, Void> = Ok(0); let res: Result<u32, Void> = Ok(0);

@ -28,6 +28,7 @@ and add `extern crate libc;` to your crate root.
The following is a minimal example of calling a foreign function which will The following is a minimal example of calling a foreign function which will
compile if snappy is installed: compile if snappy is installed:
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
extern crate libc; extern crate libc;
use libc::size_t; use libc::size_t;
@ -61,6 +62,7 @@ of keeping the binding correct at runtime.
The `extern` block can be extended to cover the entire snappy API: The `extern` block can be extended to cover the entire snappy API:
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
extern crate libc; extern crate libc;
use libc::{c_int, size_t}; use libc::{c_int, size_t};
@ -96,6 +98,7 @@ vectors as pointers to memory. Rust's vectors are guaranteed to be a contiguous
length is the number of elements currently contained, and the capacity is the total size in elements of length is the number of elements currently contained, and the capacity is the total size in elements of
the allocated memory. The length is less than or equal to the capacity. the allocated memory. The length is less than or equal to the capacity.
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
# extern crate libc; # extern crate libc;
# use libc::{c_int, size_t}; # use libc::{c_int, size_t};
@ -120,6 +123,7 @@ required capacity to hold the compressed output. The vector can then be passed t
`snappy_compress` function as an output parameter. An output parameter is also passed to retrieve `snappy_compress` function as an output parameter. An output parameter is also passed to retrieve
the true length after compression for setting the length. the true length after compression for setting the length.
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
# extern crate libc; # extern crate libc;
# use libc::{size_t, c_int}; # use libc::{size_t, c_int};
@ -146,6 +150,7 @@ pub fn compress(src: &[u8]) -> Vec<u8> {
Decompression is similar, because snappy stores the uncompressed size as part of the compression Decompression is similar, because snappy stores the uncompressed size as part of the compression
format and `snappy_uncompressed_length` will retrieve the exact buffer size required. format and `snappy_uncompressed_length` will retrieve the exact buffer size required.
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
# extern crate libc; # extern crate libc;
# use libc::{size_t, c_int}; # use libc::{size_t, c_int};
@ -180,6 +185,7 @@ pub fn uncompress(src: &[u8]) -> Option<Vec<u8>> {
Then, we can add some tests to show how to use them. Then, we can add some tests to show how to use them.
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
# extern crate libc; # extern crate libc;
# use libc::{c_int, size_t}; # use libc::{c_int, size_t};
@ -452,6 +458,7 @@ Foreign APIs often export a global variable which could do something like track
global state. In order to access these variables, you declare them in `extern` global state. In order to access these variables, you declare them in `extern`
blocks with the `static` keyword: blocks with the `static` keyword:
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
extern crate libc; extern crate libc;
@ -470,6 +477,7 @@ Alternatively, you may need to alter global state provided by a foreign
interface. To do this, statics can be declared with `mut` so we can mutate interface. To do this, statics can be declared with `mut` so we can mutate
them. them.
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
extern crate libc; extern crate libc;
@ -502,6 +510,7 @@ Most foreign code exposes a C ABI, and Rust uses the platform's C calling conven
calling foreign functions. Some foreign functions, most notably the Windows API, use other calling calling foreign functions. Some foreign functions, most notably the Windows API, use other calling
conventions. Rust provides a way to tell the compiler which convention to use: conventions. Rust provides a way to tell the compiler which convention to use:
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
extern crate libc; extern crate libc;
@ -582,10 +591,10 @@ fn main() {
Normal Rust functions can *not* be variadic: Normal Rust functions can *not* be variadic:
```ignore ```rust,compile_fail
// This will not compile // This will not compile
fn foo(x: i32, ...) { } fn foo(x: i32, ...) {}
``` ```
## The "nullable pointer optimization" ## The "nullable pointer optimization"
@ -613,6 +622,7 @@ callback, which gets called in certain situations. The callback is passed a func
and an integer and it is supposed to run the function with the integer as a parameter. So and an integer and it is supposed to run the function with the integer as a parameter. So
we have function pointers flying across the FFI boundary in both directions. we have function pointers flying across the FFI boundary in both directions.
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
extern crate libc; extern crate libc;
use libc::c_int; use libc::c_int;
@ -712,6 +722,7 @@ void bar(void *arg);
We can represent this in Rust with the `c_void` type: We can represent this in Rust with the `c_void` type:
<!-- ignore: requires libc crate -->
```rust,ignore ```rust,ignore
extern crate libc; extern crate libc;

@ -28,6 +28,7 @@ fn main() {
If we try to naively desugar this code in the same way that we did in the If we try to naively desugar this code in the same way that we did in the
lifetimes section, we run into some trouble: lifetimes section, we run into some trouble:
<!-- ignore: desugared code -->
```rust,ignore ```rust,ignore
struct Closure<F> { struct Closure<F> {
data: (u8, u16), data: (u8, u16),
@ -60,6 +61,7 @@ named until we enter the body of `call`! Also, that isn't some fixed lifetime;
This job requires The Magic of Higher-Rank Trait Bounds (HRTBs). The way we This job requires The Magic of Higher-Rank Trait Bounds (HRTBs). The way we
desugar this is as follows: desugar this is as follows:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8, where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,
``` ```

@ -69,6 +69,7 @@ unwinding-safe! Easy!
Now consider the following: Now consider the following:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let mut vec = vec![Box::new(0); 4]; let mut vec = vec![Box::new(0); 4];
@ -116,6 +117,7 @@ Nope.
Let's consider a simplified implementation of Rc: Let's consider a simplified implementation of Rc:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
struct Rc<T> { struct Rc<T> {
ptr: *mut RcBox<T>, ptr: *mut RcBox<T>,
@ -179,6 +181,7 @@ data on their parent's stack without any synchronization over that data by
ensuring the parent joins the thread before any of the shared data goes out ensuring the parent joins the thread before any of the shared data goes out
of scope. of scope.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub fn scoped<'a, F>(f: F) -> JoinGuard<'a> pub fn scoped<'a, F>(f: F) -> JoinGuard<'a>
where F: FnOnce() + Send + 'a where F: FnOnce() + Send + 'a
@ -196,6 +199,7 @@ of the closed-over data goes out of scope in the parent.
Usage looked like: Usage looked like:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let mut data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let mut data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
{ {
@ -224,6 +228,7 @@ let mut data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
In principle, this totally works! Rust's ownership system perfectly ensures it! In principle, this totally works! Rust's ownership system perfectly ensures it!
...except it relies on a destructor being called to be safe. ...except it relies on a destructor being called to be safe.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let mut data = Box::new(0); let mut data = Box::new(0);
{ {

@ -5,6 +5,7 @@ In order to make common patterns more ergonomic, Rust allows lifetimes to be
A *lifetime position* is anywhere you can write a lifetime in a type: A *lifetime position* is anywhere you can write a lifetime in a type:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
&'a T &'a T
&'a mut T &'a mut T
@ -38,6 +39,7 @@ Elision rules are as follows:
Examples: Examples:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn print(s: &str); // elided fn print(s: &str); // elided
fn print<'a>(s: &'a str); // expanded fn print<'a>(s: &'a str); // expanded
@ -60,5 +62,4 @@ fn args<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command // ex
fn new(buf: &mut [u8]) -> BufWriter; // elided fn new(buf: &mut [u8]) -> BufWriter; // elided
fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a> // expanded fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a> // expanded
``` ```

@ -40,6 +40,7 @@ What happened? Well, we got the exact same reasoning as we did for
[Example 2 in the previous section][ex2]. We desugar the program and we get [Example 2 in the previous section][ex2]. We desugar the program and we get
the following: the following:
<!-- ignore: desugared code -->
```rust,ignore ```rust,ignore
struct Foo; struct Foo;

@ -45,6 +45,7 @@ let z = &y;
The borrow checker always tries to minimize the extent of a lifetime, so it will The borrow checker always tries to minimize the extent of a lifetime, so it will
likely desugar to the following: likely desugar to the following:
<!-- ignore: desugared code -->
```rust,ignore ```rust,ignore
// NOTE: `'a: {` and `&'b x` is not valid syntax! // NOTE: `'a: {` and `&'b x` is not valid syntax!
'a: { 'a: {
@ -72,6 +73,7 @@ let y = &x;
z = y; z = y;
``` ```
<!-- ignore: desugared code -->
```rust,ignore ```rust,ignore
'a: { 'a: {
let x: i32 = 0; let x: i32 = 0;
@ -100,6 +102,7 @@ fn as_str(data: &u32) -> &str {
desugars to: desugars to:
<!-- ignore: desugared code -->
```rust,ignore ```rust,ignore
fn as_str<'a>(data: &'a u32) -> &'a str { fn as_str<'a>(data: &'a u32) -> &'a str {
'b: { 'b: {
@ -127,6 +130,7 @@ up in our face.
To make this more clear, we can expand the example: To make this more clear, we can expand the example:
<!-- ignore: desugared code -->
```rust,ignore ```rust,ignore
fn as_str<'a>(data: &'a u32) -> &'a str { fn as_str<'a>(data: &'a u32) -> &'a str {
'b: { 'b: {
@ -178,6 +182,7 @@ data.push(4);
println!("{}", x); println!("{}", x);
``` ```
<!-- ignore: desugared code -->
```rust,ignore ```rust,ignore
'a: { 'a: {
let mut data: Vec<i32> = vec![1, 2, 3]; let mut data: Vec<i32> = vec![1, 2, 3];

@ -17,7 +17,7 @@ issue...). This is a pervasive problem that C and C++ programs need to deal
with. Consider this simple mistake that all of us who have used a non-GC'd with. Consider this simple mistake that all of us who have used a non-GC'd
language have made at one point: language have made at one point:
```rust,ignore ```rust,compile_fail
fn as_str(data: &u32) -> &str { fn as_str(data: &u32) -> &str {
// compute the string // compute the string
let s = format!("{}", data); let s = format!("{}", data);
@ -46,7 +46,7 @@ verifying that references don't escape the scope of their referent. That's
because ensuring pointers are always valid is much more complicated than this. because ensuring pointers are always valid is much more complicated than this.
For instance in this code, For instance in this code,
```rust,ignore ```rust,compile_fail
let mut data = vec![1, 2, 3]; let mut data = vec![1, 2, 3];
// get an internal reference // get an internal reference
let x = &data[0]; let x = &data[0];

@ -17,9 +17,10 @@ Below is shown an example where an application has a different panicking behavio
whether is compiled using the dev profile (`cargo build`) or using the release profile (`cargo build whether is compiled using the dev profile (`cargo build`) or using the release profile (`cargo build
--release`). --release`).
``` rust, ignore `panic-semihosting` crate -- log panic messages to the host stderr using semihosting:
// crate: panic-semihosting -- log panic messages to the host stderr using semihosting
<!-- ignore: simplified code -->
```rust,ignore
#![no_std] #![no_std]
use core::fmt::{Write, self}; use core::fmt::{Write, self};
@ -49,8 +50,10 @@ fn panic(info: &PanicInfo) -> ! {
} }
``` ```
``` rust, ignore `panic-halt` crate -- halt the thread on panic; messages are discarded:
// crate: panic-halt -- halt the thread on panic; messages are discarded
<!-- ignore: simplified code -->
```rust,ignore
#![no_std] #![no_std]
@ -62,8 +65,10 @@ fn panic(_info: &PanicInfo) -> ! {
} }
``` ```
``` rust, ignore `app` crate:
// crate: app
<!-- ignore: requires external crate -->
```rust,ignore
#![no_std] #![no_std]

@ -104,6 +104,7 @@ 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 values in the struct to satisfy their alignment requirements. So if Rust
didn't reorder fields, we would expect it to produce the following: didn't reorder fields, we would expect it to produce the following:
<!-- ignore: explanation code -->
```rust,ignore ```rust,ignore
struct Foo<u16, u32> { struct Foo<u16, u32> {
count: u16, count: u16,

@ -34,6 +34,7 @@ But unlike normal traits, we can use them as concrete and sized types, just like
Now, say we have a very simple function that takes an Animal, like this: Now, say we have a very simple function that takes an Animal, like this:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn love(pet: Animal) { fn love(pet: Animal) {
pet.snuggle(); pet.snuggle();
@ -43,6 +44,7 @@ fn love(pet: Animal) {
By default, static types must match *exactly* for a program to compile. As such, By default, static types must match *exactly* for a program to compile. As such,
this code won't compile: this code won't compile:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let mr_snuggles: Cat = ...; let mr_snuggles: Cat = ...;
love(mr_snuggles); // ERROR: expected Animal, found Cat love(mr_snuggles); // ERROR: expected Animal, found Cat
@ -78,6 +80,7 @@ of our static type system, making it worse than useless (and leading to Undefine
Here's a simple example of this happening when we apply subtyping in a completely naive Here's a simple example of this happening when we apply subtyping in a completely naive
"find and replace" way. "find and replace" way.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn evil_feeder(pet: &mut Animal) { fn evil_feeder(pet: &mut Animal) {
let spike: Dog = ...; let spike: Dog = ...;
@ -199,6 +202,7 @@ and look at some examples.
First off, let's revisit the meowing dog example: First off, let's revisit the meowing dog example:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn evil_feeder(pet: &mut Animal) { fn evil_feeder(pet: &mut Animal) {
let spike: Dog = ...; let spike: Dog = ...;
@ -344,6 +348,7 @@ are guaranteed to be the only one with access to it.
Consider the following code: Consider the following code:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
let mr_snuggles: Box<Cat> = ..; let mr_snuggles: Box<Cat> = ..;
let spike: Box<Dog> = ..; let spike: Box<Dog> = ..;
@ -369,6 +374,7 @@ Only one thing left to explain: function pointers.
To see why `fn(T) -> U` should be covariant over `U`, consider the following signature: To see why `fn(T) -> U` should be covariant over `U`, consider the following signature:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn get_animal() -> Animal; fn get_animal() -> Animal;
``` ```
@ -376,6 +382,7 @@ fn get_animal() -> Animal;
This function claims to produce an Animal. As such, it is perfectly valid to This function claims to produce an Animal. As such, it is perfectly valid to
provide a function with the following signature instead: provide a function with the following signature instead:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn get_animal() -> Cat; fn get_animal() -> Cat;
``` ```
@ -388,12 +395,14 @@ just forget that fact.
However, the same logic does not apply to *arguments*. Consider trying to satisfy: However, the same logic does not apply to *arguments*. Consider trying to satisfy:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn handle_animal(Animal); fn handle_animal(Animal);
``` ```
with with:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn handle_animal(Cat); fn handle_animal(Cat);
``` ```

@ -17,6 +17,7 @@ boundaries.
Given a function, any output lifetimes that don't derive from inputs are Given a function, any output lifetimes that don't derive from inputs are
unbounded. For instance: unbounded. For instance:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn get_str<'a>() -> &'a str; fn get_str<'a>() -> &'a str;
``` ```

@ -82,6 +82,7 @@ It's worth spending a bit more time on the loop in the middle, and in particular
the assignment operator and its interaction with `drop`. If we would have the assignment operator and its interaction with `drop`. If we would have
written something like: written something like:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
*x[i].as_mut_ptr() = Box::new(i as u32); // WRONG! *x[i].as_mut_ptr() = Box::new(i as u32); // WRONG!
``` ```

@ -17,6 +17,7 @@ want to use `dangling` because there's no real allocation to talk about but
So: So:
<!-- ignore: explanation code -->
```rust,ignore ```rust,ignore
use std::mem; use std::mem;
@ -83,6 +84,7 @@ compiler to be able to reason about data dependencies and aliasing.
As a simple example, consider the following fragment of code: As a simple example, consider the following fragment of code:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
*x *= 7; *x *= 7;
*y *= 3; *y *= 3;
@ -162,6 +164,7 @@ such we will guard against this case explicitly.
Ok with all the nonsense out of the way, let's actually allocate some memory: Ok with all the nonsense out of the way, let's actually allocate some memory:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
use std::alloc::{self, Layout}; use std::alloc::{self, Layout};

@ -10,6 +10,7 @@ wouldn't bother unless you notice it's not being stripped (in this case it is).
We must not call `alloc::dealloc` when `self.cap == 0`, as in this case we We must not call `alloc::dealloc` when `self.cap == 0`, as in this case we
haven't actually allocated any memory. haven't actually allocated any memory.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> Drop for Vec<T> { impl<T> Drop for Vec<T> {
fn drop(&mut self) { fn drop(&mut self) {

@ -11,6 +11,7 @@ All we need is `slice::from_raw_parts`. It will correctly handle empty slices
for us. Later once we set up zero-sized type support it will also Just Work for us. Later once we set up zero-sized type support it will also Just Work
for those too. for those too.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
use std::ops::Deref; use std::ops::Deref;
@ -26,6 +27,7 @@ impl<T> Deref for Vec<T> {
And let's do DerefMut too: And let's do DerefMut too:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
use std::ops::DerefMut; use std::ops::DerefMut;

@ -4,6 +4,7 @@ Let's move on to Drain. Drain is largely the same as IntoIter, except that
instead of consuming the Vec, it borrows the Vec and leaves its allocation instead of consuming the Vec, it borrows the Vec and leaves its allocation
untouched. For now we'll only implement the "basic" full-range version. untouched. For now we'll only implement the "basic" full-range version.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
use std::marker::PhantomData; use std::marker::PhantomData;
@ -26,6 +27,7 @@ impl<'a, T> Iterator for Drain<'a, T> {
-- wait, this is seeming familiar. Let's do some more compression. Both -- wait, this is seeming familiar. Let's do some more compression. Both
IntoIter and Drain have the exact same structure, let's just factor it out. IntoIter and Drain have the exact same structure, let's just factor it out.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
struct RawValIter<T> { struct RawValIter<T> {
start: *const T, start: *const T,
@ -57,6 +59,7 @@ impl<T> RawValIter<T> {
And IntoIter becomes the following: And IntoIter becomes the following:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub struct IntoIter<T> { pub struct IntoIter<T> {
_buf: RawVec<T>, // we don't actually care about this. Just need it to live. _buf: RawVec<T>, // we don't actually care about this. Just need it to live.
@ -103,6 +106,7 @@ We also take a slice to simplify Drain initialization.
Alright, now Drain is really easy: Alright, now Drain is really easy:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
use std::marker::PhantomData; use std::marker::PhantomData;

@ -12,6 +12,7 @@ definitely happen here).
If we insert at index `i`, we want to shift the `[i .. len]` to `[i+1 .. len+1]` If we insert at index `i`, we want to shift the `[i .. len]` to `[i+1 .. len+1]`
using the old len. using the old len.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub fn insert(&mut self, index: usize, elem: T) { pub fn insert(&mut self, index: usize, elem: T) {
// Note: `<=` because it's valid to insert after everything // Note: `<=` because it's valid to insert after everything
@ -33,6 +34,7 @@ pub fn insert(&mut self, index: usize, elem: T) {
Remove behaves in the opposite manner. We need to shift all the elements from Remove behaves in the opposite manner. We need to shift all the elements from
`[i+1 .. len + 1]` to `[i .. len]` using the *new* len. `[i+1 .. len + 1]` to `[i .. len]` using the *new* len.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub fn remove(&mut self, index: usize) -> T { pub fn remove(&mut self, index: usize) -> T {
// Note: `<` because it's *not* valid to remove after everything // Note: `<` because it's *not* valid to remove after everything

@ -42,6 +42,7 @@ dropped.
So we're going to use the following struct: So we're going to use the following struct:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub struct IntoIter<T> { pub struct IntoIter<T> {
buf: NonNull<T>, buf: NonNull<T>,
@ -54,6 +55,7 @@ pub struct IntoIter<T> {
And this is what we end up with for initialization: And this is what we end up with for initialization:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> Vec<T> { impl<T> Vec<T> {
pub fn into_iter(self) -> IntoIter<T> { pub fn into_iter(self) -> IntoIter<T> {
@ -85,6 +87,7 @@ impl<T> Vec<T> {
Here's iterating forward: Here's iterating forward:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> Iterator for IntoIter<T> { impl<T> Iterator for IntoIter<T> {
type Item = T; type Item = T;
@ -110,6 +113,7 @@ impl<T> Iterator for IntoIter<T> {
And here's iterating backwards. And here's iterating backwards.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> DoubleEndedIterator for IntoIter<T> { impl<T> DoubleEndedIterator for IntoIter<T> {
fn next_back(&mut self) -> Option<T> { fn next_back(&mut self) -> Option<T> {
@ -129,6 +133,7 @@ Because IntoIter takes ownership of its allocation, it needs to implement Drop
to free it. However it also wants to implement Drop to drop any elements it to free it. However it also wants to implement Drop to drop any elements it
contains that weren't yielded. contains that weren't yielded.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> Drop for IntoIter<T> { impl<T> Drop for IntoIter<T> {
fn drop(&mut self) { fn drop(&mut self) {

@ -6,6 +6,7 @@ elements that have been initialized.
Naively, this means we just want this design: Naively, this means we just want this design:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub struct Vec<T> { pub struct Vec<T> {
ptr: *mut T, ptr: *mut T,

@ -17,6 +17,7 @@ target address with the bits of the value we provide. No evaluation involved.
For `push`, if the old len (before push was called) is 0, then we want to write For `push`, if the old len (before push was called) is 0, then we want to write
to the 0th index. So we should offset by the old len. to the 0th index. So we should offset by the old len.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub fn push(&mut self, elem: T) { pub fn push(&mut self, elem: T) {
if self.len == self.cap { self.grow(); } if self.len == self.cap { self.grow(); }
@ -41,6 +42,7 @@ of T there.
For `pop`, if the old len is 1, we want to read out of the 0th index. So we For `pop`, if the old len is 1, we want to read out of the 0th index. So we
should offset by the new len. should offset by the new len.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub fn pop(&mut self) -> Option<T> { pub fn pop(&mut self) -> Option<T> {
if self.len == 0 { if self.len == 0 {

@ -8,6 +8,7 @@ time to perform some logic compression.
We're going to abstract out the `(ptr, cap)` pair and give them the logic for We're going to abstract out the `(ptr, cap)` pair and give them the logic for
allocating, growing, and freeing: allocating, growing, and freeing:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
struct RawVec<T> { struct RawVec<T> {
ptr: NonNull<T>, ptr: NonNull<T>,
@ -76,6 +77,7 @@ impl<T> Drop for RawVec<T> {
And change Vec as follows: And change Vec as follows:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub struct Vec<T> { pub struct Vec<T> {
buf: RawVec<T>, buf: RawVec<T>,
@ -114,6 +116,7 @@ impl<T> Drop for Vec<T> {
And finally we can really simplify IntoIter: And finally we can really simplify IntoIter:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
pub struct IntoIter<T> { pub struct IntoIter<T> {
_buf: RawVec<T>, // we don't actually care about this. Just need it to live. _buf: RawVec<T>, // we don't actually care about this. Just need it to live.

@ -29,6 +29,7 @@ overflow for zero-sized types.
Due to our current architecture, all this means is writing 3 guards, one in each Due to our current architecture, all this means is writing 3 guards, one in each
method of `RawVec`. method of `RawVec`.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> RawVec<T> { impl<T> RawVec<T> {
fn new() -> Self { fn new() -> Self {
@ -107,6 +108,7 @@ initialize `start` and `end` as the same value, and our iterators will yield
nothing. The current solution to this is to cast the pointers to integers, nothing. The current solution to this is to cast the pointers to integers,
increment, and then cast them back: increment, and then cast them back:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> RawValIter<T> { impl<T> RawValIter<T> {
unsafe fn new(slice: &[T]) -> Self { unsafe fn new(slice: &[T]) -> Self {
@ -130,6 +132,7 @@ Also, our size_hint computation code will divide by 0 for ZSTs. Since we'll
basically be treating the two pointers as if they point to bytes, we'll just basically be treating the two pointers as if they point to bytes, we'll just
map size 0 to divide by 1. map size 0 to divide by 1.
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
impl<T> Iterator for RawValIter<T> { impl<T> Iterator for RawValIter<T> {
type Item = T; type Item = T;

@ -84,6 +84,7 @@ impl<T> Vec<T> {
This code is simple enough to reasonably audit and informally verify. Now consider This code is simple enough to reasonably audit and informally verify. Now consider
adding the following method: adding the following method:
<!-- ignore: simplified code -->
```rust,ignore ```rust,ignore
fn make_room(&mut self) { fn make_room(&mut self) {
// grow the capacity // grow the capacity

Loading…
Cancel
Save