From 375923e203d323dadc639434ebc1f29530f4ac2a Mon Sep 17 00:00:00 2001 From: Nicola 'tekNico' Larosa Date: Sun, 4 Feb 2018 21:46:03 +0100 Subject: [PATCH] Fixes as per review. --- src/part02.rs | 3 --- src/part03.rs | 1 - src/part04.rs | 2 -- src/part05.rs | 9 +++++---- src/part06.rs | 10 +++++----- src/part07.rs | 6 ++---- src/part08.rs | 3 --- src/part09.rs | 4 +--- src/part10.rs | 10 ++++++---- src/part11.rs | 1 - src/part12.rs | 2 -- src/part15.rs | 19 +++++++++---------- src/part16.rs | 2 +- 13 files changed, 29 insertions(+), 43 deletions(-) diff --git a/src/part02.rs b/src/part02.rs index 235e22d..a97c367 100644 --- a/src/part02.rs +++ b/src/part02.rs @@ -21,7 +21,6 @@ pub use self::SomethingOrNothing::*; //@ What this does is define an entire family of types: We can now write //@ `SomethingOrNothing` to get back our `NumberOrNothing`. type NumberOrNothing = SomethingOrNothing; - //@ However, we can also write `SomethingOrNothing` or even //@ `SomethingOrNothing>`. In fact, a type like `SomethingOrNothing` is so //@ useful that it is already present in the standard library: It's called an *option type*, @@ -122,8 +121,6 @@ impl Minimum for i32 { } // We again provide a `print` function. - - //@ This also shows that we can have multiple `impl` blocks for the same type (remember that //@ `NumberOrNothing` is just a type alias for `SomethingOrNothing`), and we can provide some //@ methods only for certain instances of a generic type. diff --git a/src/part03.rs b/src/part03.rs index 3dfb2ab..2a5eb16 100644 --- a/src/part03.rs +++ b/src/part03.rs @@ -60,7 +60,6 @@ fn read_vec() -> Vec { //@ the return type of the function), but that's a bit too much magic for my taste. We are //@ being more explicit here: `parse::` is `parse` with its generic type set to `i32`. match line.trim().parse::() { - //@ `parse` returns again a `Result`, and this time we use a `match` to handle errors //@ (like, the user entering something that is not a number). //@ This is a common pattern in Rust: Operations that could go wrong will return diff --git a/src/part04.rs b/src/part04.rs index fb3604e..faa3e31 100644 --- a/src/part04.rs +++ b/src/part04.rs @@ -98,7 +98,6 @@ fn shared_ref_demo() { //@ that was created before calling `vec_min` remains valid. // ## Unique, mutable references - //@ There is a second way to borrow something, a second kind of reference: The *mutable reference*. //@ This is a reference that comes with the promise that nobody else has *any kind of access* to //@ the referee - in contrast to shared references, there is no aliasing with mutable references. @@ -121,7 +120,6 @@ fn mutable_ref_demo() { vec_inc(&mut v); /* println!("The first element is: {}", *first); */ /* BAD! */ } - //@ `&mut` is the operator to create a mutable reference. We have to mark `v` as mutable in order //@ to create such a reference: Even though we completely own `v`, Rust tries to protect us from //@ accidentally mutating things. diff --git a/src/part05.rs b/src/part05.rs index 6abfe0c..c0a573b 100644 --- a/src/part05.rs +++ b/src/part05.rs @@ -6,7 +6,6 @@ //@ In the course of the next few parts, we are going to build a data-structure for computations //@ with *big* numbers. We would like to not have an upper bound to how large these numbers can //@ get, with the memory of the machine being the only limit. -//@ //@ We start by deciding how to represent such big numbers. One possibility here is to use a vector //@ "digits" of the number. This is like "1337" being a vector of four digits (1, 3, 3, 7), except //@ that we will use `u64` as type of our digits, meaning we have 2^64 individual digits. Now we @@ -146,9 +145,11 @@ fn work_on_variant(mut var: Variant, text: String) { //@ (Technically, the first field of a `String` is a pointer to its character data, so by //@ overwriting that pointer with an integer, we make it a completely invalid address. When the //@ destructor of `var` runs, it would try to deallocate that address, and Rust would eat your -//@ laundry - or whatever.) I hope this example clarifies why Rust has to rule out mutation in the -//@ presence of aliasing *in general*, not just for the specific case of a buffer being -//@ reallocated, and old pointers becoming hence invalid. +//@ laundry - or whatever.) +//@ +//@ I hope this example clarifies why Rust has to rule out mutation in the presence of aliasing +//@ *in general*, not just for the specific case of a buffer being reallocated, and old pointers +//@ becoming hence invalid. //@ [index](main.html) | [previous](part04.html) | [raw source](workspace/src/part05.rs) | //@ [next](part06.html) diff --git a/src/part06.rs b/src/part06.rs index 04601ba..21fe644 100644 --- a/src/part06.rs +++ b/src/part06.rs @@ -56,7 +56,6 @@ fn vec_min(v: &Vec) -> Option { //@ in the next part. // ## `Copy` types - //@ But before we go there, I should answer the second question I brought up above: Why did our old //@ `vec_min` work? We stored the minimal `i32` locally without cloning, and Rust did not complain. //@ That's because there isn't really much of an "ownership" when it comes to types like `i32` or @@ -71,6 +70,7 @@ fn vec_min(v: &Vec) -> Option { //@ must also implement `Copy`, and that's why the compiler accepted our generic `vec_min` in part //@ 02. `Copy` is the first *marker trait* that we encounter: It does not provide any methods, but //@ makes a promise about the behavior of the type - in this case, being duplicable. + //@ If you try to implement `Copy` for `BigInt`, you will notice that Rust does not let you do //@ that. A type can only be `Copy` if all its elements are `Copy`, and that's not the case for //@ `BigInt`. However, we can make `SomethingOrNothing` copy if `T` is `Copy`. @@ -100,6 +100,7 @@ impl Copy for SomethingOrNothing {} //@ To fix the performance problems of `vec_min`, we need to avoid using `clone`. We'd like the //@ return value to not be owned (remember that this was the source of our need for cloning), but //@ *borrowed*. In other words, we want to return a shared reference to the minimal element. + //@ The function `head` demonstrates how that could work: It returns a reference to the first //@ element of a vector if it is non-empty. The type of the function says that it will either //@ return nothing, or it will return a borrowed `T`. We can then obtain a reference to the first @@ -158,10 +159,9 @@ fn rust_foo(mut v: Vec) -> i32 { //@ references into data they got as argument, and make sure they are used correctly, *while //@ looking only at the function type*. At no point in our analysis of `rust_foo` did we have to //@ look *into* `head`. That's, of course, crucial if we want to separate library code from -//@ application code. -//@ Most of the time, we don't have to explicitly add lifetimes to function types. This is thanks -//@ to *lifetime elision*, where Rust will automatically insert lifetimes we did not specify, -//@ following some simple, well-documented +//@ application code. Most of the time, we don't have to explicitly add lifetimes to function +//@ types. This is thanks to *lifetime elision*, where Rust will automatically insert lifetimes we +//@ did not specify, following some simple, well-documented //@ [rules](https://doc.rust- lang.org/stable/book/lifetimes.html#lifetime-elision). //@ [index](main.html) | [previous](part05.html) | [raw source](workspace/src/part06.rs) | diff --git a/src/part07.rs b/src/part07.rs index 8f65c2b..b2ef809 100644 --- a/src/part07.rs +++ b/src/part07.rs @@ -35,8 +35,8 @@ pub fn vec_min(v: &Vec) -> Option<&T> { //@ much safer to use. // **Exercise 07.1**: For our `vec_min` to be usable with `BigInt`, you will have to provide an -// **implementation of `Minimum`. You should be able to pretty much copy the code you wrote for -// **exercise 06.1. You should *not* make any copies of `BigInt`! +// implementation of `Minimum`. You should be able to pretty much copy the code you wrote for +// exercise 06.1. You should *not* make any copies of `BigInt`! impl Minimum for BigInt { fn min<'a>(&'a self, other: &'a Self) -> &'a Self { unimplemented!() @@ -44,7 +44,6 @@ impl Minimum for BigInt { } // ## Operator Overloading - //@ How can we know that our `min` function actually does what we want it to do? One possibility //@ here is to do *testing*. Rust comes with nice built-in support for both unit tests and //@ integration tests. However, before we go there, we need to have a way of checking whether the @@ -73,7 +72,6 @@ impl PartialEq for BigInt { // Now we can compare `BigInt`s. Rust treats `PartialEq` special in that it is wired to the operator // `==`: - //@ That operator can now be used on our numbers! Speaking in C++ terms, we just overloaded the //@ `==` operator for `BigInt`. Rust does not have function overloading (i.e., it will not dispatch //@ to different functions depending on the type of the argument). Instead, one typically finds (or diff --git a/src/part08.rs b/src/part08.rs index d89ea0d..17beefa 100644 --- a/src/part08.rs +++ b/src/part08.rs @@ -13,7 +13,6 @@ use part05::BigInt; // So, let us write a function to "add with carry", and give it the appropriate type. Notice Rust's // native support for pairs. fn overflowing_add(a: u64, b: u64, carry: bool) -> (u64, bool) { - //@ Rust's stanza on integer overflows may be a bit surprising: In general, when we write `a + //@ b`, an overflow is considered an *error*. If you compile your program in debug mode, Rust //@ will actually check for that error and panic the program in case of overflows. For @@ -64,7 +63,6 @@ fn test_overflowing_add() { //@ other operand. In this case, it will also be `BigInt` (and we could have left it away, since //@ that's the default). impl ops::Add for BigInt { - //@ Besides static functions and methods, traits can contain *associated types*: This is a type //@ chosen by every particular implementation of the trait. The methods of the trait can then //@ refer to that type. In the case of addition, it is used to give the type of the result. @@ -136,7 +134,6 @@ impl<'a, 'b> ops::Add<&'a BigInt> for &'b BigInt { //@ The `cfg` attribute controls whether this module is even compiled: If we added some functions //@ that are useful for testing, Rust would not bother compiling them when you just build your //@ program for normal use. Other than that, tests work as usually. - #[cfg(test)] mod tests { use part05::BigInt; diff --git a/src/part09.rs b/src/part09.rs index 80a9c08..9cad15d 100644 --- a/src/part09.rs +++ b/src/part09.rs @@ -19,6 +19,7 @@ use part05::BigInt; //@ current location. However, it cannot *own* the `BigInt`, because then the number would be gone //@ after iteration! That'd certainly be bad. The only alternative is for the iterator to *borrow* //@ the number, so it takes a reference. + //@ In writing this down, we again have to be explicit about the lifetime of the reference: We //@ can't just have an `Iter`, we must have an `Iter<'a>` that borrows the number for lifetime //@ `'a`. This is our first example of a data-type that's polymorphic in a lifetime, as opposed to @@ -26,7 +27,6 @@ use part05::BigInt; //@ `usize` here is the type of unsigned, pointer-sized numbers. It is typically the type of //@ "lengths of things", in particular, it is the type of the length of a `Vec` and hence the right //@ type to store an offset into the vector of digits. - pub struct Iter<'a> { num: &'a BigInt, idx: usize, // the index of the last number that was returned @@ -118,7 +118,6 @@ fn iter_invalidation_demo() { /*b = b + BigInt::new(1);*/ /* BAD! */ } } - //@ If you enable the bad line, Rust will reject the code. Why? The problem is that we are //@ modifying the number while iterating over it. In other languages, this can have all sorts of //@ effects from inconsistent data or throwing an exception (Java) to bad pointers being @@ -158,7 +157,6 @@ impl<'a> IntoIterator for &'a BigInt { } } // With this in place, you can now replace `b.iter()` in `main` by `&b`. Go ahead and try it!
- //@ Wait, `&b`? Why that? Well, we implemented `IntoIterator` for `&BigInt`. If we are in a place //@ where `b` is already borrowed, we can just do `for digit in b`. If however, we own `b`, we have //@ to create a reference to it. Alternatively, we could implement `IntoIterator` for `BigInt` - diff --git a/src/part10.rs b/src/part10.rs index a28b611..39270de 100644 --- a/src/part10.rs +++ b/src/part10.rs @@ -125,10 +125,12 @@ pub fn print_and_count(b: &BigInt) { // then looks for numbers larger than some threshold, and prints them. fn inc_print_threshold(v: &Vec, offset: i32, threshold: i32) { //@ `map` takes a closure that is applied to every element of the iterator. `filter` removes - //@ elements from the iterator that do not pass the test given by the closure. Since all these - //@ closures compile down to the pattern described above, there is actually no heap allocation - //@ going on here. This makes closures very efficient, and it makes optimization fairly - //@ trivial: The resulting code will look like you hand-rolled the loop in C. + //@ elements from the iterator that do not pass the test given by the closure. + //@ + //@ Since all these closures compile down to the pattern described above, there is actually no + //@ heap allocation going on here. This makes closures very efficient, and it makes + //@ optimization fairly trivial: The resulting code will look like you hand-rolled the loop in + //@ C. for i in v.iter().map(|n| *n + offset).filter(|n| *n > threshold) { println!("{}", i); } diff --git a/src/part11.rs b/src/part11.rs index 322e2f2..a088e77 100644 --- a/src/part11.rs +++ b/src/part11.rs @@ -87,7 +87,6 @@ impl Callbacks { //@ The difference to a reference is that `Box` implies full ownership: Once you drop //@ the box (i.e., when the entire `Callbacks` instance is dropped), the content it //@ points to on the heap will be deleted. - } } } diff --git a/src/part12.rs b/src/part12.rs index 6803aca..e7ccf99 100644 --- a/src/part12.rs +++ b/src/part12.rs @@ -11,7 +11,6 @@ use std::cell::{Cell, RefCell}; //@ This restriction propagates up to `Callbacks` itself. What could we do about this? //@ ## `Rc` - //@ The solution is to find some way of cloning `Callbacks` without cloning the environments. This //@ can be achieved with `Rc`, a *reference-counted* pointer. This is is another example of a //@ smart pointer. You can `clone` an `Rc` as often as you want, that doesn't affect the data it @@ -143,7 +142,6 @@ impl CallbacksMut { // `borrow_mut`. //@ At run-time, the cell will keep track of the number of outstanding shared and //@ mutable references, and panic if the rules are violated.
- //@ For this check to be performed, `closure` is a *guard*: Rather than a normal //@ reference, `borrow_mut` returns a smart pointer ([`RefMut`](https://doc.rust- //@ lang.org/stable/std/cell/struct.RefMut.html), in this case) that waits until is diff --git a/src/part15.rs b/src/part15.rs index ef5e9b4..e646d34 100644 --- a/src/part15.rs +++ b/src/part15.rs @@ -116,16 +116,15 @@ pub fn main() { // `unwrap_or_else` for this job. //@ ## `RwLock` - -//@ Besides `Mutex`, there's also [`RwLock`](https://doc.rust- -//@ lang.org/stable/std/sync/struct.RwLock.html), which provides two ways of locking: One that -//@ grants only read-only access, to any number of concurrent readers, and another one for -//@ exclusive write access. Notice that this is the same pattern we already saw with shared vs. -//@ mutable references. Hence another way of explaining `RwLock` is to say that it is like -//@ `RefCell`, but works even for concurrent access. Rather than panicking when the data is already -//@ borrowed, `RwLock` will of course block the current thread until the lock is available. -//@ In this view, `Mutex` is a stripped-down version of `RwLock` that does not distinguish readers -//@ and writers. +//@ Besides `Mutex`, there's also +//@ [`RwLock`](https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html), which provides two +//@ ways of locking: One that grants only read-only access, to any number of concurrent readers, +//@ and another one for exclusive write access. Notice that this is the same pattern we already +//@ saw with shared vs. mutable references. Hence another way of explaining `RwLock` is to say that +//@ it is like `RefCell`, but works even for concurrent access. Rather than panicking when the data +//@ is already borrowed, `RwLock` will of course block the current thread until the lock is +//@ available. In this view, `Mutex` is a stripped-down version of `RwLock` that does not +//@ distinguish readers and writers. // **Exercise 15.3**: Change the code above to use `RwLock`, such that multiple calls to `get` can // be executed at the same time. diff --git a/src/part16.rs b/src/part16.rs index 4b3e773..876df27 100644 --- a/src/part16.rs +++ b/src/part16.rs @@ -28,6 +28,7 @@ use std::marker::PhantomData; //@ low-level data-structure like a doubly-linked list, it makes sense to implement an efficient //@ version once, that is unsafe internally, but that can be used without any risk by safe client //@ code. + //@ As usually, we start by defining the types. Everything is parameterized by the type `T` of the //@ data stored in the list. // A node of the list consists of the data, and two node pointers for the predecessor and successor. @@ -216,7 +217,6 @@ impl Drop for LinkedList { } // ## The End - //@ Congratulations! You completed Rust-101. This was the last part of the course. I hope you //@ enjoyed it. If you have feedback or want to contribute yourself, please head to the //@ [Rust-101](https://www.ralfj.de/projects/rust-101/) website fur further information. The entire -- 2.30.2