Add new section on trait bounds.
authorLukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Thu, 23 May 2024 13:29:42 +0000 (15:29 +0200)
committerLukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com>
Thu, 23 May 2024 13:29:42 +0000 (15:29 +0200)
60 files changed:
Cargo.lock
book/src/04_traits/05_str_slice.md [deleted file]
book/src/04_traits/05_trait_bounds.md [new file with mode: 0644]
book/src/04_traits/06_deref.md [deleted file]
book/src/04_traits/06_str_slice.md [new file with mode: 0644]
book/src/04_traits/07_deref.md [new file with mode: 0644]
book/src/04_traits/07_sized.md [deleted file]
book/src/04_traits/08_from.md [deleted file]
book/src/04_traits/08_sized.md [new file with mode: 0644]
book/src/04_traits/09_assoc_vs_generic.md [deleted file]
book/src/04_traits/09_from.md [new file with mode: 0644]
book/src/04_traits/10_assoc_vs_generic.md [new file with mode: 0644]
book/src/04_traits/10_clone.md [deleted file]
book/src/04_traits/11_clone.md [new file with mode: 0644]
book/src/04_traits/11_copy.md [deleted file]
book/src/04_traits/12_copy.md [new file with mode: 0644]
book/src/04_traits/12_drop.md [deleted file]
book/src/04_traits/13_drop.md [new file with mode: 0644]
book/src/04_traits/13_outro.md [deleted file]
book/src/04_traits/14_outro.md [new file with mode: 0644]
exercises/04_traits/05_str_slice/Cargo.toml [deleted file]
exercises/04_traits/05_str_slice/src/lib.rs [deleted file]
exercises/04_traits/05_trait_bounds/Cargo.toml [new file with mode: 0644]
exercises/04_traits/05_trait_bounds/src/lib.rs [new file with mode: 0644]
exercises/04_traits/06_deref/Cargo.toml [deleted file]
exercises/04_traits/06_deref/src/lib.rs [deleted file]
exercises/04_traits/06_str_slice/Cargo.toml [new file with mode: 0644]
exercises/04_traits/06_str_slice/src/lib.rs [new file with mode: 0644]
exercises/04_traits/07_deref/Cargo.toml [new file with mode: 0644]
exercises/04_traits/07_deref/src/lib.rs [new file with mode: 0644]
exercises/04_traits/07_sized/Cargo.toml [deleted file]
exercises/04_traits/07_sized/src/lib.rs [deleted file]
exercises/04_traits/08_from/Cargo.toml [deleted file]
exercises/04_traits/08_from/src/lib.rs [deleted file]
exercises/04_traits/08_sized/Cargo.toml [new file with mode: 0644]
exercises/04_traits/08_sized/src/lib.rs [new file with mode: 0644]
exercises/04_traits/09_assoc_vs_generic/Cargo.toml [deleted file]
exercises/04_traits/09_assoc_vs_generic/src/lib.rs [deleted file]
exercises/04_traits/09_from/Cargo.toml [new file with mode: 0644]
exercises/04_traits/09_from/src/lib.rs [new file with mode: 0644]
exercises/04_traits/10_assoc_vs_generic/Cargo.toml [new file with mode: 0644]
exercises/04_traits/10_assoc_vs_generic/src/lib.rs [new file with mode: 0644]
exercises/04_traits/10_clone/Cargo.toml [deleted file]
exercises/04_traits/10_clone/src/lib.rs [deleted file]
exercises/04_traits/11_clone/Cargo.toml [new file with mode: 0644]
exercises/04_traits/11_clone/src/lib.rs [new file with mode: 0644]
exercises/04_traits/11_copy/Cargo.toml [deleted file]
exercises/04_traits/11_copy/src/lib.rs [deleted file]
exercises/04_traits/12_copy/Cargo.toml [new file with mode: 0644]
exercises/04_traits/12_copy/src/lib.rs [new file with mode: 0644]
exercises/04_traits/12_drop/Cargo.toml [deleted file]
exercises/04_traits/12_drop/src/lib.rs [deleted file]
exercises/04_traits/13_drop/Cargo.toml [new file with mode: 0644]
exercises/04_traits/13_drop/src/lib.rs [new file with mode: 0644]
exercises/04_traits/13_outro/Cargo.toml [deleted file]
exercises/04_traits/13_outro/src/lib.rs [deleted file]
exercises/04_traits/13_outro/tests/integration.rs [deleted file]
exercises/04_traits/14_outro/Cargo.toml [new file with mode: 0644]
exercises/04_traits/14_outro/src/lib.rs [new file with mode: 0644]
exercises/04_traits/14_outro/tests/integration.rs [new file with mode: 0644]

index c572c57..5b93b94 100644 (file)
@@ -768,6 +768,10 @@ dependencies = [
 name = "trait_"
 version = "0.1.0"
 
+[[package]]
+name = "trait_bounds"
+version = "0.1.0"
+
 [[package]]
 name = "tryfrom"
 version = "0.1.0"
diff --git a/book/src/04_traits/05_str_slice.md b/book/src/04_traits/05_str_slice.md
deleted file mode 100644 (file)
index 4b78f1c..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-# String slices
-
-Throughout the previous chapters you've seen quite a few **string literals** being used in the code, 
-like `"To-Do"` or `"A ticket description"`.
-They were always followed by a call to `.to_string()` or `.into()`. It's time to understand why!
-
-## String literals
-
-You define a string literal by enclosing the raw text in double quotes:
-
-```rust
-let s = "Hello, world!";
-```
-
-The type of `s` is `&str`, a **reference to a string slice**.  
-
-## Memory layout
-
-`&str` and `String` are different types—they're not interchangeable.  
-Let's recall the memory layout of a `String` from our 
-[previous exploration](../03_ticket_v1/09_heap.md).
-If we run:
-
-```rust
-let mut s = String::with_capacity(5);
-s.push_str("Hello");
-```
-
-we'll get this scenario in memory:
-
-```text
-      +---------+--------+----------+
-Stack | pointer | length | capacity | 
-      |  |      |   5    |    5     |
-      +--|------+--------+----------+
-         |
-         |
-         v
-       +---+---+---+---+---+
-Heap:  | H | e | l | l | o |
-       +---+---+---+---+---+
-```
-
-If you remember, we've [also examined](../03_ticket_v1/10_references_in_memory.md) 
-how a `&String` is laid out in memory:
-
-```text
-      --------------------------------------
-      |                                    |         
- +----v----+--------+----------+      +----|----+
- | pointer | length | capacity |      | pointer |
- |    |    |   5    |    5     |      |         |
- +----|----+--------+----------+      +---------+
-      |        s                          &s 
-      |       
-      v       
-    +---+---+---+---+---+
-    | H | e | l | l | o |
-    +---+---+---+---+---+
-```
-
-`&String` points to the memory location where the `String`'s metadata is stored.  
-If we follow the pointer, we get to the heap-allocated data. In particular, we get to the first byte of the string, `H`.
-
-What if we wanted a type that represents a **substring** of `s`? E.g. `ello` in `Hello`?
-
-## String slices
-
-A `&str` is a **view** into a string, a **reference** to a sequence of UTF-8 bytes stored elsewhere.
-You can, for example, create a `&str` from a `String` like this:
-
-```rust
-let mut s = String::with_capacity(5);
-s.push_str("Hello");
-// Create a string slice reference from the `String`, skipping the first byte.
-let slice: &str = &s[1..];
-```
-
-In memory, it'd look like this:
-
-```text
-                    s                              slice
-      +---------+--------+----------+      +---------+--------+
-Stack | pointer | length | capacity |      | pointer | length |
-      |    |    |   5    |    5     |      |    |    |   4    |
-      +----|----+--------+----------+      +----|----+--------+
-           |        s                           |  
-           |                                    |
-           v                                    | 
-         +---+---+---+---+---+                  |
-Heap:    | H | e | l | l | o |                  |
-         +---+---+---+---+---+                  |
-               ^                                |
-               |                                |
-               +--------------------------------+
-```
-
-`slice` stores two pieces of information on the stack:
-
-- A pointer to the first byte of the slice.
-- The length of the slice.
-
-`slice` doesn't own the data, it just points to it: it's a **reference** to the `String`'s heap-allocated data.  
-When `slice` is dropped, the heap-allocated data won't be deallocated, because it's still owned by `s`.
-That's why `slice` doesn't have a `capacity` field: it doesn't own the data, so it doesn't need to know how much 
-space it was allocated for it; it only cares about the data it references.
-
-## `&str` vs `&String`
-
-As a rule of thumb, use `&str` rather than `&String` whenever you need a reference to textual data.  
-`&str` is more flexible and generally considered more idiomatic in Rust code.
-
-If a method returns a `&String`, you're promising that there is heap-allocated UTF-8 text somewhere that 
-**matches exactly** the one you're returning a reference to.  
-If a method returns a `&str`, instead, you have a lot more freedom: you're just saying that *somewhere* there's a 
-bunch of text data and that a subset of it matches what you need, therefore you're returning a reference to it.
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/05_str_slice`
diff --git a/book/src/04_traits/05_trait_bounds.md b/book/src/04_traits/05_trait_bounds.md
new file mode 100644 (file)
index 0000000..9c3a50c
--- /dev/null
@@ -0,0 +1,154 @@
+# Trait bounds 
+
+We've seen two use cases for traits so far:
+
+- Unlocking "built-in" behaviour (e.g. operator overloading)
+- Adding new behaviour to existing types (i.e. extension traits)
+
+There's a third use case: **generic programming**.  
+
+## The problem
+
+All our functions and methods, so far, have been working with **concrete types**.  
+Code that operates on concrete types is usually straightforward to write and understand. But it's also
+limited in its reusability.  
+Let's imagine, for example, that we want to write a function that returns `true` if an integer is even.
+Working with concrete types, we'd have to write a separate function for each integer type we want to 
+support:
+
+```rust
+fn is_even_i32(n: i32) -> bool {
+    n % 2 == 0
+}
+
+fn is_even_i64(n: i64) -> bool {
+    n % 2 == 0
+}
+
+// Etc.
+```
+
+Alternatively, we could write a single extension trait and then different implementations for each integer type:
+
+```rust
+trait IsEven {
+    fn is_even(&self) -> bool;
+}
+
+impl IsEven for i32 {
+    fn is_even(&self) -> bool {
+        self % 2 == 0
+    }
+}
+
+impl IsEven for i64 {
+    fn is_even(&self) -> bool {
+        self % 2 == 0
+    }
+}
+
+// Etc.
+```
+
+The duplication remains.
+
+## Generic programming
+
+We can do better using **generics**.  
+Generics allow us to write that works with a **type parameter** instead of a concrete type:
+
+```rust
+fn print_if_even<T>(n: T) 
+where
+    T: IsEven + Debug
+{
+    if n.is_even() {
+        println!("{n:?} is even");
+    }
+}
+```
+
+`print_if_even` is a **generic function**.  
+It isn't tied to a specific input type. Instead, it works with any type `T` that:
+
+- Implements the `IsEven` trait.
+- Implements the `Debug` trait.
+
+This contract is expressed with a **trait bound**: `T: IsEven + Debug`.  
+The `+` symbol is used to require that `T` implements multiple traits. `T: IsEven + Debug` is equivalent to
+"where `T` implements `IsEven` **and** `Debug`".
+
+## Trait bounds
+
+What purpose do trait bounds serve in `print_if_even`?  
+To find out, let's try to remove them:
+
+```rust
+fn print_if_even<T>(n: T) {
+    if n.is_even() {
+        println!("{n:?} is even");
+    }
+}
+```
+
+This code won't compile:
+
+```text
+error[E0599]: no method named `is_even` found for type parameter `T` in the current scope
+ --> src/lib.rs:2:10
+  |
+1 | fn print_if_even<T>(n: T) {
+  |                  - method `is_even` not found for this type parameter
+2 |     if n.is_even() {
+  |          ^^^^^^^ method not found in `T`
+
+error[E0277]: `T` doesn't implement `Debug`
+ --> src/lib.rs:3:19
+  |
+3 |         println!("{n:?} is even");
+  |                   ^^^^^ `T` cannot be formatted using `{:?}` because it doesn't implement `Debug`
+  |
+help: consider restricting type parameter `T`
+  |
+1 | fn print_if_even<T: std::fmt::Debug>(n: T) {
+  |                   +++++++++++++++++
+```
+
+Without trait bounds, the compiler doesn't know what `T` **can do**.  
+It doesn't know that `T` has an `is_even` method, and it doesn't know how to format `T` for printing. 
+Trait bounds restrict the set of types that can be used by ensuring that the behaviour required by the function
+body is present.
+
+## Inlining trait bounds
+
+All the examples above used a **`where` clause** to specify trait bounds:
+
+```rust
+fn print_if_even<T>(n: T) 
+where
+    T: IsEven + Debug
+//  ^^^^^^^^^^^^^^^^^
+//  This is a `where` clause
+{
+    // [...]
+}
+```
+
+If the trait bounds are simple, you can **inline** them directly next to the type parameter:
+
+```rust
+fn print_if_even<T: IsEven + Debug>(n: T) {
+    //           ^^^^^^^^^^^^^^^^^
+    //           This is an inline trait bound
+    // [...]
+}
+```
+
+## The function signature is king
+
+You may wonder why we need trait bounds at all. Can't the compiler infer the required traits from the function's body?  
+It could, but it won't.  
+The rationale is the same as for [explicit type annotations on function parameters](../02_basic_calculator/02_variables#function-arguments-are-variables): 
+each function signature is a contract between the caller and the callee, and the terms must be explicitly stated. 
+This allows for better error messages, better documentation, less unintentional breakages across versions, 
+and faster compilation times.
diff --git a/book/src/04_traits/06_deref.md b/book/src/04_traits/06_deref.md
deleted file mode 100644 (file)
index 6947f7e..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-# `Deref` trait
-
-In the previous exercise you didn't have to do much, did you?  
-
-Changing
-
-```rust
-impl Ticket {
-    pub fn title(&self) -> &String {
-        &self.title
-    }
-}
-```
-
-to
-
-```rust
-impl Ticket {
-    pub fn title(&self) -> &str {
-        &self.title
-    }
-}
-```
-
-was all you needed to do to get the code to compile and the tests to pass. 
-Some alarm bells should be ringing in your head though. 
-
-## It shouldn't work, but it does
-
-Let's review the facts:
-
-- `self.title` is a `String`
-- `&self.title` is, therefore, a `&String`
-- The output of the (modified) `title` method is `&str`
-
-You would expect a compiler error, wouldn't you? `Expected &String, found &str` or something similar.
-Instead, it just works. **Why**?
-
-## `Deref` to the rescue
-
-The `Deref` trait is the mechanism behind the language feature known as [**deref coercion**](https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion).  
-The trait is defined in the standard library, in the `std::ops` module:
-
-```rust
-// I've slightly simplified the definition for now.
-// We'll see the full definition later on.
-pub trait Deref {
-    type Target;
-    
-    fn deref(&self) -> &Self::Target;
-}
-```
-
-`type Target` is an **associated type**.  
-It's a placeholder for a concrete type that must be specified when the trait is implemented.
-
-## Deref coercion
-
-By implementing `Deref<Target = U>` for a type `T` you're telling the compiler that `&T` and `&U` are 
-somewhat interchangeable.  
-In particular, you get the following behavior:
-
-- References to `T` are implicitly converted into references to `U` (i.e. `&T` becomes `&U`)
-- You can call on `&T` all the methods defined on `U` that take `&self` as input.
-
-There is one more thing around the dereference operator, `*`, but we don't need it yet (see `std`'s docs
-if you're curious).
-
-## `String` implements `Deref`
-
-`String` implements `Deref` with `Target = str`:
-
-```rust
-impl Deref for String {
-    type Target = str;
-    
-    fn deref(&self) -> &str {
-        // [...]
-    }
-}
-```
-
-Thanks to this implementation and deref coercion, a `&String` is automatically converted into a `&str` when needed.
-
-## Don't abuse deref coercion
-
-Deref coercion is a powerful feature, but it can lead to confusion.  
-Automatically converting types can make the code harder to read and understand. If a method with the same name
-is defined on both `T` and `U`, which one will be called?  
-
-We'll examine later in the course the "safest" use cases for deref coercion: smart pointers.  
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/06_deref`
diff --git a/book/src/04_traits/06_str_slice.md b/book/src/04_traits/06_str_slice.md
new file mode 100644 (file)
index 0000000..4b78f1c
--- /dev/null
@@ -0,0 +1,120 @@
+# String slices
+
+Throughout the previous chapters you've seen quite a few **string literals** being used in the code, 
+like `"To-Do"` or `"A ticket description"`.
+They were always followed by a call to `.to_string()` or `.into()`. It's time to understand why!
+
+## String literals
+
+You define a string literal by enclosing the raw text in double quotes:
+
+```rust
+let s = "Hello, world!";
+```
+
+The type of `s` is `&str`, a **reference to a string slice**.  
+
+## Memory layout
+
+`&str` and `String` are different types—they're not interchangeable.  
+Let's recall the memory layout of a `String` from our 
+[previous exploration](../03_ticket_v1/09_heap.md).
+If we run:
+
+```rust
+let mut s = String::with_capacity(5);
+s.push_str("Hello");
+```
+
+we'll get this scenario in memory:
+
+```text
+      +---------+--------+----------+
+Stack | pointer | length | capacity | 
+      |  |      |   5    |    5     |
+      +--|------+--------+----------+
+         |
+         |
+         v
+       +---+---+---+---+---+
+Heap:  | H | e | l | l | o |
+       +---+---+---+---+---+
+```
+
+If you remember, we've [also examined](../03_ticket_v1/10_references_in_memory.md) 
+how a `&String` is laid out in memory:
+
+```text
+      --------------------------------------
+      |                                    |         
+ +----v----+--------+----------+      +----|----+
+ | pointer | length | capacity |      | pointer |
+ |    |    |   5    |    5     |      |         |
+ +----|----+--------+----------+      +---------+
+      |        s                          &s 
+      |       
+      v       
+    +---+---+---+---+---+
+    | H | e | l | l | o |
+    +---+---+---+---+---+
+```
+
+`&String` points to the memory location where the `String`'s metadata is stored.  
+If we follow the pointer, we get to the heap-allocated data. In particular, we get to the first byte of the string, `H`.
+
+What if we wanted a type that represents a **substring** of `s`? E.g. `ello` in `Hello`?
+
+## String slices
+
+A `&str` is a **view** into a string, a **reference** to a sequence of UTF-8 bytes stored elsewhere.
+You can, for example, create a `&str` from a `String` like this:
+
+```rust
+let mut s = String::with_capacity(5);
+s.push_str("Hello");
+// Create a string slice reference from the `String`, skipping the first byte.
+let slice: &str = &s[1..];
+```
+
+In memory, it'd look like this:
+
+```text
+                    s                              slice
+      +---------+--------+----------+      +---------+--------+
+Stack | pointer | length | capacity |      | pointer | length |
+      |    |    |   5    |    5     |      |    |    |   4    |
+      +----|----+--------+----------+      +----|----+--------+
+           |        s                           |  
+           |                                    |
+           v                                    | 
+         +---+---+---+---+---+                  |
+Heap:    | H | e | l | l | o |                  |
+         +---+---+---+---+---+                  |
+               ^                                |
+               |                                |
+               +--------------------------------+
+```
+
+`slice` stores two pieces of information on the stack:
+
+- A pointer to the first byte of the slice.
+- The length of the slice.
+
+`slice` doesn't own the data, it just points to it: it's a **reference** to the `String`'s heap-allocated data.  
+When `slice` is dropped, the heap-allocated data won't be deallocated, because it's still owned by `s`.
+That's why `slice` doesn't have a `capacity` field: it doesn't own the data, so it doesn't need to know how much 
+space it was allocated for it; it only cares about the data it references.
+
+## `&str` vs `&String`
+
+As a rule of thumb, use `&str` rather than `&String` whenever you need a reference to textual data.  
+`&str` is more flexible and generally considered more idiomatic in Rust code.
+
+If a method returns a `&String`, you're promising that there is heap-allocated UTF-8 text somewhere that 
+**matches exactly** the one you're returning a reference to.  
+If a method returns a `&str`, instead, you have a lot more freedom: you're just saying that *somewhere* there's a 
+bunch of text data and that a subset of it matches what you need, therefore you're returning a reference to it.
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/05_str_slice`
diff --git a/book/src/04_traits/07_deref.md b/book/src/04_traits/07_deref.md
new file mode 100644 (file)
index 0000000..6947f7e
--- /dev/null
@@ -0,0 +1,95 @@
+# `Deref` trait
+
+In the previous exercise you didn't have to do much, did you?  
+
+Changing
+
+```rust
+impl Ticket {
+    pub fn title(&self) -> &String {
+        &self.title
+    }
+}
+```
+
+to
+
+```rust
+impl Ticket {
+    pub fn title(&self) -> &str {
+        &self.title
+    }
+}
+```
+
+was all you needed to do to get the code to compile and the tests to pass. 
+Some alarm bells should be ringing in your head though. 
+
+## It shouldn't work, but it does
+
+Let's review the facts:
+
+- `self.title` is a `String`
+- `&self.title` is, therefore, a `&String`
+- The output of the (modified) `title` method is `&str`
+
+You would expect a compiler error, wouldn't you? `Expected &String, found &str` or something similar.
+Instead, it just works. **Why**?
+
+## `Deref` to the rescue
+
+The `Deref` trait is the mechanism behind the language feature known as [**deref coercion**](https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion).  
+The trait is defined in the standard library, in the `std::ops` module:
+
+```rust
+// I've slightly simplified the definition for now.
+// We'll see the full definition later on.
+pub trait Deref {
+    type Target;
+    
+    fn deref(&self) -> &Self::Target;
+}
+```
+
+`type Target` is an **associated type**.  
+It's a placeholder for a concrete type that must be specified when the trait is implemented.
+
+## Deref coercion
+
+By implementing `Deref<Target = U>` for a type `T` you're telling the compiler that `&T` and `&U` are 
+somewhat interchangeable.  
+In particular, you get the following behavior:
+
+- References to `T` are implicitly converted into references to `U` (i.e. `&T` becomes `&U`)
+- You can call on `&T` all the methods defined on `U` that take `&self` as input.
+
+There is one more thing around the dereference operator, `*`, but we don't need it yet (see `std`'s docs
+if you're curious).
+
+## `String` implements `Deref`
+
+`String` implements `Deref` with `Target = str`:
+
+```rust
+impl Deref for String {
+    type Target = str;
+    
+    fn deref(&self) -> &str {
+        // [...]
+    }
+}
+```
+
+Thanks to this implementation and deref coercion, a `&String` is automatically converted into a `&str` when needed.
+
+## Don't abuse deref coercion
+
+Deref coercion is a powerful feature, but it can lead to confusion.  
+Automatically converting types can make the code harder to read and understand. If a method with the same name
+is defined on both `T` and `U`, which one will be called?  
+
+We'll examine later in the course the "safest" use cases for deref coercion: smart pointers.  
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/06_deref`
diff --git a/book/src/04_traits/07_sized.md b/book/src/04_traits/07_sized.md
deleted file mode 100644 (file)
index ddbfbdc..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-# `Sized`
-
-There's more to `&str` than meets the eye, even after having 
-investigated deref coercion.  
-From our previous [discussion on memory layouts](../03_ticket_v1/10_references_in_memory.md),
-it would have been reasonable to expect `&str` to be represented as a single `usize` on
-the stack, a pointer. That's not the case though. `&str` stores some **metadata** next 
-to the pointer: the length of the slice it points to. Going back to the example from 
-[a previous section](05_str_slice.md):
-
-```rust
-let mut s = String::with_capacity(5);
-s.push_str("Hello");
-// Create a string slice reference from the `String`, skipping the first byte.
-let slice: &str = &s[1..];
-```
-
-In memory, we get:
-
-```text
-                    s                              slice
-      +---------+--------+----------+      +---------+--------+
-Stack | pointer | length | capacity |      | pointer | length |
-      |    |    |   5    |    5     |      |    |    |   4    |
-      +----|----+--------+----------+      +----|----+--------+
-           |        s                           |  
-           |                                    |
-           v                                    | 
-         +---+---+---+---+---+                  |
-Heap:    | H | e | l | l | o |                  |
-         +---+---+---+---+---+                  |
-               ^                                |
-               |                                |
-               +--------------------------------+
-```
-
-What's going on?
-
-## Dynamically sized types
-
-`str` is a **dynamically sized type** (DST).  
-A DST is a type whose size is not known at compile time. Whenever you have a 
-reference to a DST, like `&str`, it has to include additional
-information about the data it points to. It is a **fat pointer**.  
-In the case of `&str`, it stores the length of the slice it points to. 
-We'll see more examples of DSTs in the rest of the course.
-
-## The `Sized` trait
-
-Rust's `std` library defines a trait called `Sized`.  
-
-```rust
-pub trait Sized {
-    // This is an empty trait, no methods to implement.
-}
-```
-
-A type is `Sized` if its size is known at compile time. In other words, it's not a DST.
-
-### Marker traits
-
-`Sized` is your first example of a **marker trait**.  
-A marker trait is a trait that doesn't require any methods to be implemented. It doesn't define any behavior.
-It only serves to **mark** a type as having certain properties.
-The mark is then leveraged by the compiler to enable certain behaviors or optimizations. 
-
-### Auto traits
-
-In particular, `Sized` is also an **auto trait**.  
-You don't need to implement it explicitly; the compiler implements it automatically for you
-based on the type's definition.
-
-### Examples
-
-All the types we've seen so far are `Sized`: `u32`, `String`, `bool`, etc.
-
-`str`, as we just saw, is not `Sized`.  
-`&str` is `Sized` though! We know its size at compile time: two `usize`s, one for the pointer 
-and one for the length.
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/07_sized`
diff --git a/book/src/04_traits/08_from.md b/book/src/04_traits/08_from.md
deleted file mode 100644 (file)
index ee48205..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-# `From` and `Into`
-
-Let's go back to where our string journey started:
-
-```rust
-let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into());
-```
-
-We can now know enough to start unpacking what `.into()` is doing here.
-
-## The problem
-
-This is the signature of the `new` method:
-
-```rust
-impl Ticket {
-    pub fn new(title: String, description: String, status: String) -> Self {
-        // [...]
-    }
-}
-```
-
-We've also seen that string literals (such as `"A title"`) are of type `&str`.  
-We have a type mismatch here: a `String` is expected, but we have a `&str`. 
-No magical coercion will come to save us this time; we need **to perform a conversion**.
-
-## `From` and `Into`
-
-The Rust standard library defines two traits for **infallible conversions**: `From` and `Into`, 
-in the `std::convert` module.
-
-```rust
-pub trait From<T>: Sized {
-    fn from(value: T) -> Self;
-}
-
-pub trait Into<T>: Sized {
-    fn into(self) -> T;
-}
-```
-
-These trait definitions showcase a few concepts that we haven't seen before: **supertraits**, **generics**, 
-and **implicit trait bounds**. Let's unpack those first.
-
-### Supertrait / Subtrait
-
-The `From: Sized` syntax implies that `From` is a **subtrait** of `Sized`: any type that
-implements `From` must also implement `Sized`.
-Alternatively, you could say that `Sized` is a **supertrait** of `From`.
-
-### Generics
-
-Both `From` and `Into` are **generic traits**.  
-They take a type parameter, `T`, to refer to the type being converted from or into.
-`T` is a placeholder for the actual type, which will be specified when the trait is implemented or used.
-
-### Implicit trait bounds
-
-Every time you have a generic type parameter, the compiler implicitly assumes that it's `Sized`.
-
-For example:
-
-```rust
-pub struct Foo<T> {
-    inner: T,
-}
-```
-
-is actually equivalent to:
-
-```rust
-pub struct Foo<T> 
-where
-    T: Sized,
-//  ^^^^^^^^^
-//  This is known as a **trait bound**
-//  It specifies that this implementation applies exclusively
-//  to types `T` that implement `Sized`
-//  You can require multiple traits to be implemented using 
-//  the `+` sign. E.g. `Sized + PartialEq<T>`
-{
-    inner: T,
-}
-```
-
-You can opt out of this behavior by using a **negative trait bound**:
-
-```rust
-// You can also choose to inline trait bounds,
-// rather than using `where` clauses
-
-pub struct Foo<T: ?Sized> {
-    //            ^^^^^^^
-    //            This is a negative trait bound
-    inner: T,
-}
-```
-
-This syntax reads as "`T` may or may not be `Sized`", and it allows you to
-bind `T` to a DST (e.g. `Foo<str>`).  
-In the case of `From<T>`, we want _both_ `T` and the type implementing `From<T>` to be `Sized`, even
-though the former bound is implicit.
-
-## `&str` to `String`
-
-In [`std`'s documentation](https://doc.rust-lang.org/std/convert/trait.From.html#implementors) 
-you can see which `std` types implement the `From` trait.  
-You'll find that `String` implements `From<&str> for String`. Thus, we can write:
-
-```rust
-let title = String::from("A title");
-```
-
-We've been primarily using `.into()`, though.  
-If you check out the [implementors of `Into`](https://doc.rust-lang.org/std/convert/trait.Into.html#implementors)
-you won't find `Into<&str> for String`. What's going on?
-
-`From` and `Into` are **dual traits**.  
-In particular, `Into` is implemented for any type that implements `From` using a **blanket implementation**:
-
-```rust
-impl<T, U> Into<U> for T
-where
-    U: From<T>,
-{
-    fn into(self) -> U {
-        U::from(self)
-    }
-}
-```
-
-If a type `U` implements `From<T>`, then `Into<U> for T` is automatically implemented. That's why
-we can write `let title = "A title".into();`.
-
-## `.into()`
-
-Every time you see `.into()`, you're witnessing a conversion between types.  
-What's the target type, though?
-
-In most cases, the target type is either:
-
-- Specified by the signature of a function/method (e.g. `Ticket::new` in our example above)
-- Specified in the variable declaration with a type annotation (e.g. `let title: String = "A title".into();`)
-
-`.into()` will work out of the box as long as the compiler can infer the target type from the context without ambiguity.
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/08_from`
diff --git a/book/src/04_traits/08_sized.md b/book/src/04_traits/08_sized.md
new file mode 100644 (file)
index 0000000..ddbfbdc
--- /dev/null
@@ -0,0 +1,83 @@
+# `Sized`
+
+There's more to `&str` than meets the eye, even after having 
+investigated deref coercion.  
+From our previous [discussion on memory layouts](../03_ticket_v1/10_references_in_memory.md),
+it would have been reasonable to expect `&str` to be represented as a single `usize` on
+the stack, a pointer. That's not the case though. `&str` stores some **metadata** next 
+to the pointer: the length of the slice it points to. Going back to the example from 
+[a previous section](05_str_slice.md):
+
+```rust
+let mut s = String::with_capacity(5);
+s.push_str("Hello");
+// Create a string slice reference from the `String`, skipping the first byte.
+let slice: &str = &s[1..];
+```
+
+In memory, we get:
+
+```text
+                    s                              slice
+      +---------+--------+----------+      +---------+--------+
+Stack | pointer | length | capacity |      | pointer | length |
+      |    |    |   5    |    5     |      |    |    |   4    |
+      +----|----+--------+----------+      +----|----+--------+
+           |        s                           |  
+           |                                    |
+           v                                    | 
+         +---+---+---+---+---+                  |
+Heap:    | H | e | l | l | o |                  |
+         +---+---+---+---+---+                  |
+               ^                                |
+               |                                |
+               +--------------------------------+
+```
+
+What's going on?
+
+## Dynamically sized types
+
+`str` is a **dynamically sized type** (DST).  
+A DST is a type whose size is not known at compile time. Whenever you have a 
+reference to a DST, like `&str`, it has to include additional
+information about the data it points to. It is a **fat pointer**.  
+In the case of `&str`, it stores the length of the slice it points to. 
+We'll see more examples of DSTs in the rest of the course.
+
+## The `Sized` trait
+
+Rust's `std` library defines a trait called `Sized`.  
+
+```rust
+pub trait Sized {
+    // This is an empty trait, no methods to implement.
+}
+```
+
+A type is `Sized` if its size is known at compile time. In other words, it's not a DST.
+
+### Marker traits
+
+`Sized` is your first example of a **marker trait**.  
+A marker trait is a trait that doesn't require any methods to be implemented. It doesn't define any behavior.
+It only serves to **mark** a type as having certain properties.
+The mark is then leveraged by the compiler to enable certain behaviors or optimizations. 
+
+### Auto traits
+
+In particular, `Sized` is also an **auto trait**.  
+You don't need to implement it explicitly; the compiler implements it automatically for you
+based on the type's definition.
+
+### Examples
+
+All the types we've seen so far are `Sized`: `u32`, `String`, `bool`, etc.
+
+`str`, as we just saw, is not `Sized`.  
+`&str` is `Sized` though! We know its size at compile time: two `usize`s, one for the pointer 
+and one for the length.
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/07_sized`
diff --git a/book/src/04_traits/09_assoc_vs_generic.md b/book/src/04_traits/09_assoc_vs_generic.md
deleted file mode 100644 (file)
index 8961777..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-# Generics and associated types
-
-Let's re-examine the definition for two of the traits we studied so far, `From` and `Deref`:
-
-```rust
-pub trait From<T> {
-    fn from(value: T) -> Self;
-}
-
-pub trait Deref {
-    type Target;
-    
-    fn deref(&self) -> &Self::Target;
-}
-```
-
-They both feature type parameters.  
-In the case of `From`, it's a generic parameter, `T`.  
-In the case of `Deref`, it's an associated type, `Target`.
-
-What's the difference? Why use one over the other?
-
-## At most one implementation
-
-Due to how deref coercion works, there can only be one "target" type for a given type. E.g. `String` can 
-only deref to `str`. 
-It's about avoiding ambiguity: if you could implement `Deref` multiple times for a type,
-which `Target` type should the compiler choose when you call a `&self` method?
-
-That's why `Deref` uses an associated type, `Target`.  
-An associated type is uniquely determined **by the trait implementation**.
-Since you can't implement `Deref` more than once, you'll only be able to specify one `Target` for a given type
-and there won't be any ambiguity.
-
-## Generic traits
-
-On the other hand, you can implement `From` multiple times for a type, **as long as the input type `T` is different**.
-For example, you can implement `From` for `WrappingU32` using both `u32` and `u16` as input types:
-
-```rust
-impl From<u32> for WrappingU32 {
-    fn from(value: u32) -> Self {
-        WrappingU32 { inner: value }
-    }
-}
-
-impl From<u16> for WrappingU32 {
-    fn from(value: u16) -> Self {
-        WrappingU32 { inner: value.into() }
-    }
-}
-```
-
-This works because `From<u16>` and `From<u32>` are considered **different traits**.  
-There is no ambiguity: the compiler can determine which implementation to use based on type of the value being converted.
-
-## Case study: `Add`
-
-As a closing example, consider the `Add` trait from the standard library:
-
-```rust
-pub trait Add<RHS = Self> {
-    type Output;
-    
-    fn add(self, rhs: RHS) -> Self::Output;
-}
-```
-
-It uses both mechanisms:
-
-- it has a generic parameter, `RHS` (right-hand side), which defaults to `Self`
-- it has an associated type, `Output`, the type of the result of the addition
-
-### `RHS`
-
-`RHS` is a generic parameter to allow for different types to be added together.  
-For example, you'll find these two implementations in the standard library:
-
-```rust
-impl Add<u32> for u32 {
-    type Output = u32;
-    
-    fn add(self, rhs: u32) -> u32 {
-      // [...]
-    }
-}
-
-impl Add<&u32> for u32 {
-    type Output = u32;
-    
-    fn add(self, rhs: &u32) -> u32 {
-        // [...]
-    }
-}
-```
-
-This allows the following code to compile:
-
-```rust
-let x = 5u32 + &5u32 + 6u32;
-```
-
-because `u32` implements `Add<&u32>` _as well as_ `Add<u32>`.
-
-### `Output`
-
-`Output`, on the other hand, **must** be uniquely determined once the types of the operands
-are known. That's why it's an associated type instead of a second generic parameter.
-
-To recap:
-
-- Use an **associated type** when the type must be uniquely determined for a given trait implementation.
-- Use a **generic parameter** when you want to allow multiple implementations of the trait for the same type, 
-  with different input types.
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/09_assoc_vs_generic`
diff --git a/book/src/04_traits/09_from.md b/book/src/04_traits/09_from.md
new file mode 100644 (file)
index 0000000..4048955
--- /dev/null
@@ -0,0 +1,133 @@
+# `From` and `Into`
+
+Let's go back to where our string journey started:
+
+```rust
+let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into());
+```
+
+We can now know enough to start unpacking what `.into()` is doing here.
+
+## The problem
+
+This is the signature of the `new` method:
+
+```rust
+impl Ticket {
+    pub fn new(title: String, description: String, status: String) -> Self {
+        // [...]
+    }
+}
+```
+
+We've also seen that string literals (such as `"A title"`) are of type `&str`.  
+We have a type mismatch here: a `String` is expected, but we have a `&str`. 
+No magical coercion will come to save us this time; we need **to perform a conversion**.
+
+## `From` and `Into`
+
+The Rust standard library defines two traits for **infallible conversions**: `From` and `Into`, 
+in the `std::convert` module.
+
+```rust
+pub trait From<T>: Sized {
+    fn from(value: T) -> Self;
+}
+
+pub trait Into<T>: Sized {
+    fn into(self) -> T;
+}
+```
+
+These trait definitions showcase a few concepts that we haven't seen before: **supertraits** and **implicit trait bounds**. 
+Let's unpack those first.
+
+### Supertrait / Subtrait
+
+The `From: Sized` syntax implies that `From` is a **subtrait** of `Sized`: any type that
+implements `From` must also implement `Sized`.
+Alternatively, you could say that `Sized` is a **supertrait** of `From`.
+
+### Implicit trait bounds
+
+Every time you have a generic type parameter, the compiler implicitly assumes that it's `Sized`.
+
+For example:
+
+```rust
+pub struct Foo<T> {
+    inner: T,
+}
+```
+
+is actually equivalent to:
+
+```rust
+pub struct Foo<T: Sized> 
+{
+    inner: T,
+}
+```
+
+You can opt out of this behavior by using a **negative trait bound**:
+
+```rust
+pub struct Foo<T: ?Sized> {
+    //            ^^^^^^^
+    //            This is a negative trait bound
+    inner: T,
+}
+```
+
+This syntax reads as "`T` may or may not be `Sized`", and it allows you to
+bind `T` to a DST (e.g. `Foo<str>`). It is a special case, though: negative trait bounds are exclusive to `Sized`,
+you can't use them with other traits.  
+In the case of `From<T>`, we want _both_ `T` and the type implementing `From<T>` to be `Sized`, even
+though the former bound is implicit.
+
+## `&str` to `String`
+
+In [`std`'s documentation](https://doc.rust-lang.org/std/convert/trait.From.html#implementors) 
+you can see which `std` types implement the `From` trait.  
+You'll find that `String` implements `From<&str> for String`. Thus, we can write:
+
+```rust
+let title = String::from("A title");
+```
+
+We've been primarily using `.into()`, though.  
+If you check out the [implementors of `Into`](https://doc.rust-lang.org/std/convert/trait.Into.html#implementors)
+you won't find `Into<&str> for String`. What's going on?
+
+`From` and `Into` are **dual traits**.  
+In particular, `Into` is implemented for any type that implements `From` using a **blanket implementation**:
+
+```rust
+impl<T, U> Into<U> for T
+where
+    U: From<T>,
+{
+    fn into(self) -> U {
+        U::from(self)
+    }
+}
+```
+
+If a type `U` implements `From<T>`, then `Into<U> for T` is automatically implemented. That's why
+we can write `let title = "A title".into();`.
+
+## `.into()`
+
+Every time you see `.into()`, you're witnessing a conversion between types.  
+What's the target type, though?
+
+In most cases, the target type is either:
+
+- Specified by the signature of a function/method (e.g. `Ticket::new` in our example above)
+- Specified in the variable declaration with a type annotation (e.g. `let title: String = "A title".into();`)
+
+`.into()` will work out of the box as long as the compiler can infer the target type from the context without ambiguity.
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/08_from`
diff --git a/book/src/04_traits/10_assoc_vs_generic.md b/book/src/04_traits/10_assoc_vs_generic.md
new file mode 100644 (file)
index 0000000..8961777
--- /dev/null
@@ -0,0 +1,118 @@
+# Generics and associated types
+
+Let's re-examine the definition for two of the traits we studied so far, `From` and `Deref`:
+
+```rust
+pub trait From<T> {
+    fn from(value: T) -> Self;
+}
+
+pub trait Deref {
+    type Target;
+    
+    fn deref(&self) -> &Self::Target;
+}
+```
+
+They both feature type parameters.  
+In the case of `From`, it's a generic parameter, `T`.  
+In the case of `Deref`, it's an associated type, `Target`.
+
+What's the difference? Why use one over the other?
+
+## At most one implementation
+
+Due to how deref coercion works, there can only be one "target" type for a given type. E.g. `String` can 
+only deref to `str`. 
+It's about avoiding ambiguity: if you could implement `Deref` multiple times for a type,
+which `Target` type should the compiler choose when you call a `&self` method?
+
+That's why `Deref` uses an associated type, `Target`.  
+An associated type is uniquely determined **by the trait implementation**.
+Since you can't implement `Deref` more than once, you'll only be able to specify one `Target` for a given type
+and there won't be any ambiguity.
+
+## Generic traits
+
+On the other hand, you can implement `From` multiple times for a type, **as long as the input type `T` is different**.
+For example, you can implement `From` for `WrappingU32` using both `u32` and `u16` as input types:
+
+```rust
+impl From<u32> for WrappingU32 {
+    fn from(value: u32) -> Self {
+        WrappingU32 { inner: value }
+    }
+}
+
+impl From<u16> for WrappingU32 {
+    fn from(value: u16) -> Self {
+        WrappingU32 { inner: value.into() }
+    }
+}
+```
+
+This works because `From<u16>` and `From<u32>` are considered **different traits**.  
+There is no ambiguity: the compiler can determine which implementation to use based on type of the value being converted.
+
+## Case study: `Add`
+
+As a closing example, consider the `Add` trait from the standard library:
+
+```rust
+pub trait Add<RHS = Self> {
+    type Output;
+    
+    fn add(self, rhs: RHS) -> Self::Output;
+}
+```
+
+It uses both mechanisms:
+
+- it has a generic parameter, `RHS` (right-hand side), which defaults to `Self`
+- it has an associated type, `Output`, the type of the result of the addition
+
+### `RHS`
+
+`RHS` is a generic parameter to allow for different types to be added together.  
+For example, you'll find these two implementations in the standard library:
+
+```rust
+impl Add<u32> for u32 {
+    type Output = u32;
+    
+    fn add(self, rhs: u32) -> u32 {
+      // [...]
+    }
+}
+
+impl Add<&u32> for u32 {
+    type Output = u32;
+    
+    fn add(self, rhs: &u32) -> u32 {
+        // [...]
+    }
+}
+```
+
+This allows the following code to compile:
+
+```rust
+let x = 5u32 + &5u32 + 6u32;
+```
+
+because `u32` implements `Add<&u32>` _as well as_ `Add<u32>`.
+
+### `Output`
+
+`Output`, on the other hand, **must** be uniquely determined once the types of the operands
+are known. That's why it's an associated type instead of a second generic parameter.
+
+To recap:
+
+- Use an **associated type** when the type must be uniquely determined for a given trait implementation.
+- Use a **generic parameter** when you want to allow multiple implementations of the trait for the same type, 
+  with different input types.
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/09_assoc_vs_generic`
diff --git a/book/src/04_traits/10_clone.md b/book/src/04_traits/10_clone.md
deleted file mode 100644 (file)
index a897b48..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-# Copying values, pt. 1
-
-In the previous chapter we introduced ownership and borrowing.  
-We stated, in particular, that:
-
-- Every value in Rust has a single owner at any given time.
-- When a function takes ownership of a value ("it consumes it"), the caller can't use that value anymore.
-
-These restrictions can be somewhat limiting.  
-Sometimes we might have to call a function that takes ownership of a value, but we still need to use 
-that value afterward.
-
-```rust
-fn consumer(s: String) { /* */ }
-
-fn example() {
-     let mut s = String::from("hello");
-     consumer(s);
-     s.push_str(", world!"); // error: value borrowed here after move
-}
-```
-
-That's where `Clone` comes in.
-
-## `Clone`
-
-`Clone` is a trait defined in Rust's standard library:
-
-```rust
-pub trait Clone {
-    fn clone(&self) -> Self;
-}
-```
-
-Its method, `clone`, takes a reference to `self` and returns a new **owned** instance of the same type.
-
-## In action
-
-Going back to the example above, we can use `clone` to create a new `String` instance before calling `consumer`:
-
-```rust
-fn consumer(s: String) { /* */ }
-
-fn example() {
-     let mut s = String::from("hello");
-     let t = s.clone();
-     consumer(t);
-     s.push_str(", world!"); // no error
-}
-```
-
-Instead of giving ownership of `s` to `consumer`, we create a new `String` (by cloning `s`) and give 
-that to `consumer` instead.  
-`s` remains valid and usable after the call to `consumer`.
-
-## In memory
-
-Let's look at what happened in memory in the example above.
-When `let mut s: String::from("hello");` is executed, the memory looks like this:
-
-```text
-                    s
-      +---------+--------+----------+
-Stack | pointer | length | capacity | 
-      |  |      |   5    |    5     |
-      +--|------+--------+----------+
-         |
-         |
-         v
-       +---+---+---+---+---+
-Heap:  | H | e | l | l | o |
-       +---+---+---+---+---+
-```
-
-When `let t = s.clone()` is executed, a whole new region is allocated on the heap to store a copy of the data:
-
-```text
-                    s                                    t
-      +---------+--------+----------+      +---------+--------+----------+
-Stack | pointer | length | capacity |      | pointer | length | capacity |
-      |  |      |   5    |    5     |      |  |      |   5    |    5     |
-      +--|------+--------+----------+      +--|------+--------+----------+
-         |                                    |
-         |                                    |
-         v                                    v
-       +---+---+---+---+---+                +---+---+---+---+---+
-Heap:  | H | e | l | l | o |                | H | e | l | l | o |
-       +---+---+---+---+---+                +---+---+---+---+---+
-```
-
-If you're coming from a language like Java, you can think of `clone` as a way to create a deep copy of an object.
-
-## Implementing `Clone`
-
-To make a type `Clone`-able, we have to implement the `Clone` trait for it.  
-You almost always implement `Clone` by deriving it:
-
-```rust
-#[derive(Clone)]
-struct MyType {
-    // fields
-}
-```
-
-The compiler implements `Clone` for `MyType` as you would expect: it clones each field of `MyType` individually and
-then constructs a new `MyType` instance using the cloned fields.  
-Remember that you can use `cargo expand` (or your IDE) to explore the code generated by `derive` macros.
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/10_clone`
diff --git a/book/src/04_traits/11_clone.md b/book/src/04_traits/11_clone.md
new file mode 100644 (file)
index 0000000..a897b48
--- /dev/null
@@ -0,0 +1,111 @@
+# Copying values, pt. 1
+
+In the previous chapter we introduced ownership and borrowing.  
+We stated, in particular, that:
+
+- Every value in Rust has a single owner at any given time.
+- When a function takes ownership of a value ("it consumes it"), the caller can't use that value anymore.
+
+These restrictions can be somewhat limiting.  
+Sometimes we might have to call a function that takes ownership of a value, but we still need to use 
+that value afterward.
+
+```rust
+fn consumer(s: String) { /* */ }
+
+fn example() {
+     let mut s = String::from("hello");
+     consumer(s);
+     s.push_str(", world!"); // error: value borrowed here after move
+}
+```
+
+That's where `Clone` comes in.
+
+## `Clone`
+
+`Clone` is a trait defined in Rust's standard library:
+
+```rust
+pub trait Clone {
+    fn clone(&self) -> Self;
+}
+```
+
+Its method, `clone`, takes a reference to `self` and returns a new **owned** instance of the same type.
+
+## In action
+
+Going back to the example above, we can use `clone` to create a new `String` instance before calling `consumer`:
+
+```rust
+fn consumer(s: String) { /* */ }
+
+fn example() {
+     let mut s = String::from("hello");
+     let t = s.clone();
+     consumer(t);
+     s.push_str(", world!"); // no error
+}
+```
+
+Instead of giving ownership of `s` to `consumer`, we create a new `String` (by cloning `s`) and give 
+that to `consumer` instead.  
+`s` remains valid and usable after the call to `consumer`.
+
+## In memory
+
+Let's look at what happened in memory in the example above.
+When `let mut s: String::from("hello");` is executed, the memory looks like this:
+
+```text
+                    s
+      +---------+--------+----------+
+Stack | pointer | length | capacity | 
+      |  |      |   5    |    5     |
+      +--|------+--------+----------+
+         |
+         |
+         v
+       +---+---+---+---+---+
+Heap:  | H | e | l | l | o |
+       +---+---+---+---+---+
+```
+
+When `let t = s.clone()` is executed, a whole new region is allocated on the heap to store a copy of the data:
+
+```text
+                    s                                    t
+      +---------+--------+----------+      +---------+--------+----------+
+Stack | pointer | length | capacity |      | pointer | length | capacity |
+      |  |      |   5    |    5     |      |  |      |   5    |    5     |
+      +--|------+--------+----------+      +--|------+--------+----------+
+         |                                    |
+         |                                    |
+         v                                    v
+       +---+---+---+---+---+                +---+---+---+---+---+
+Heap:  | H | e | l | l | o |                | H | e | l | l | o |
+       +---+---+---+---+---+                +---+---+---+---+---+
+```
+
+If you're coming from a language like Java, you can think of `clone` as a way to create a deep copy of an object.
+
+## Implementing `Clone`
+
+To make a type `Clone`-able, we have to implement the `Clone` trait for it.  
+You almost always implement `Clone` by deriving it:
+
+```rust
+#[derive(Clone)]
+struct MyType {
+    // fields
+}
+```
+
+The compiler implements `Clone` for `MyType` as you would expect: it clones each field of `MyType` individually and
+then constructs a new `MyType` instance using the cloned fields.  
+Remember that you can use `cargo expand` (or your IDE) to explore the code generated by `derive` macros.
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/10_clone`
diff --git a/book/src/04_traits/11_copy.md b/book/src/04_traits/11_copy.md
deleted file mode 100644 (file)
index bf26b26..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-# Copying values, pt. 2
-
-Let's consider the same example as before, but with a slight twist: using `u32` rather than `String` as a type.
-
-```rust
-fn consumer(s: u32) { /* */ }
-
-fn example() {
-     let s: u32 = 5;
-     consumer(s);
-     let t = s + 1;
-}
-```
-
-It'll compile without errors! What's going on here? What's the difference between `String` and `u32` 
-that makes the latter work without `.clone()`?
-
-## `Copy`
-
-`Copy` is another trait defined in Rust's standard library:
-
-```rust
-pub trait Copy: Clone { }
-```
-
-It is a marker trait, just like `Sized`.
-
-If a type implements `Copy`, there's no need to call `.clone()` to create a new instance of the type:
-Rust does it **implicitly** for you.  
-`u32` is an example of a type that implements `Copy`, which is why the example above compiles without errors:
-when `consumer(s)` is called, Rust creates a new `u32` instance by performing a **bitwise copy** of `s`, 
-and then passes that new instance to `consumer`. It all happens behind the scenes, without you having to do anything.
-
-## What can be `Copy`?
-
-`Copy` is not equivalent to "automatic cloning", although it implies it.  
-Types must meet a few requirements in order to be allowed to implement `Copy`.
-
-First of all, it must implement `Clone`, since `Copy` is a subtrait of `Clone`.
-This makes sense: if Rust can create a new instance of a type _implicitly_, it should 
-also be able to create a new instance _explicitly_ by calling `.clone()`.
-
-That's not all, though. A few more conditions must be met:
-
-1. The type doesn't manage any _additional_ resources (e.g. heap memory, file handles, etc.) beyond the `std::mem::size_of`
-   bytes that it occupies in memory. 
-2. The type is not a mutable reference (`&mut T`).
-
-If both conditions are met, then Rust can safely create a new instance of the type by performing a **bitwise copy** 
-of the original instance—this is often referred to as a `memcpy` operation, after the C standard library function
-that performs the bitwise copy.
-
-### Case study 1: `String`
-
-`String` is a type that doesn't implement `Copy`.  
-Why? Because it manages an additional resource: the heap-allocated memory buffer that stores the string's data.
-
-Let's imagine that Rust allowed `String` to implement `Copy`.  
-Then, when a new `String` instance is created by performing a bitwise copy of the original instance, both the original
-and the new instance would point to the same memory buffer: 
-
-```text
-               s                                 copied_s
- +---------+--------+----------+      +---------+--------+----------+
- | pointer | length | capacity |      | pointer | length | capacity |
- |  |      |   5    |    5     |      |  |      |   5    |    5     |
- +--|------+--------+----------+      +--|------+--------+----------+
-    |                                    |
-    |                                    |
-    v                                    |
-  +---+---+---+---+---+                  |
-  | H | e | l | l | o |                  |
-  +---+---+---+---+---+                  |
-    ^                                    |
-    |                                    |
-    +------------------------------------+
-```
-
-This is bad!
-Both `String` instances would try to free the memory buffer when they go out of scope, 
-leading to a double-free error.
-You could also create two distinct `&mut String` references that point to the same memory buffer,
-violating Rust's borrowing rules.
-
-### Case study 2: `u32`
-
-`u32` implements `Copy`. All integer types do, in fact.  
-An integer is "just" the bytes that represent the number in memory. There's nothing more!
-If you copy those bytes, you get another perfectly valid integer instance.
-Nothing bad can happen, so Rust allows it.
-
-### Case study 3: `&mut u32`
-
-When we introduced ownership and mutable borrows, we stated one rule quite clearly: there
-can only ever be *one* mutable borrow of a value at any given time.  
-That's why `&mut u32` doesn't implement `Copy`, even though `u32` does.
-
-If `&mut u32` implemented `Copy`, you could create multiple mutable references to 
-the same value and modify it in multiple places at the same time.
-That'd be a violation of Rust's borrowing rules! 
-It follows that `&mut T` never implements `Copy`, no matter what `T` is.
-
-## Implementing `Copy`
-
-In most cases, you don't need to manually implement `Copy`.
-You can just derive it, like this:
-
-```rust
-#[derive(Copy, Clone)]
-struct MyStruct {
-    field: u32,
-}
-```
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/11_copy`
diff --git a/book/src/04_traits/12_copy.md b/book/src/04_traits/12_copy.md
new file mode 100644 (file)
index 0000000..bf26b26
--- /dev/null
@@ -0,0 +1,117 @@
+# Copying values, pt. 2
+
+Let's consider the same example as before, but with a slight twist: using `u32` rather than `String` as a type.
+
+```rust
+fn consumer(s: u32) { /* */ }
+
+fn example() {
+     let s: u32 = 5;
+     consumer(s);
+     let t = s + 1;
+}
+```
+
+It'll compile without errors! What's going on here? What's the difference between `String` and `u32` 
+that makes the latter work without `.clone()`?
+
+## `Copy`
+
+`Copy` is another trait defined in Rust's standard library:
+
+```rust
+pub trait Copy: Clone { }
+```
+
+It is a marker trait, just like `Sized`.
+
+If a type implements `Copy`, there's no need to call `.clone()` to create a new instance of the type:
+Rust does it **implicitly** for you.  
+`u32` is an example of a type that implements `Copy`, which is why the example above compiles without errors:
+when `consumer(s)` is called, Rust creates a new `u32` instance by performing a **bitwise copy** of `s`, 
+and then passes that new instance to `consumer`. It all happens behind the scenes, without you having to do anything.
+
+## What can be `Copy`?
+
+`Copy` is not equivalent to "automatic cloning", although it implies it.  
+Types must meet a few requirements in order to be allowed to implement `Copy`.
+
+First of all, it must implement `Clone`, since `Copy` is a subtrait of `Clone`.
+This makes sense: if Rust can create a new instance of a type _implicitly_, it should 
+also be able to create a new instance _explicitly_ by calling `.clone()`.
+
+That's not all, though. A few more conditions must be met:
+
+1. The type doesn't manage any _additional_ resources (e.g. heap memory, file handles, etc.) beyond the `std::mem::size_of`
+   bytes that it occupies in memory. 
+2. The type is not a mutable reference (`&mut T`).
+
+If both conditions are met, then Rust can safely create a new instance of the type by performing a **bitwise copy** 
+of the original instance—this is often referred to as a `memcpy` operation, after the C standard library function
+that performs the bitwise copy.
+
+### Case study 1: `String`
+
+`String` is a type that doesn't implement `Copy`.  
+Why? Because it manages an additional resource: the heap-allocated memory buffer that stores the string's data.
+
+Let's imagine that Rust allowed `String` to implement `Copy`.  
+Then, when a new `String` instance is created by performing a bitwise copy of the original instance, both the original
+and the new instance would point to the same memory buffer: 
+
+```text
+               s                                 copied_s
+ +---------+--------+----------+      +---------+--------+----------+
+ | pointer | length | capacity |      | pointer | length | capacity |
+ |  |      |   5    |    5     |      |  |      |   5    |    5     |
+ +--|------+--------+----------+      +--|------+--------+----------+
+    |                                    |
+    |                                    |
+    v                                    |
+  +---+---+---+---+---+                  |
+  | H | e | l | l | o |                  |
+  +---+---+---+---+---+                  |
+    ^                                    |
+    |                                    |
+    +------------------------------------+
+```
+
+This is bad!
+Both `String` instances would try to free the memory buffer when they go out of scope, 
+leading to a double-free error.
+You could also create two distinct `&mut String` references that point to the same memory buffer,
+violating Rust's borrowing rules.
+
+### Case study 2: `u32`
+
+`u32` implements `Copy`. All integer types do, in fact.  
+An integer is "just" the bytes that represent the number in memory. There's nothing more!
+If you copy those bytes, you get another perfectly valid integer instance.
+Nothing bad can happen, so Rust allows it.
+
+### Case study 3: `&mut u32`
+
+When we introduced ownership and mutable borrows, we stated one rule quite clearly: there
+can only ever be *one* mutable borrow of a value at any given time.  
+That's why `&mut u32` doesn't implement `Copy`, even though `u32` does.
+
+If `&mut u32` implemented `Copy`, you could create multiple mutable references to 
+the same value and modify it in multiple places at the same time.
+That'd be a violation of Rust's borrowing rules! 
+It follows that `&mut T` never implements `Copy`, no matter what `T` is.
+
+## Implementing `Copy`
+
+In most cases, you don't need to manually implement `Copy`.
+You can just derive it, like this:
+
+```rust
+#[derive(Copy, Clone)]
+struct MyStruct {
+    field: u32,
+}
+```
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/11_copy`
diff --git a/book/src/04_traits/12_drop.md b/book/src/04_traits/12_drop.md
deleted file mode 100644 (file)
index 5368abd..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-# The `Drop` trait
-
-When we introduced [destructors](../03_ticket_v1/11_destructor),
-we mentioned that the `drop` function:
-
-1. reclaims the memory occupied by the type (i.e. `std::mem::size_of` bytes)
-2. cleans up any additional resources that the value might be managing (e.g. the heap buffer of a `String`)
-
-Step 2. is where the `Drop` trait comes in.
-
-```rust
-pub trait Drop {
-    fn drop(&mut self);
-}
-```
-
-The `Drop` trait is a mechanism for you to define _additional_ cleanup logic for your types,
-beyond what the compiler does for you automatically.  
-Whatever you put in the `drop` method will be executed when the value goes out of scope.
-
-## `Drop` and `Copy`
-
-When talking about the `Copy` trait, we said that a type can't implement `Copy` if it
-manages additional resources beyond the `std::mem::size_of` bytes that it occupies in memory.
-
-You might wonder: how does the compiler know if a type manages additional resources?
-That's right: `Drop` trait implementations!  
-If your type has an explicit `Drop` implementation, the compiler will assume
-that your type has additional resources attached to it and won't allow you to implement `Copy`.
-
-```rust
-// This is a unit struct, i.e. a struct with no fields.
-#[derive(Clone, Copy)]
-struct MyType;
-
-impl Drop for MyType {
-    fn drop(&mut self) {
-       // We don't need to do anything here,
-       // it's enough to have an "empty" Drop implementation
-    }
-}
-```
-
-The compiler will complain with this error message:
-
-```text
-error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor
- --> src/lib.rs:2:17
-  |
-2 | #[derive(Clone, Copy)]
-  |                 ^^^^ `Copy` not allowed on types with destructors
-```
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/12_drop`
diff --git a/book/src/04_traits/13_drop.md b/book/src/04_traits/13_drop.md
new file mode 100644 (file)
index 0000000..5368abd
--- /dev/null
@@ -0,0 +1,56 @@
+# The `Drop` trait
+
+When we introduced [destructors](../03_ticket_v1/11_destructor),
+we mentioned that the `drop` function:
+
+1. reclaims the memory occupied by the type (i.e. `std::mem::size_of` bytes)
+2. cleans up any additional resources that the value might be managing (e.g. the heap buffer of a `String`)
+
+Step 2. is where the `Drop` trait comes in.
+
+```rust
+pub trait Drop {
+    fn drop(&mut self);
+}
+```
+
+The `Drop` trait is a mechanism for you to define _additional_ cleanup logic for your types,
+beyond what the compiler does for you automatically.  
+Whatever you put in the `drop` method will be executed when the value goes out of scope.
+
+## `Drop` and `Copy`
+
+When talking about the `Copy` trait, we said that a type can't implement `Copy` if it
+manages additional resources beyond the `std::mem::size_of` bytes that it occupies in memory.
+
+You might wonder: how does the compiler know if a type manages additional resources?
+That's right: `Drop` trait implementations!  
+If your type has an explicit `Drop` implementation, the compiler will assume
+that your type has additional resources attached to it and won't allow you to implement `Copy`.
+
+```rust
+// This is a unit struct, i.e. a struct with no fields.
+#[derive(Clone, Copy)]
+struct MyType;
+
+impl Drop for MyType {
+    fn drop(&mut self) {
+       // We don't need to do anything here,
+       // it's enough to have an "empty" Drop implementation
+    }
+}
+```
+
+The compiler will complain with this error message:
+
+```text
+error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor
+ --> src/lib.rs:2:17
+  |
+2 | #[derive(Clone, Copy)]
+  |                 ^^^^ `Copy` not allowed on types with destructors
+```
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/12_drop`
diff --git a/book/src/04_traits/13_outro.md b/book/src/04_traits/13_outro.md
deleted file mode 100644 (file)
index 2e1cd85..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# Wrapping up
-
-We've covered quite a few different traits in this chapter—and we've only scratched the surface!
-It may feel like you have a lot to remember, but don't worry: you'll bump into these traits
-so often when writing Rust code that they'll soon become second nature.
-
-Before moving on, let's go through one last exercise to consolidate what we've learned. 
-You'll have minimal guidance this time—just the exercise description and the tests to guide you.
-
-## References
-
-- The exercise for this section is located in `exercises/04_traits/13_outro` 
diff --git a/book/src/04_traits/14_outro.md b/book/src/04_traits/14_outro.md
new file mode 100644 (file)
index 0000000..2e1cd85
--- /dev/null
@@ -0,0 +1,12 @@
+# Wrapping up
+
+We've covered quite a few different traits in this chapter—and we've only scratched the surface!
+It may feel like you have a lot to remember, but don't worry: you'll bump into these traits
+so often when writing Rust code that they'll soon become second nature.
+
+Before moving on, let's go through one last exercise to consolidate what we've learned. 
+You'll have minimal guidance this time—just the exercise description and the tests to guide you.
+
+## References
+
+- The exercise for this section is located in `exercises/04_traits/13_outro` 
diff --git a/exercises/04_traits/05_str_slice/Cargo.toml b/exercises/04_traits/05_str_slice/Cargo.toml
deleted file mode 100644 (file)
index a290d9b..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-[package]
-name = "str_slice"
-version = "0.1.0"
-edition = "2021"
-
-[dev-dependencies]
-common = { path = "../../../helpers/common" }
diff --git a/exercises/04_traits/05_str_slice/src/lib.rs b/exercises/04_traits/05_str_slice/src/lib.rs
deleted file mode 100644 (file)
index 5bf6614..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-// TODO: Re-implement `Ticket`'s accessor methods. This time return a `&str` rather than a `&String`.
-
-pub struct Ticket {
-    title: String,
-    description: String,
-    status: String,
-}
-
-impl Ticket {
-    pub fn new(title: String, description: String, status: String) -> Ticket {
-        if title.is_empty() {
-            panic!("Title cannot be empty");
-        }
-        if title.len() > 50 {
-            panic!("Title cannot be longer than 50 bytes");
-        }
-        if description.is_empty() {
-            panic!("Description cannot be empty");
-        }
-        if description.len() > 500 {
-            panic!("Description cannot be longer than 500 bytes");
-        }
-        if status != "To-Do" && status != "In Progress" && status != "Done" {
-            panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
-        }
-
-        Ticket {
-            title,
-            description,
-            status,
-        }
-    }
-
-    pub fn title(&self) -> &String {
-        &self.title
-    }
-
-    pub fn description(&self) -> &String {
-        &self.description
-    }
-
-    pub fn status(&self) -> &String {
-        &self.status
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use common::{valid_description, valid_title};
-    use std::any::{Any, TypeId};
-
-    #[test]
-    fn test_type() {
-        let ticket = Ticket::new(valid_title(), valid_description(), "To-Do".to_string());
-        // Some dark magic to verify that you used the expected return types
-        assert_eq!(TypeId::of::<str>(), ticket.title().type_id());
-        assert_eq!(TypeId::of::<str>(), ticket.description().type_id());
-        assert_eq!(TypeId::of::<str>(), ticket.status().type_id());
-    }
-}
diff --git a/exercises/04_traits/05_trait_bounds/Cargo.toml b/exercises/04_traits/05_trait_bounds/Cargo.toml
new file mode 100644 (file)
index 0000000..a9a51d1
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "trait_bounds"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/05_trait_bounds/src/lib.rs b/exercises/04_traits/05_trait_bounds/src/lib.rs
new file mode 100644 (file)
index 0000000..f3d87c0
--- /dev/null
@@ -0,0 +1,15 @@
+// TODO: Add the necessary trait bounds to `min` so that it compiles successfully.
+//   Refer to `std::cmp` for more information on the traits you might need.
+//
+// Note: there are different trait bounds that'll make the compiler happy, but they come with
+// different _semantics_. We'll cover those differences later in the course when we talk about ordered
+// collections (e.g. BTreeMap).
+
+/// Return the minimum of two values.
+pub fn min<T>(left: T, right: T) -> T {
+    if left <= right {
+        left
+    } else {
+        right
+    }
+}
diff --git a/exercises/04_traits/06_deref/Cargo.toml b/exercises/04_traits/06_deref/Cargo.toml
deleted file mode 100644 (file)
index c56f0bf..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[package]
-name = "deref"
-version = "0.1.0"
-edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/06_deref/src/lib.rs b/exercises/04_traits/06_deref/src/lib.rs
deleted file mode 100644 (file)
index c7a5c35..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-// TODO: whenever `title` and `description` are returned via their accessor methods, they
-//   should be normalized—i.e. leading and trailing whitespace should be removed.
-//   There is a method in Rust's standard library that can help with this, but you won't
-//   find it in the documentation for `String`.
-//   Can you figure out where it is defined and how to use it?
-
-pub struct Ticket {
-    title: String,
-    description: String,
-    status: String,
-}
-
-impl Ticket {
-    pub fn title(&self) -> &str {
-        todo!()
-    }
-
-    pub fn description(&self) -> &str {
-        todo!()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_normalization() {
-        let ticket = Ticket {
-            title: "   A title ".to_string(),
-            description: " A description   ".to_string(),
-            status: "To-Do".to_string(),
-        };
-
-        assert_eq!("A title", ticket.title());
-        assert_eq!("A description", ticket.description());
-    }
-}
diff --git a/exercises/04_traits/06_str_slice/Cargo.toml b/exercises/04_traits/06_str_slice/Cargo.toml
new file mode 100644 (file)
index 0000000..a290d9b
--- /dev/null
@@ -0,0 +1,7 @@
+[package]
+name = "str_slice"
+version = "0.1.0"
+edition = "2021"
+
+[dev-dependencies]
+common = { path = "../../../helpers/common" }
diff --git a/exercises/04_traits/06_str_slice/src/lib.rs b/exercises/04_traits/06_str_slice/src/lib.rs
new file mode 100644 (file)
index 0000000..5bf6614
--- /dev/null
@@ -0,0 +1,61 @@
+// TODO: Re-implement `Ticket`'s accessor methods. This time return a `&str` rather than a `&String`.
+
+pub struct Ticket {
+    title: String,
+    description: String,
+    status: String,
+}
+
+impl Ticket {
+    pub fn new(title: String, description: String, status: String) -> Ticket {
+        if title.is_empty() {
+            panic!("Title cannot be empty");
+        }
+        if title.len() > 50 {
+            panic!("Title cannot be longer than 50 bytes");
+        }
+        if description.is_empty() {
+            panic!("Description cannot be empty");
+        }
+        if description.len() > 500 {
+            panic!("Description cannot be longer than 500 bytes");
+        }
+        if status != "To-Do" && status != "In Progress" && status != "Done" {
+            panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
+        }
+
+        Ticket {
+            title,
+            description,
+            status,
+        }
+    }
+
+    pub fn title(&self) -> &String {
+        &self.title
+    }
+
+    pub fn description(&self) -> &String {
+        &self.description
+    }
+
+    pub fn status(&self) -> &String {
+        &self.status
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use common::{valid_description, valid_title};
+    use std::any::{Any, TypeId};
+
+    #[test]
+    fn test_type() {
+        let ticket = Ticket::new(valid_title(), valid_description(), "To-Do".to_string());
+        // Some dark magic to verify that you used the expected return types
+        assert_eq!(TypeId::of::<str>(), ticket.title().type_id());
+        assert_eq!(TypeId::of::<str>(), ticket.description().type_id());
+        assert_eq!(TypeId::of::<str>(), ticket.status().type_id());
+    }
+}
diff --git a/exercises/04_traits/07_deref/Cargo.toml b/exercises/04_traits/07_deref/Cargo.toml
new file mode 100644 (file)
index 0000000..c56f0bf
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "deref"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/07_deref/src/lib.rs b/exercises/04_traits/07_deref/src/lib.rs
new file mode 100644 (file)
index 0000000..c7a5c35
--- /dev/null
@@ -0,0 +1,38 @@
+// TODO: whenever `title` and `description` are returned via their accessor methods, they
+//   should be normalized—i.e. leading and trailing whitespace should be removed.
+//   There is a method in Rust's standard library that can help with this, but you won't
+//   find it in the documentation for `String`.
+//   Can you figure out where it is defined and how to use it?
+
+pub struct Ticket {
+    title: String,
+    description: String,
+    status: String,
+}
+
+impl Ticket {
+    pub fn title(&self) -> &str {
+        todo!()
+    }
+
+    pub fn description(&self) -> &str {
+        todo!()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_normalization() {
+        let ticket = Ticket {
+            title: "   A title ".to_string(),
+            description: " A description   ".to_string(),
+            status: "To-Do".to_string(),
+        };
+
+        assert_eq!("A title", ticket.title());
+        assert_eq!("A description", ticket.description());
+    }
+}
diff --git a/exercises/04_traits/07_sized/Cargo.toml b/exercises/04_traits/07_sized/Cargo.toml
deleted file mode 100644 (file)
index 763ac55..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[package]
-name = "sized"
-version = "0.1.0"
-edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/07_sized/src/lib.rs b/exercises/04_traits/07_sized/src/lib.rs
deleted file mode 100644 (file)
index a406fc5..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-pub fn example() {
-    // Trying to get the size of a str (or any other DST)
-    // via `std::mem::size_of` will result in a compile-time error.
-    //
-    // TODO: Comment out the following line and move on to the next exercise.
-    std::mem::size_of::<str>();
-}
diff --git a/exercises/04_traits/08_from/Cargo.toml b/exercises/04_traits/08_from/Cargo.toml
deleted file mode 100644 (file)
index 64818e2..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[package]
-name = "from"
-version = "0.1.0"
-edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/08_from/src/lib.rs b/exercises/04_traits/08_from/src/lib.rs
deleted file mode 100644 (file)
index cc6f5b1..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-// TODO: Implement the `From` trait for the `WrappingU32` type to make `example` compile.
-
-pub struct WrappingU32 {
-    value: u32,
-}
-
-fn example() {
-    let wrapping: WrappingU32 = 42.into();
-    let wrapping = WrappingU32::from(42);
-}
diff --git a/exercises/04_traits/08_sized/Cargo.toml b/exercises/04_traits/08_sized/Cargo.toml
new file mode 100644 (file)
index 0000000..763ac55
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "sized"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/08_sized/src/lib.rs b/exercises/04_traits/08_sized/src/lib.rs
new file mode 100644 (file)
index 0000000..a406fc5
--- /dev/null
@@ -0,0 +1,7 @@
+pub fn example() {
+    // Trying to get the size of a str (or any other DST)
+    // via `std::mem::size_of` will result in a compile-time error.
+    //
+    // TODO: Comment out the following line and move on to the next exercise.
+    std::mem::size_of::<str>();
+}
diff --git a/exercises/04_traits/09_assoc_vs_generic/Cargo.toml b/exercises/04_traits/09_assoc_vs_generic/Cargo.toml
deleted file mode 100644 (file)
index 5a2c0f5..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[package]
-name = "assoc_vs_generic"
-version = "0.1.0"
-edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/09_assoc_vs_generic/src/lib.rs b/exercises/04_traits/09_assoc_vs_generic/src/lib.rs
deleted file mode 100644 (file)
index 84f3e7b..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-// TODO: Define a new trait, `Power`, that has a method `power` that raises `self`
-//  to the power of `n`.
-//  The trait definition and its implementations should be enough to get
-//  the tests to compile and pass.
-//
-// Recommendation: you may be tempted to write a generic implementation to handle
-// all cases at once. However, this is fairly complicated and requires the use of
-// additional crates (i.e. `num-traits`).
-// Even then, it might be preferable to use a simple macro instead to avoid
-// the complexity of a highly generic implementation. Check out the
-// "Little book of Rust macros" (https://veykril.github.io/tlborm/) if you're
-// interested in learning more about it.
-// You don't have to though: it's perfectly okay to write three separate
-// implementations manually. Venture further only if you're curious.
-
-#[cfg(test)]
-mod tests {
-    use super::Power;
-
-    #[test]
-    fn test_power_u16() {
-        let x: u32 = 2_u32.power(3u16);
-        assert_eq!(x, 8);
-    }
-
-    #[test]
-    fn test_power_u32() {
-        let x: u32 = 2_u32.power(3u32);
-        assert_eq!(x, 8);
-    }
-
-    #[test]
-    fn test_power_ref_u32() {
-        let x: u32 = 2_u32.power(&3u32);
-        assert_eq!(x, 8);
-    }
-}
diff --git a/exercises/04_traits/09_from/Cargo.toml b/exercises/04_traits/09_from/Cargo.toml
new file mode 100644 (file)
index 0000000..64818e2
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "from"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/09_from/src/lib.rs b/exercises/04_traits/09_from/src/lib.rs
new file mode 100644 (file)
index 0000000..cc6f5b1
--- /dev/null
@@ -0,0 +1,10 @@
+// TODO: Implement the `From` trait for the `WrappingU32` type to make `example` compile.
+
+pub struct WrappingU32 {
+    value: u32,
+}
+
+fn example() {
+    let wrapping: WrappingU32 = 42.into();
+    let wrapping = WrappingU32::from(42);
+}
diff --git a/exercises/04_traits/10_assoc_vs_generic/Cargo.toml b/exercises/04_traits/10_assoc_vs_generic/Cargo.toml
new file mode 100644 (file)
index 0000000..5a2c0f5
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "assoc_vs_generic"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/10_assoc_vs_generic/src/lib.rs b/exercises/04_traits/10_assoc_vs_generic/src/lib.rs
new file mode 100644 (file)
index 0000000..84f3e7b
--- /dev/null
@@ -0,0 +1,37 @@
+// TODO: Define a new trait, `Power`, that has a method `power` that raises `self`
+//  to the power of `n`.
+//  The trait definition and its implementations should be enough to get
+//  the tests to compile and pass.
+//
+// Recommendation: you may be tempted to write a generic implementation to handle
+// all cases at once. However, this is fairly complicated and requires the use of
+// additional crates (i.e. `num-traits`).
+// Even then, it might be preferable to use a simple macro instead to avoid
+// the complexity of a highly generic implementation. Check out the
+// "Little book of Rust macros" (https://veykril.github.io/tlborm/) if you're
+// interested in learning more about it.
+// You don't have to though: it's perfectly okay to write three separate
+// implementations manually. Venture further only if you're curious.
+
+#[cfg(test)]
+mod tests {
+    use super::Power;
+
+    #[test]
+    fn test_power_u16() {
+        let x: u32 = 2_u32.power(3u16);
+        assert_eq!(x, 8);
+    }
+
+    #[test]
+    fn test_power_u32() {
+        let x: u32 = 2_u32.power(3u32);
+        assert_eq!(x, 8);
+    }
+
+    #[test]
+    fn test_power_ref_u32() {
+        let x: u32 = 2_u32.power(&3u32);
+        assert_eq!(x, 8);
+    }
+}
diff --git a/exercises/04_traits/10_clone/Cargo.toml b/exercises/04_traits/10_clone/Cargo.toml
deleted file mode 100644 (file)
index ca199a7..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[package]
-name = "clone"
-version = "0.1.0"
-edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/10_clone/src/lib.rs b/exercises/04_traits/10_clone/src/lib.rs
deleted file mode 100644 (file)
index 4fbbe47..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-// TODO: add the necessary `Clone` implementations (and invocations)
-//  to get the code to compile.
-
-pub fn summary(ticket: Ticket) -> (Ticket, Summary) {
-    (ticket, ticket.summary())
-}
-
-pub struct Ticket {
-    pub title: String,
-    pub description: String,
-    pub status: String,
-}
-
-impl Ticket {
-    pub fn summary(self) -> Summary {
-        Summary {
-            title: self.title,
-            status: self.status,
-        }
-    }
-}
-
-pub struct Summary {
-    pub title: String,
-    pub status: String,
-}
diff --git a/exercises/04_traits/11_clone/Cargo.toml b/exercises/04_traits/11_clone/Cargo.toml
new file mode 100644 (file)
index 0000000..ca199a7
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "clone"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/11_clone/src/lib.rs b/exercises/04_traits/11_clone/src/lib.rs
new file mode 100644 (file)
index 0000000..4fbbe47
--- /dev/null
@@ -0,0 +1,26 @@
+// TODO: add the necessary `Clone` implementations (and invocations)
+//  to get the code to compile.
+
+pub fn summary(ticket: Ticket) -> (Ticket, Summary) {
+    (ticket, ticket.summary())
+}
+
+pub struct Ticket {
+    pub title: String,
+    pub description: String,
+    pub status: String,
+}
+
+impl Ticket {
+    pub fn summary(self) -> Summary {
+        Summary {
+            title: self.title,
+            status: self.status,
+        }
+    }
+}
+
+pub struct Summary {
+    pub title: String,
+    pub status: String,
+}
diff --git a/exercises/04_traits/11_copy/Cargo.toml b/exercises/04_traits/11_copy/Cargo.toml
deleted file mode 100644 (file)
index af5c2e3..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[package]
-name = "copy"
-version = "0.1.0"
-edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/11_copy/src/lib.rs b/exercises/04_traits/11_copy/src/lib.rs
deleted file mode 100644 (file)
index d74720b..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-// TODO: implement the necessary traits to make the test compile and pass.
-//  You *can't* modify the test.
-
-pub struct WrappingU32 {
-    value: u32,
-}
-
-impl WrappingU32 {
-    pub fn new(value: u32) -> Self {
-        Self { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_ops() {
-        let x = WrappingU32::new(42);
-        let y = WrappingU32::new(31);
-        let z = WrappingU32::new(u32::MAX);
-        assert_eq!(x + y + y + z, WrappingU32::new(103));
-    }
-}
diff --git a/exercises/04_traits/12_copy/Cargo.toml b/exercises/04_traits/12_copy/Cargo.toml
new file mode 100644 (file)
index 0000000..af5c2e3
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "copy"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/12_copy/src/lib.rs b/exercises/04_traits/12_copy/src/lib.rs
new file mode 100644 (file)
index 0000000..d74720b
--- /dev/null
@@ -0,0 +1,25 @@
+// TODO: implement the necessary traits to make the test compile and pass.
+//  You *can't* modify the test.
+
+pub struct WrappingU32 {
+    value: u32,
+}
+
+impl WrappingU32 {
+    pub fn new(value: u32) -> Self {
+        Self { value }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_ops() {
+        let x = WrappingU32::new(42);
+        let y = WrappingU32::new(31);
+        let z = WrappingU32::new(u32::MAX);
+        assert_eq!(x + y + y + z, WrappingU32::new(103));
+    }
+}
diff --git a/exercises/04_traits/12_drop/Cargo.toml b/exercises/04_traits/12_drop/Cargo.toml
deleted file mode 100644 (file)
index 171117a..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[package]
-name = "drop"
-version = "0.1.0"
-edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/12_drop/src/lib.rs b/exercises/04_traits/12_drop/src/lib.rs
deleted file mode 100644 (file)
index 2124d34..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-// TODO: implement a so-called "Drop bomb": a type that panics when dropped
-//  unless a certain operation has been performed on it.
-//  You can see the expected API in the tests below.
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic]
-    fn test_drop_bomb() {
-        let bomb = DropBomb::new();
-        // The bomb should panic when dropped
-    }
-
-    #[test]
-    fn test_defused_drop_bomb() {
-        let mut bomb = DropBomb::new();
-        bomb.defuse();
-        // The bomb should not panic when dropped
-        // since it has been defused
-    }
-}
diff --git a/exercises/04_traits/13_drop/Cargo.toml b/exercises/04_traits/13_drop/Cargo.toml
new file mode 100644 (file)
index 0000000..171117a
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "drop"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/13_drop/src/lib.rs b/exercises/04_traits/13_drop/src/lib.rs
new file mode 100644 (file)
index 0000000..2124d34
--- /dev/null
@@ -0,0 +1,23 @@
+// TODO: implement a so-called "Drop bomb": a type that panics when dropped
+//  unless a certain operation has been performed on it.
+//  You can see the expected API in the tests below.
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    #[should_panic]
+    fn test_drop_bomb() {
+        let bomb = DropBomb::new();
+        // The bomb should panic when dropped
+    }
+
+    #[test]
+    fn test_defused_drop_bomb() {
+        let mut bomb = DropBomb::new();
+        bomb.defuse();
+        // The bomb should not panic when dropped
+        // since it has been defused
+    }
+}
diff --git a/exercises/04_traits/13_outro/Cargo.toml b/exercises/04_traits/13_outro/Cargo.toml
deleted file mode 100644 (file)
index 17791ec..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[package]
-name = "outro_03"
-version = "0.1.0"
-edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/13_outro/src/lib.rs b/exercises/04_traits/13_outro/src/lib.rs
deleted file mode 100644 (file)
index 547fd94..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-// TODO: Define a new `SaturatingU16` type.
-//   It should hold a `u16` value.
-//   It should provide conversions from `u16`, `u8`, `&u16` and `&u8`.
-//   It should support addition with a right-hand side of type
-//   SaturatingU16, u16, &u16, and &SaturatingU16. Addition should saturate at the
-//   maximum value for `u16`.
-//   It should be possible to compare it with another `SaturatingU16` or a `u16`.
-//   It should be possible to print its debug representation.
-//
-// Tests are located in the `tests` folder—pay attention to the visibility of your types and methods.
diff --git a/exercises/04_traits/13_outro/tests/integration.rs b/exercises/04_traits/13_outro/tests/integration.rs
deleted file mode 100644 (file)
index e991b95..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-use outro_03::SaturatingU16;
-
-#[test]
-fn test_saturating_u16() {
-    let a: SaturatingU16 = (&10u8).into();
-    let b: SaturatingU16 = 5u8.into();
-    let c: SaturatingU16 = u16::MAX.into();
-    let d: SaturatingU16 = (&1u16).into();
-
-    assert_eq!(a + b, SaturatingU16::from(15u16));
-    assert_eq!(a + c, SaturatingU16::from(u16::MAX));
-    assert_eq!(a + d, SaturatingU16::from(11u16));
-    assert_eq!(a + a, 20u16);
-    assert_eq!(a + 5u16, 15u16);
-    assert_eq!(a + &u16::MAX, SaturatingU16::from(u16::MAX));
-}
diff --git a/exercises/04_traits/14_outro/Cargo.toml b/exercises/04_traits/14_outro/Cargo.toml
new file mode 100644 (file)
index 0000000..17791ec
--- /dev/null
@@ -0,0 +1,4 @@
+[package]
+name = "outro_03"
+version = "0.1.0"
+edition = "2021"
\ No newline at end of file
diff --git a/exercises/04_traits/14_outro/src/lib.rs b/exercises/04_traits/14_outro/src/lib.rs
new file mode 100644 (file)
index 0000000..547fd94
--- /dev/null
@@ -0,0 +1,10 @@
+// TODO: Define a new `SaturatingU16` type.
+//   It should hold a `u16` value.
+//   It should provide conversions from `u16`, `u8`, `&u16` and `&u8`.
+//   It should support addition with a right-hand side of type
+//   SaturatingU16, u16, &u16, and &SaturatingU16. Addition should saturate at the
+//   maximum value for `u16`.
+//   It should be possible to compare it with another `SaturatingU16` or a `u16`.
+//   It should be possible to print its debug representation.
+//
+// Tests are located in the `tests` folder—pay attention to the visibility of your types and methods.
diff --git a/exercises/04_traits/14_outro/tests/integration.rs b/exercises/04_traits/14_outro/tests/integration.rs
new file mode 100644 (file)
index 0000000..e991b95
--- /dev/null
@@ -0,0 +1,16 @@
+use outro_03::SaturatingU16;
+
+#[test]
+fn test_saturating_u16() {
+    let a: SaturatingU16 = (&10u8).into();
+    let b: SaturatingU16 = 5u8.into();
+    let c: SaturatingU16 = u16::MAX.into();
+    let d: SaturatingU16 = (&1u16).into();
+
+    assert_eq!(a + b, SaturatingU16::from(15u16));
+    assert_eq!(a + c, SaturatingU16::from(u16::MAX));
+    assert_eq!(a + d, SaturatingU16::from(11u16));
+    assert_eq!(a + a, 20u16);
+    assert_eq!(a + 5u16, 15u16);
+    assert_eq!(a + &u16::MAX, SaturatingU16::from(u16::MAX));
+}