Skip to content

Commit

Permalink
More proofreading.
Browse files Browse the repository at this point in the history
  • Loading branch information
jwalton committed Apr 24, 2023
1 parent b122be9 commit 6e0289c
Show file tree
Hide file tree
Showing 22 changed files with 493 additions and 189 deletions.
2 changes: 1 addition & 1 deletion docs/ch00-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ hide_title: true

<div align="center">
<h1>The Rust Book (Abridged)</h1>
<p>v0.0.2 - Draft</p>
<p>v0.1.0 - Draft</p>
<p>By Jason Walton</p>
<p>Based on <a href="https://github.com/rust-lang/book/commit/c06006157b14b3d47b5c716fc392b77f3b2e21ce">"The Rust Programming Language"</a> by Steve Klabnik and Carol Nichols.</p>
</div>
Expand Down
6 changes: 6 additions & 0 deletions docs/ch09-error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ fn main() {

When a panic occurs in the main thread, it halts the program. If the `RUST_BACKTRACE=1` environment variable is set, then the program will also print a stack trace showing where the panic happened, although this only works if the binary contains debug symbols. (If a panic occurs in another thread, it will only halt that thread. See [chapter 16][chap16].)

:::tip

There's also a `todo!` macro which works just like the `panic!` macro, which can be used to mark places in your code where you have yet to fill in an implementation.

:::

### Unwinding the Stack or Aborting in Response to a Panic

There are two options for what happens when a panic occurs. By default, the program starts _unwinding_, which means it starts walking back up the stack, freeing memory and cleaning up data. The alternative is _aborting_ in which the program just immediately halts and lets the OS clean up everything (if you've ever written a C program, you've probably at some point seen the dreaded message "segmentation fault (core dumped)" - aborting is a bit like this).
Expand Down
2 changes: 1 addition & 1 deletion docs/ch12-io-project-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ use std::{error::Error, fs};
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;

// TODO: Implement me!
todo!("Implement me!");

Ok(())
}
Expand Down
128 changes: 73 additions & 55 deletions docs/ch15-smart-pointers.md

Large diffs are not rendered by default.

42 changes: 21 additions & 21 deletions docs/ch16-fearless-concurrency.md

Large diffs are not rendered by default.

40 changes: 19 additions & 21 deletions docs/ch17-object-oriented-features.md

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions docs/ch18-patterns-and-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ match VALUE {

The patterns in a `match` need to be _exhaustive_ - they need to cover every possibility. The `_` pattern will match anything and not bind to a variable, so it will often be used as a catch-all at the end of a `match`.

In this example, we extract a value from a `Some`. Note that value we extract shadows the outer variable:
In this example, we extract a value from a `Some`. Note that value we extract in this example will shadow the outer variable:

```rust
match i {
Expand Down Expand Up @@ -87,7 +87,7 @@ In a `for` loop, the bit immediately after the `for` keyword is actually a patte

### `let` statements

Simple let statements actually use patterns too:
Simple let statements use patterns too:

```rust
let x = 5;
Expand Down Expand Up @@ -134,7 +134,7 @@ There are some places where we're only allowed to use irrefutable patterns. For
let Some(x) = value;
```

Here if `value` is `Some(1)`, then we expect `x` to get the value 1. But if `value` were `None`, what would `x` be here? This statement makes no sense, and will result in a compiler error, because `let` needs an irrefutable pattern. (Although we could fix this with an `if let` instead.)
Here if `value` is `Some(1)`, then we expect `x` to get the value 1. But if `value` were `None`, what would `x` be here? This statement makes no sense, and will result in a compiler error, because an assignment needs an irrefutable pattern. (Although we could fix this with an `if let` instead.)

There are also places where an irrefutable parameter is allowed, but is somewhat pointless, which will generate compiler warnings, such as this:

Expand Down Expand Up @@ -432,7 +432,7 @@ One downside to match guards is that they generally require the match to have a
match x {
Some(x) if y => println!("{x}"),
Some(x) if !y => println!("{x}"),
None => println!("no"),
None => panic!("Silly compiler"),
}
```

Expand All @@ -450,6 +450,7 @@ enum Message {
let msg = Message::Hello { id: 5 };

match msg {
// Match `id` and bind it to `id_variable`.
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {}", id_variable),
Expand Down
18 changes: 9 additions & 9 deletions docs/ch19/ch19-01-unsafe.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Imagine we have a vector with six elements in it. We could create a mutable slic

_Unsafe_ code in Rust is code where we're allowed to ignore or bypass some of the restrictions Rust places on us, and tell the compiler "Don't worry, I got this." Of course, sometimes we only think we know better than the compiler when in fact what we're actually doing is creating a hard-to-diagnose problem that we won't fund until our code is running in production. So it's not a bad idea to keep unsafe code to a minimum.

But it's important to realize that "unsafe" code isn't "dangerous" code, it's just code that hasn't been inspected by the eagle eye of the Rust compiler. If you could teach the Rust compiler to look at C code, then pretty much all C code would be considered unsafe, but there are plenty of C programs out there doing useful work every day.
But it's important to note that "unsafe" doesn't necessarily mean incorrect, it's just code that hasn't been inspected by the eagle eye of the Rust compiler. There are plenty of C programs in the world performing useful tasks that are correct (or reasonably correct) and C doesn't even have a borrow checker, so all C code is unsafe as far as a Rust programmer is concerned.

We can write code inside an unsafe block or inside an unsafe function:

Expand Down Expand Up @@ -64,7 +64,7 @@ let address = 0x012345usize;
let r = address as *const i32;
```

Note that we're actually allowed to create pointers outside of unsafe code. Creating a pointer never hurt anyone, it's dereferencing a pointer that gets us into trouble, so the dereference is only allowed to happen inside an `unsafe` block.
We're allowed to create pointers outside of unsafe code. Creating a pointer never hurt anyone, it's dereferencing a pointer that gets us into trouble, so the dereference is only allowed to happen inside an `unsafe` block.

Why would you want to use a raw pointer instead of a reference? One case is for calling into C code. Another is when you want to build a "safe" abstraction that the borrow checker won't understand, like our "two mutable slices" example above. We'll see examples of both of these.

Expand Down Expand Up @@ -97,9 +97,9 @@ assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);
```

Here `split_at_mut` is going to call unsafe code, but that doesn't mean that it also has to be unsafe. In fact, the above code works because vector has this method on it already!
`split_at_mut` is going to call unsafe code, but that doesn't mean that it also has to be unsafe. In fact, the above code works because vector has this method on it already!

What we're doing here is creating a "safe abstraction". This is a very common pattern - we hide away the unsafe stuff behind an API that's easy and safe to use. This makes it so we only have to reason about our small API. Here's the implementation:
What `split_at_mut` is doing here is creating a "safe abstraction". This is a very common pattern - we hide away the unsafe stuff behind an API that's easy and safe to use. This makes it so we only have to reason about our small API. Here's the implementation of `split_at_mut`:

```rust
use std::slice;
Expand All @@ -119,7 +119,7 @@ fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
}
```

Here the `slice::from_raw_parts_mut` is unsafe (because it uses a raw pointer to the underlying slice) so we need to call this inside an `unsafe` block.
`slice::from_raw_parts_mut` is unsafe (because it uses a raw pointer to the underlying slice) so we need to call this inside an `unsafe` block.

### Using `extern` Functions to Call External Code

Expand Down Expand Up @@ -164,7 +164,7 @@ fn main() {

Static variables are similar to constants, but we name them in `SCREAMING_SNAKE_CASE`. These variables are always in the `'static` lifetime, and accessing an immutable static variable is considered safe.

Constants can be duplicated in memory, but static variables are always guaranteed to occupy the exact same memory, no matter where they are referenced in code. Unlike constants, static variables can also be `mut`, but accessing or modifying a mutable static variable is always unsafe:
When we use a constant in Rust, the compiler may duplicate the constant in multiple places in memory if they are referenced in multiple places. Static variables, on the other hand, are always guaranteed to occur once in memory, so no matter where they are referenced in code you'll get back the same instance. Unlike constants, static variables can also be `mut`, but accessing or modifying a mutable static variable is always unsafe:

```rust
static mut COUNTER: u32 = 0;
Expand Down Expand Up @@ -204,7 +204,9 @@ unsafe impl Foo for i32 {

## Accessing Fields of a Union

A `union` is like a `struct`, but each field in the union occupies the same memory. Only one of the fields is ever safe to access at a time, depending on what is stored in the union. This example, for instance, will be four bytes long and holds either a `u32` or an `f32`:
Unions are included in Rust mainly for calling into C code that uses them. If you want to access a union, it has to be done from an `unsafe` block.

For the non-C programmers reading this, a `union` is like a `struct`, but each field in the union occupies the same memory. Only one of the fields is ever correct to access at a time, depending on what is stored in the union. This example, for instance, will be four bytes long and holds either a `u32` or an `f32`:

```rust
#[repr(C)]
Expand All @@ -215,5 +217,3 @@ union MyUnion {
```

Rust has no idea what's stored in this union, and you'll get back a `u32` or an `f32` depending on which one you access, but odds are only one of them contains a meaningful value. You can learn more about unions in [the Rust Reference](https://doc.rust-lang.org/stable/reference/items/unions.html).

Unions are included in Rust mainly for calling into C code that uses them. If you want to access a union, it has to be done from an `unsafe` block.
54 changes: 36 additions & 18 deletions docs/ch19/ch19-02-advanced-traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub trait GenericIterator<T> {
}
```

Well, actually, we _can_ do this. You can have generic traits, but there's an important difference: a trait with an associated type can only be implemented for a given struct once, but a trait with a generic type could be implemented for a given struct multiple times for different generic types.
Well, actually, we _can_ do this. You can have generic traits, but there's an important difference: a trait with an associated type can only be implemented for a given type once, but a trait with a generic type could be implemented for a given type multiple times for different generic types.

This means, practically speaking, that if someone implemented `GenericIterator` then whenever we called `next`, we'd have to explicitly annotate the type of the return value so we'd know which version of `next` to call.

Expand Down Expand Up @@ -85,6 +85,23 @@ This isn't a problem for associated types, because we know there can only ever b

## Default Generic Type Parameters and Operator Overloading

When we have a generic type, we can specify a _default type parameter_ that will be used if no generic type is specified:

```rust
struct Point<T = i32> {
x: T,
y: T,
}

// Don't need to specify `Point<i32>` here.
fn foo(p: Point) {
println!("{}, {}", p.x, p.y)
}

```

Generally there are two cases where a default type parameter is useful. You can use it to make a non-generic type generic without breaking existing uses, and you can allow customization in places where most users won't need it.

_Operator overloading_ lets you define custom behavior for certain operators. For example, we all understand what happens when we apply the `+` operator to two `i32`s. But, what if we want to add two `Point`s together?

```rust
Expand Down Expand Up @@ -119,7 +136,7 @@ impl Add for Point {
}
```

The [`std:ops` section of the standard library](https://doc.rust-lang.org/std/ops/index.html) describes what operators you can overload this way. If we have a look at the `Add` trait, it has an `Output` associated item, but the `Add` trait is also generic, and lets us specify the `Rhs` or "right-hand-side":
The [`std:ops` section of the standard library](https://doc.rust-lang.org/std/ops/index.html) describes what operators you can overload this way. If we have a look at the `Add` trait, it has an `Output` associated type, but the `Add` trait is also generic, and lets us specify the `Rhs` or "right-hand-side":

```rust
trait Add<Rhs=Self> {
Expand All @@ -129,9 +146,9 @@ trait Add<Rhs=Self> {
}
```

We didn't specify an `Rhs` above though when we wrote `impl Add for Point`. That's because the `Add` trait uses a _default type parameter_ for `Rhs`. Since we didn't specify one, it defaulted to `Self` (which in our case is another `Point`.
Again, this is an example of a generic with a default type parameter. We didn't specify an `Rhs` above so it defaults to `Self` (or in this case `Point`). Generally when you want to add a thing to another thing, they're going to be of the same type, so here the default saves us some typing.

This generic parameter lets us specify what happens when you try to add together two different types. Here's an example where we defined a `Millimeters` and `Meters` type, and specify how to add meters to millimeters:
But having the `Rhs` be a generic type means we can also implement `Add` for cases where we're adding together two different types. Here's an example where we define a `Millimeters` and `Meters` type, and specify how to add meters to millimeters:

```rust
use std::ops::Add;
Expand All @@ -148,13 +165,9 @@ impl Add<Meters> for Millimeters {
}
```

There are two cases where a default type parameter is useful. You can use it to make a non-generic type generic without breaking existing uses, and you can allow customization in places where most users won't need it.

## Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name

If you're like me, the first time you saw that `impl TRAIT for TYPE` syntax, you realized you could have two different traits that each defined a function called `foo`, and then you could create a type that implemented both traits.

You absolutely can do this. In fact, you can also have a trait that defines a method named `foo` that differs from a method defined on the struct outside any trait also called `foo`. The `Human` struct in this next example has three different methods called `fly`:
The first time you saw that `impl TRAIT for TYPE` syntax, you probably realized you could have two different traits that each defined a function called `foo`, and then you could create a type that implemented both traits. In fact, you can also have a trait that defines a method named `foo` that differs from a method defined on the struct outside any trait also called `foo`. The `Human` struct in this next example has three different methods called `fly`:

```rust
trait Pilot {
Expand Down Expand Up @@ -204,9 +217,9 @@ fn main() {
}
```

When we define these methods, we always have that `self` parameter for the receiver. I like to think of this syntax as calling this like an associated function and explicitly passing in `self`.
When we call these methods explicitly like this, we have to pass in the `self` parameter, as if we were calling these like an associated function. (We've already seen an example of this syntax when we called `Rc::clone`, although we didn't know it at the time!)

Although, this brings up an interesting point. One thing we haven't done yet is to define an associated function on a trait:
Although, this brings up an interesting point; if we can call a method on a trait using the associated function syntax, can we define an associated function on a trait?

```rust
trait Animal {
Expand All @@ -226,7 +239,7 @@ fn main() {
}
```

But what happens here if `Dog` has an associated function also called `baby_name`?
But what happens here if `Dog` also has an associated function also called `baby_name`?

```rust
impl Dog {
Expand All @@ -252,9 +265,9 @@ You could use this same syntax in our `Human` example above:
<Human as Wizard>::fly(&person);
```

The general syntax is `<Type as Trait>::function(receiver_if_method, next_arg, ...)`, but you can omit any part of this that Rust can work out on it's own.
These are actually all different examples of the same thing. The general syntax is `<Type as Trait>::function(receiver_if_method, next_arg, ...)`, but you can omit any part of this that Rust can work out on it's own.

## Using Supertraits to Require One Traits Functionality Within Another Trait
## Using Supertraits to Require One Trait's Functionality Within Another Trait

Let's say we want to define a trait called `OutlinePrint`. Any type that implements `OutlinePrint` will have a method called `outline_print` that will print the value with a box made of `*`s around it:

Expand All @@ -266,7 +279,7 @@ Let's say we want to define a trait called `OutlinePrint`. Any type that impleme
**********
```

We can provide a default implementation of `outline_print`, but in order to do so we'd have to call into `self.to_string()`, which means that `self` has to implement `fmt:Display`.
We can provide a default implementation of `outline_print`, but in order to do so we'd have to call into `self.fmt()`, which means that `self` has to implement `fmt:Display`.

We can write this trait like this:

Expand All @@ -286,7 +299,7 @@ trait OutlinePrint: fmt::Display {
}
```

We say here that `fmt::Display` is a _supertrait_ of `OutlinePrint`. This is kind of like adding a trait bounds to `OutlinePrint` - saying that in order to implement OutlinePrint, your type also has to implement `fmt::Display`.
We say here that `fmt::Display` is a _supertrait_ of `OutlinePrint`. This is kind of like adding a trait bounds to `OutlinePrint` - saying that in order to implement OutlinePrint, your type also has to implement `fmt::Display`. It's also kind of like saying that `OutlinePrint` inherits from `fmt:Display` which is why we call it a supertrait (although you can't define `fmt` in the `impl` block for `OutlinePrint`, so it's not quite like OO style inheritance).

We can implement this on a `Point`:

Expand All @@ -304,20 +317,25 @@ impl fmt::Display for Point {
}
}

// No need to implement the outline_print method as we get
// the default definition, which automatically calls into
// `fmt` above.
impl OutlinePrint for Point {}
```

## Using the Newtype Pattern to Implement External Traits on External Types

Back in [chapter 10](../ch10/ch10-02-traits.md#implementing-a-trait-on-a-type), we mentioned the "orphan rule". If you want to implement a trait on a type, then either the trait or the type (or both) need to be defined locally in your crate.

It's possible to get around this using the _newtype_ pattern. The basic idea is to create a tuple "wrapper" around the existing type. Let's suppose we want to implement `Display` on `Vec<T>`. These are both from the standard library, so normally we couldn't do this. We'll use the newtype pattern here:
It's possible to get around this using the _newtype_ pattern (borrowed from Haskell). The basic idea is to create a tuple "wrapper" around the existing type. Let's suppose we want to implement `Display` on `Vec<T>`. These are both from the standard library, so normally we couldn't do this. We'll use the newtype pattern here:

```rust title="src/main.rs"
use std::fmt;

// Create a newtype wrapper around `Vec<String>`.
struct Wrapper(Vec<String>);

// Implement `Display` trait on the wrapper.
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
Expand All @@ -330,4 +348,4 @@ fn main() {
}
```

The disadvantage to this approach is that we have a new type `Wrapper` here, and we can't just treat `w` like we could a regular vector. Most of the methods we want to call on `Vec` aren't defined on Wrapper. We could redefine just the methods we want to call on `Wrapper`. We could also implement the `Deref` trait so we can treat a `w` like vector.
The disadvantage to this approach is that we have a new type `Wrapper` here, and we can't just treat `w` like we could a regular vector. Most of the methods we want to call on `Vec` aren't defined on Wrapper. We could redefine just the methods we want to call on `Wrapper` (which could ve an advantage if we want to present a subset of it's API as our API). We could also implement the `Deref` trait so we can treat a `w` like vector.
Loading

0 comments on commit 6e0289c

Please sign in to comment.