Tech review comments for chapter 15

This commit is contained in:
Carol (Nichols || Goulding)
2022-06-04 20:07:38 -04:00
parent 02d3f7b458
commit f6076c20e2

View File

@@ -149,6 +149,14 @@ assume the list is the result of the function, so is a recursive list of nested
pairs? /LC -->
<!-- There was a definition here, "These pairs containing pairs form a list",
but I guess it wasn't clear enough. Is this better? /Carol -->
<!-- JT, what do you think? I suppose what I wanted was to open with an idea of
what the cons list is and what it's for -- is it right that it's a recursive
list of nested pairs? /LC -->
<!-- I think having the example pretty early on would help readers, though a
cons list is a bit of a niche idea for developers. It's basically the lisp
version of a linked list - maybe we use a linked list as the example? /JT -->
<!-- I added the note about it being the Lisp version of a linked list above, I
like that /Carol -->
For example, here's a pseudocode representation of a cons list containing the
list 1, 2, 3 with each pair in parentheses:
@@ -157,10 +165,6 @@ list 1, 2, 3 with each pair in parentheses:
(1, (2, (3, Nil)))
```
<!-- maybe here also show us a cons list -- not necessarily how to make one,
but the actual list that's the result /LC -->
<!-- Done! /Carol -->
Each item in a cons list contains two elements: the value of the current item
and the next item. The last item in the list contains only a value called `Nil`
without a next item. A cons list is produced by recursively calling the `cons`
@@ -215,8 +219,6 @@ another `Cons` value that holds `2` and another `List` value. This `List` value
is one more `Cons` value that holds `3` and a `List` value, which is finally
`Nil`, the non-recursive variant that signals the end of the list.
<!-- I really like the use of the cons list as the example here /LC -->
If we try to compile the code in Listing 15-3, we get the error shown in
Listing 15-4:
@@ -371,11 +373,6 @@ A regular reference is a type of pointer, and one way to think of a pointer is
as an arrow to a value stored somewhere else. In Listing 15-6, we create a
reference to an `i32` value and then use the dereference operator to follow the
reference to the value:
<!-- is the data different to the value? I wasn't sure if the two things above
were supposed to be doing different things? /LC -->
<!-- I'm not sure what you mean by "two things above", but generally "value"
and "data" are interchangeable. I switched "data" to "value" in this paragraph,
does that fix the issue? /Carol -->
Filename: src/main.rs
@@ -444,14 +441,6 @@ use the dereference operator to follow the boxs pointer in the same way that
we did when `y` was a reference. Next, well explore what is special about
`Box<T>` that enables us to use the dereference operator by defining our own
box type.
<!-- is there a benefit to using this method -- a box rather than a reference
-- or is this just pure example? they seemed to be doing pretty much the same
thing, I wasn't clear why you'd choose one over the other /LC -->
<!-- This example is illustrating that the box and the reference behave the
same in regards to the dereference operator. Differences and why to use `Box`
are discussed before and after this section. I tried to clarify the purpose of
this listing a bit in the paragraph just before the listing, does that help?
/Carol -->
### Defining Our Own Smart Pointer
@@ -598,6 +587,10 @@ value as an argument to a function or method that doesnt match the parameter
type in the function or method definition. A sequence of calls to the `deref`
method converts the type we provided into the type the parameter needs.
<!-- We use both "deref coercion" and "deref conversion" in the above. Might
want to stick with just the first term as that's the more common one. /JT -->
<!-- WHOOPS great catch! Fixed! /Carol -->
Deref coercion was added to Rust so that programmers writing function and
method calls dont need to add as many explicit references and dereferences
with `&` and `*`. The deref coercion feature also lets us write more code that
@@ -688,10 +681,6 @@ implements mutability. The first case states that if you have a `&T`, and `T`
implements `Deref` to some type `U`, you can get a `&U` transparently. The
second case states that the same deref coercion happens for mutable references.
<!-- the first two cases are the same as what? each other? I think so,
so have edited as much, but wasn't sure /LC -->
<!-- Yep! /Carol -->
The third case is trickier: Rust will also coerce a mutable reference to an
immutable one. But the reverse is *not* possible: immutable references will
never coerce to mutable references. Because of the borrowing rules, if you have
@@ -710,13 +699,11 @@ The second trait important to the smart pointer pattern is `Drop`, which lets
you customize what happens when a value is about to go out of scope. You can
provide an implementation for the `Drop` trait on any type, and that code
can be used to release resources like files or network connections.
<!-- so we're saying Drop can be used in other contexts, this is just a
convenient place to introduce it? /LC -->
<!-- Yep! /Carol -->
Were introducing `Drop` in the context of smart pointers because the
functionality of the `Drop` trait is almost always used when implementing a
smart pointer. For example, when a `Box<T>` is dropped it will deallocate the
space on the heap that the box points to.
space on the heap that the box points to. `Drop` can of course also be used in other contexts.
In some languages, for some types, the programmer must call code to free memory
or resources every time they finish using an instance those types. Examples
@@ -1111,12 +1098,10 @@ immutability restriction.
data even when there are immutable references to that data; normally, this
action is disallowed by the borrowing rules. To mutate data, the pattern uses
`unsafe` code inside a data structure to bend Rusts usual rules that govern
mutation and borrowing. We havent yet covered unsafe code that indicates we're
checking the rules manually instead of the compiler checking them for us; we
mutation and borrowing. Unsafe code indicates to the compiler that we're
checking the rules manually instead of relying on the compiler to check them for us; we
will discuss unsafe code more in Chapter 19.
<!-- maybe quickly give a quick idea of what unsafe code means, at a really
high level, to let them know how it helps in this situation /LC -->
<!-- Done! /Carol -->
We can use types that use the interior mutability pattern only when we can
ensure that the borrowing rules will be followed at runtime, even though the
compiler cant guarantee that. The `unsafe` code involved is then wrapped in a
@@ -1222,12 +1207,6 @@ an immutable value and see why that is useful.
Sometimes during testing a programmer will use a type in place of another type,
in order to observe particular behavior and assert it's implemented correctly.
<!-- why do we use other types -- to test different types with the code? Or you
mean they might use special types that offer particular functionality, like
mock objects do? /LC -->
<!-- I'm not sure I understand your question completely, but I've tried to
clarify by completing the sentence you left and adding a bit after this spot--
is it clearer now? /Carol -->
This placeholder type is called a *test double*. Think of it in the sense of a
"stunt double" in filmmaking, where a person steps in and substitutes for an
actor to do a particular tricky scene. Test doubles stand in for other types
@@ -1295,7 +1274,7 @@ where
}
}
```
'
Listing 15-20: A library to keep track of how close a value is to a maximum
value and warn when the value is at certain levels
@@ -1601,6 +1580,11 @@ The runtime checks of the borrowing rules protect us from data races, and its
sometimes worth trading a bit of speed for this flexibility in our data
structures.
<!-- While we mention threading a little bit, I think we should take a little
time here to really underscore that this method does not work for multithread
code. We could show an example of trying to share between threads and that
it errors at compile time as a result. /JT -->
The standard library has other types that provide interior mutability, such as
`Cell<T>`, which is similar except that instead of giving references to the
inner value, the value is copied in and out of the `Cell<T>`. Theres also
@@ -1738,9 +1722,6 @@ If you uncomment the last `println!` and run the program, Rust will try to
print this cycle with `a` pointing to `b` pointing to `a` and so forth until it
overflows the stack.
<!-- "in this case" meaning with the last print statement commented out? /LC -->
<!-- Technically with both the println commented out and not. I've tried to
clarify the purpose of this paragraph earlier /Carol -->
Compared to a real-world program, the consequences creating a reference cycle
in this example arent very dire: right after we create the reference cycle,
the program ends. However, if a more complex program allocated lots of memory
@@ -1783,6 +1764,15 @@ paragraph I wasn't entirely sure. It would be good to have a high level
succinct definition /LC -->
<!-- I've moved the explanation that was in the next paragraph to this spot and
modified it a bit, does this work? /Carol -->
<!-- JT, is this defined clearly enough? /LC -->
<!-- This is probably a topic that if you're familiar as a reader, you'll nod
along and if you're not, you'll have to spend some time with the ideas in
some sample programs for them to click.
I was wondering - something we could try to help this is to take our cycle
picture above and do another version, this time with a weak reference in it,
showing that the count gained by the weak reference doesn't increment what it's
pointing to. /JT -->
When you call `Rc::downgrade`, you get a smart pointer of type `Weak<T>`.
Instead of increasing the `strong_count` in the `Rc<T>` instance by 1, calling