From: Ralf Jung Date: Tue, 16 Jun 2015 17:15:56 +0000 (+0200) Subject: tune and tweak: part 05-07 X-Git-Url: https://git.ralfj.de/rust-101.git/commitdiff_plain/f5a79e8aaa07e56cf2dd8fe8438d96624dd84fbc?ds=inline tune and tweak: part 05-07 --- diff --git a/solutions/src/bigint.rs b/solutions/src/bigint.rs index 80ac63d..91e3ccf 100644 --- a/solutions/src/bigint.rs +++ b/solutions/src/bigint.rs @@ -55,16 +55,16 @@ impl BigInt { } } - /// Construct a BigInt from a vector of 64-bit "digits", with the last significant digit being first + /// Construct a BigInt from a vector of 64-bit "digits", with the last significant digit being first. Solution to 05.1. pub fn from_vec(mut v: Vec) -> Self { - // remove trailing zeroes + // remove trailing zeros while v.len() > 0 && v[v.len()-1] == 0 { v.pop(); } BigInt { data: v } } - /// Increments the number by 1. Solution to 05.1. + /// Increments the number by 1. pub fn inc1(&mut self) { let mut idx = 0; // This loop adds "(1 << idx)". If there is no more carry, we leave. @@ -131,6 +131,7 @@ impl PartialEq for BigInt { } impl Minimum for BigInt { + // This is essentially the solution to 06.1. fn min<'a>(&'a self, other: &'a Self) -> &'a Self { debug_assert!(self.test_invariant() && other.test_invariant()); if self.data.len() < other.data.len() { diff --git a/src/part03.rs b/src/part03.rs index 45caaec..0e7db1d 100644 --- a/src/part03.rs +++ b/src/part03.rs @@ -56,14 +56,15 @@ fn read_vec() -> Vec { // 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.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 `Option` or `Result`. - // The only way to get to the value we are interested in is through pattern matching (and through helper functions - // like `unwrap()`). If we call a function that returns a `Result`, and throw the return value away, - // the compiler will emit a warning. It is hence impossible for us to *forget* handling an error, - // or to accidentally use a value that doesn't make any sense because there was an error producing it. + // `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 `Option` or `Result`. + // The only way to get to the value we are interested in is through pattern matching (and through helper functions + // like `unwrap()`). If we call a function that returns a `Result`, and throw the return value away, + // the compiler will emit a warning. It is hence impossible for us to *forget* handling an error, + // or to accidentally use a value that doesn't make any sense because there was an error producing it. Ok(num) => vec.push(num), + // We don't care about the particular error, so we ignore it with a `_`. Err(_) => println!("What did I say about numbers?"), } } diff --git a/src/part04.rs b/src/part04.rs index 0ecfe76..e4c6a59 100644 --- a/src/part04.rs +++ b/src/part04.rs @@ -129,7 +129,8 @@ fn mutable_borrow_demo() { // they do. However, the `v` in `mutable_borrow_demo` is not actually usable, it is not *active*: As long as there is an // outstanding borrow, Rust will not allow you to do anything with `v`. -// So, to summarize - the ownership and borrowing system of Rust enforces the following three rules: +// ## Summary +// The ownership and borrowing system of Rust enforces the following three rules: // // * There is always exactly one owner of a piece of data // * If there is an active mutable borrow, then nobody else can have active access to the data diff --git a/src/part05.rs b/src/part05.rs index a4478f8..03c10d3 100644 --- a/src/part05.rs +++ b/src/part05.rs @@ -7,9 +7,9 @@ // 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 big 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. Now we just have to decide -// the order in which we store numbers. I decided that we will store the least significant +// 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 just +// have to decide the order in which we store numbers. I decided that we will store the least significant // digit first. This means that "1337" would actually become (7, 3, 3, 1).
// Finally, we declare that there must not be any trailing zeros (corresponding to // useless leading zeros in our usual way of writing numbers). This is to ensure that @@ -49,24 +49,19 @@ impl BigInt { // We can convert any vector of digits into a number, by removing trailing zeros. The `mut` // declaration for `v` here is just like the one in `let mut ...`, it says that we will locally - // change the vector `v`. In this case, we need to make that annotation to be able to call `pop` - // on `v`. + // change the vector `v`. + // + // **Exercise 05.1**: Implement this function. + // + // *Hint*: You can use `pop()` to remove the last element of a vector. pub fn from_vec(mut v: Vec) -> Self { - while v.len() > 0 && v[v.len()-1] == 0 { - v.pop(); - } - BigInt { data: v } + unimplemented!() } } -// **Exercise 05.1**: Write a function on `BigInt` that returns the number of digits. Write another one -// that increments the number by 1. -// -// *Hint*: To take `self` as a mutable borrow, write `fn inc1(&mut self)`. - // ## Cloning // If you have a close look at the type of `BigInt::from_vec`, you will notice that it -// consumes the vector `v`. The caller hence loses access. There is however something +// consumes the vector `v`. The caller hence loses access to its vector. There is however something // we can do if we don't want that to happen: We can explicitly `clone` the vector, // which means that a full (or *deep*) copy will be performed. Technically, // `clone` takes a borrowed vector, and returns a fully owned one. @@ -124,7 +119,7 @@ enum Variant { Text(String), } // Now consider the following piece of code. Like above, `n` will be a borrow of a part of `var`, -// and since we wrote `ref mut`, they will be mutable borrows. In other words, right after the match, `ptr` +// and since we wrote `ref mut`, the borrow will be mutable. In other words, right after the match, `ptr` // points to the number that's stored in `var`, where `var` is a `Number`. Remember that `_` means // "we don't care". fn work_on_variant(mut var: Variant, text: String) { diff --git a/src/part06.rs b/src/part06.rs index abe4c63..e159ca5 100644 --- a/src/part06.rs +++ b/src/part06.rs @@ -5,14 +5,14 @@ use part05::BigInt; // With `BigInt` being about numbers, we should be able to write a version of `vec_min` -// that computes the minimum of a list of `BigInt`. We start by writing `min` for -// `BigInt`. Now our assumption of having no trailing zeros comes in handy! +// that computes the minimum of a list of `BigInt`. First, we have to write `min` for `BigInt`. impl BigInt { fn min_try1(self, other: Self) -> Self { // Just to be sure, we first check that both operands actually satisfy our invariant. `debug_assert!` is a // macro that checks that its argument (must be of type `bool`) is `true`, and panics otherwise. It gets // removed in release builds, which you do with `cargo build --release`. debug_assert!(self.test_invariant() && other.test_invariant()); + // Now our assumption of having no trailing zeros comes in handy: // If the lengths of the two numbers differ, we already know which is larger. if self.data.len() < other.data.len() { self @@ -25,25 +25,26 @@ impl BigInt { } } -// Now we can write `vec_min`. In order to make it type-check, we have to write it as follows. +// Now we can write `vec_min`. In order to make it type-check, we have make a deep copy of e. fn vec_min(v: &Vec) -> Option { let mut min: Option = None; for e in v { + let e = e.clone(); min = Some(match min { - None => e.clone(), - Some(n) => e.clone().min_try1(n) + None => e, + Some(n) => e.min_try1(n) }); } min } -// Now, what's happening here? Why do we have to write `clone()`, and why did we not -// have to write that in our previous version? +// Now, what's happening here? Why do we have to clone `e`, and why did we not +// have to do that in our previous version? // // The answer is already hidden in the type of `vec_min`: `v` is just borrowed, but // the Option that it returns is *owned*. We can't just return one of the elements of `v`, // as that would mean that it is no longer in the vector! In our code, this comes up when we update -// the intermediate variable `min`, which also has type `Option`. If you replace `e.clone()` -// in the `None` arm with `*e`, Rust will complain "Cannot move out of borrowed content". That's because +// the intermediate variable `min`, which also has type `Option`. If you replace get rid of the +// `e.clone()`, Rust will complain "Cannot move out of borrowed content". That's because // `e` is a `&BigInt`. Assigning `min = Some(*e)` works just like a function call: Ownership of the // underlying data is transferred from where `e` borrows from to `min`. But that's not allowed, since // we just borrowed `e`, so we cannot empty it! We can, however, call `clone()` on it. Then we own @@ -54,11 +55,11 @@ fn vec_min(v: &Vec) -> Option { // 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 `bool`: If you move the value from one -// place to another, then both instance are "complete". We also say the value has been *duplicated*. This is in +// place to another, then both instances are "complete". We also say the value has been *duplicated*. This is in // stark contrast to types like `Vec`, where moving the value results in both the old and the new vector to -// point to the same underlying buffer. We don't have two vectors, there's no duplication. +// point to the same underlying buffer. We don't have two vectors, there's no proper duplication. // -// Rust calls types that can be freely duplicated `Copy` types. `Copy` is another trait, and it is implemented for +// Rust calls types that can be easily duplicated `Copy` types. `Copy` is another trait, and it is implemented for // types like `i32` and `bool`. Remember how we defined the trait `Minimum` by writing `trait Minimum : Copy { ...`? // This tells Rust that every type that implements `Minimum` 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 @@ -69,21 +70,21 @@ fn vec_min(v: &Vec) -> Option { // are `Copy`, and that's not the case for `BigInt`. However, we can make // `SomethingOrNothing` copy if `T` is `Copy`. use part02::{SomethingOrNothing,Something,Nothing}; -impl Copy for SomethingOrNothing{} +impl Copy for SomethingOrNothing {} // Again, Rust can generate implementations of `Copy` automatically. If // you add `#[derive(Copy,Clone)]` right before the definition of `SomethingOrNothing`, // both `Copy` and `Clone` will automatically be implemented. // ## An operational perspective // Instead of looking at what happens "at the surface" (i.e., visible in Rust), one can also explain -// ownership passing and how `Copy` and `Clone` fit by looking at what happens on the machine.
+// ownership passing and how `Copy` and `Clone` fit in by looking at what happens on the machine.
// When Rust code is executed, passing a value (like `i32` or `Vec`) to a function will always // result in a shallow copy being performed: Rust just copies the bytes representing that value, and // considers itself done. That's just like the default copy constructor in C++. Rust, however, will // consider this a destructive operation: After copying the bytes elsewhere, the original value must -// no longer be used. After all, the two could not share a pointer! If, however, you mark a type `Copy`, +// no longer be used. After all, the two could now share a pointer! If, however, you mark a type `Copy`, // then Rust will *not* consider a move destructive, and just like in C++, the old and new value -// can happily coexist. Now, Rust does not allow to to overload the copy constructor. This means that +// can happily coexist. Now, Rust does not allow you to overload the copy constructor. This means that // passing a value around will always be a fast operation, no allocation or any other kind of heap access // will happen. In the situations where you would write a copy constructor in C++ (and hence // incur a hidden cost on every copy of this type), you'd have the type *not* implement `Copy`, but only @@ -93,7 +94,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*. -// This is demonstrated by the function `head` that borrows the first element of a vector if it is non-empty. +// The function `head` demonstrates how that could work: It borrows 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 borrow the first element of `v` and use it to construct the return value. fn head(v: &Vec) -> Option<&T> { @@ -103,10 +104,8 @@ fn head(v: &Vec) -> Option<&T> { None } } - -// Now, coming back to `head` - here, we are returning a pointer to the first element. But doesn't -// that mean that callers have to be careful? Imagine `head` would be a C++ function, and we would -// write the following code. +// Technically, we are returning a pointer to the first element. But doesn't that mean that callers have to be +// careful? Imagine `head` would be a C++ function, and we would write the following code. /* int foo(std::vector v) { int *first = head(v); @@ -114,8 +113,9 @@ fn head(v: &Vec) -> Option<&T> { return *first; } */ -// This is very much like our very first motivating example for ownership, at the beginning of part 04. -// But this time, the bug is hidden behind the call to `head`. How does Rust solve this? If we translate +// This is very much like our very first motivating example for ownership, at the beginning of part 04: +// `push_back` could reallocate the buffer, making `first` an invalid pointer. Again, we have aliasing (of `first` +// and `v`) and mutation. But this time, the bug is hidden behind the call to `head`. How does Rust solve this? If we translate // the code above to Rust, it doesn't compile, so clearly we are good - but how and why? // (Notice that have to explicitly assert using `unwrap` that `first` is not `None`, whereas the C++ code // above would silently dereference a `NULL`-pointer. But that's another point.) @@ -127,11 +127,11 @@ fn rust_foo(mut v: Vec) -> i32 { // To give the answer to this question, we have to talk about the *lifetime* of a borrow. The point is, saying that // you borrowed your friend a `Vec`, or a book, is not good enough, unless you also agree on *how long* -// your friend can borrow. After all, you need to know when you can rely on owning your data (or book) again. +// your friend can borrow it. After all, you need to know when you can rely on owning your data (or book) again. // -// Every borrow in Rust has an associated lifetime. The full type of `head` reads as follows: -// `fn<'a, T>(&'a Vec) -> Option<&'a T>`. Here, `'a` is a *lifetime variable*, which represents how long the vector has -// been borrowed. The function type expresses that argument and return value have *the same lifetime*. +// Every borrow in Rust has an associated lifetime, written `&'a T` for a borrow of type `T` with lifetime `'a`. The full +// type of `head` reads as follows: `fn<'a, T>(&'a Vec) -> Option<&'a T>`. Here, `'a` is a *lifetime variable*, which +// represents how long the vector has been borrowed. The function type expresses that argument and return value have *the same lifetime*. // // When analyzing the code of `rust_foo`, Rust has to assign a lifetime to `first`. It will choose the scope // where `first` is valid, which is the entire rest of the function. Because `head` ties the lifetime of its diff --git a/src/part07.rs b/src/part07.rs index c12081e..8fad5a8 100644 --- a/src/part07.rs +++ b/src/part07.rs @@ -3,7 +3,7 @@ pub use part05::BigInt; -// With our new knowledge on Lifetimes, we are now able to write down the desired type +// With our new knowledge of lifetimes, we are now able to write down the desired type // of `min`: We want the function to take two borrows *of the same lifetime*, and then // return a borrow of that lifetime. If the two input lifetimes would be different, we // would not know which lifetime to use for the result. @@ -36,7 +36,7 @@ pub fn vec_min(v: &Vec) -> Option<&T> { // a pointer in C(++), if you look at what happens during execution - but it's much safer to use. // For our `vec_min` to be usable with `BigInt`, we need to provide an implementation of -// `minimum`. You should be able to pretty much copy the code you wrote for exercise 06.1. +// `Minimum`. You should be able to pretty much copy the code you wrote for exercise 06.1. impl Minimum for BigInt { fn min<'a>(&'a self, other: &'a Self) -> &'a Self { unimplemented!() @@ -46,10 +46,9 @@ 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 build-in support for both unit tests and integration -// tests. However, before we go there, we need to have a way of checking whether the results are +// tests. However, before we go there, we need to have a way of checking whether the results of function calls are // correct. In other words, we need to define how to test equality of `BigInt`. Being able to -// test equality is a property of a type, that - you guessed it - Rust expresses as a trait: -// `PartialEq`. Once a type implements that trait, one can use the `==` operator on it. +// test equality is a property of a type, that - you guessed it - Rust expresses as a trait: `PartialEq`. // Doing this for `BigInt` is fairly easy, thanks to our requirement that there be no trailing zeros. // The `inline` attribute tells Rust that we will typically want this function to be inlined. @@ -63,14 +62,14 @@ impl PartialEq for BigInt { // Since implementing `PartialEq` is a fairly mechanical business, you can let Rust automate this // by adding the attribute `derive(PartialEq)` to the type definition. In case you wonder about // the "partial", I suggest you check out the documentation of [`PartialEq`](http://doc.rust-lang.org/std/cmp/trait.PartialEq.html) -// and [`Eq`](http://doc.rust-lang.org/std/cmp/trait.Eq.html). Again, `Eq` can be automatically derived. +// and [`Eq`](http://doc.rust-lang.org/std/cmp/trait.Eq.html). `Eq` can be automatically derived as well. -// Now we can compare `BigInt`s! Speaking in C++ terms, we just overloaded the `==` operator +// Now we can compare `BigInt`s using `==`! 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 defines) a // trait that catches the core characteristic common to all the overloads, and writes a single // function that's generic in the trait. For example, instead of overloading a function for all -// the ways a string can be represented, one write a generic functions over [ToString](http://doc.rust-lang.org/std/string/trait.ToString.html). +// the ways a string can be represented, one writes a generic functions over [ToString](http://doc.rust-lang.org/std/string/trait.ToString.html). // Usually, there is a trait like this that fits the purpose - and if there is, this has the great // advantage that any type *you* write, that can convert to a string, just has to implement // that trait to be immediately usable with all the functions out there that generalize over `ToString`. @@ -83,7 +82,7 @@ fn compare_big_ints() { } // ## Testing -// With our equality test written, we are now ready to write out first testcase. It doesn't get much +// With our equality test written, we are now ready to write our first testcase. It doesn't get much // simpler: You just write a function (with no arguments or return value), and give it the `test` attribute. // `assert!` is like `debug_assert!`, but does not get compiled away in a release build. #[test] @@ -105,7 +104,7 @@ fn test_min() { // that users can understand, while `Debug` is meant to show the internal state of data and targeted at // the programmer. The latter is what we want for `assert_eq!`, so let's get started. -// Al formating is handled by [`std::fmt`](http://doc.rust-lang.org/std/fmt/index.html). I won't explain +// All formating is handled by [`std::fmt`](http://doc.rust-lang.org/std/fmt/index.html). I won't explain // all the details, and refer you to the documentation instead. use std::fmt; @@ -118,30 +117,22 @@ impl fmt::Debug for BigInt { } // `Debug` implementations can be automatically generated using the `derive(Debug)` attribute. -// Now we are ready to use `assert_eq!` to test `vec_min`. While we are at it, let's also follow the usual -// Rust style of putting tests into a *submodule*, to avoid polluting the namespace. The attribute `cfg(test)` -// at the submodule means that it will only be compiled when building the tests. -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_vec_min() { - let b1 = BigInt::new(1); - let b2 = BigInt::new(42); - let b3 = BigInt::from_vec(vec![0, 1]); - - let v1 = vec![b2.clone(), b1.clone(), b3.clone()]; - let v2 = vec![b2.clone(), b3.clone()]; - assert_eq!(vec_min(&v1), Some(&b1)); - assert_eq!(vec_min(&v2), Some(&b2)); - } +// Now we are ready to use `assert_eq!` to test `vec_min`. +#[test] +fn test_vec_min() { + let b1 = BigInt::new(1); + let b2 = BigInt::new(42); + let b3 = BigInt::from_vec(vec![0, 1]); + + let v1 = vec![b2.clone(), b1.clone(), b3.clone()]; + let v2 = vec![b2.clone(), b3.clone()]; + assert_eq!(vec_min(&v1), Some(&b1)); + assert_eq!(vec_min(&v2), Some(&b2)); } // **Exercise 07.1**: Add some more testcases. In particular, make sure you test the behavior of // `vec_min` on an empty vector. Also add tests for `BigInt::from_vec` (in particular, removing -// trailing zeros) and the functions you wrote for exercise 05.1. Finally, break one of your -// functions in a subtle way and watch the test fail. +// trailing zeros). Finally, break one of your functions in a subtle way and watch the test fail. // // **Exercise 07.2**: Go back to your good ol' `SomethingOrNothing`, and implement `Display` for it. (This will, // of course, need a `Display` bound on `T`.) Then you should be able to use them with `println!` just like you do with numbers.