diff --git a/src/control-flow-basics/break-continue/labels.md b/src/control-flow-basics/break-continue/labels.md index bff046fe994..55e26ae6027 100644 --- a/src/control-flow-basics/break-continue/labels.md +++ b/src/control-flow-basics/break-continue/labels.md @@ -19,3 +19,15 @@ fn main() { print!("elements searched: {elements_searched}"); } ``` + +
+ +- Labeled break also works on arbitrary blocks, e.g. + ```rust + 'label: { + break 'label; + println!("This line gets skipped"); + } + ``` + +
diff --git a/src/control-flow-basics/exercise.md b/src/control-flow-basics/exercise.md index 6f7df66ddd5..45d0e32ab30 100644 --- a/src/control-flow-basics/exercise.md +++ b/src/control-flow-basics/exercise.md @@ -30,7 +30,7 @@ initial `n`. todo!("Implement this") } +{{#include exercise.rs:tests}} + {{#include exercise.rs:main}} - todo!("Implement this") -} ``` diff --git a/src/control-flow-basics/exercise.rs b/src/control-flow-basics/exercise.rs index 2ebc2e1e7b0..4cb79df34b5 100644 --- a/src/control-flow-basics/exercise.rs +++ b/src/control-flow-basics/exercise.rs @@ -34,6 +34,6 @@ fn test_collatz_length() { // ANCHOR: main fn main() { - // ANCHOR_END: main println!("Length: {}", collatz_length(11)); } +// ANCHOR_END: main diff --git a/src/generics/generic-functions.md b/src/generics/generic-functions.md index 4c2a31927ce..6e0b56b3c3a 100644 --- a/src/generics/generic-functions.md +++ b/src/generics/generic-functions.md @@ -19,7 +19,7 @@ fn pick(n: i32, even: T, odd: T) -> T { fn main() { println!("picked a number: {:?}", pick(97, 222, 333)); - println!("picked a tuple: {:?}", pick(28, ("dog", 1), ("cat", 2))); + println!("picked a string: {:?}", pick(28, "dog", "cat")); } ``` @@ -27,6 +27,17 @@ fn main() { - Rust infers a type for T based on the types of the arguments and return value. +- In this example we only use the primitive types `i32` and `&str` for `T`, but + we can use any type here, including user-defined types: + + ```rust,ignore + struct Foo { + val: u8, + } + + pick(123, Foo { val: 7 }, Foo { val: 456 }); + ``` + - This is similar to C++ templates, but Rust partially compiles the generic function immediately, so that function must be valid for all types matching the constraints. For example, try modifying `pick` to return `even + odd` if diff --git a/src/methods-and-traits/exercise.rs b/src/methods-and-traits/exercise.rs index d9c66086d6f..f7b9fa035cb 100644 --- a/src/methods-and-traits/exercise.rs +++ b/src/methods-and-traits/exercise.rs @@ -14,25 +14,18 @@ // ANCHOR: solution // ANCHOR: setup -use std::fmt::Display; - pub trait Logger { /// Log a message at the given verbosity level. - fn log(&self, verbosity: u8, message: impl Display); + fn log(&self, verbosity: u8, message: &str); } struct StderrLogger; impl Logger for StderrLogger { - fn log(&self, verbosity: u8, message: impl Display) { + fn log(&self, verbosity: u8, message: &str) { eprintln!("verbosity={verbosity}: {message}"); } } - -fn do_things(logger: &impl Logger) { - logger.log(5, "FYI"); - logger.log(2, "Uhoh"); -} // ANCHOR_END: setup /// Only log messages up to the given verbosity level. @@ -42,7 +35,7 @@ struct VerbosityFilter { } impl Logger for VerbosityFilter { - fn log(&self, verbosity: u8, message: impl Display) { + fn log(&self, verbosity: u8, message: &str) { if verbosity <= self.max_verbosity { self.inner.log(verbosity, message); } @@ -51,7 +44,8 @@ impl Logger for VerbosityFilter { // ANCHOR: main fn main() { - let l = VerbosityFilter { max_verbosity: 3, inner: StderrLogger }; - do_things(&l); + let logger = VerbosityFilter { max_verbosity: 3, inner: StderrLogger }; + logger.log(5, "FYI"); + logger.log(2, "Uhoh"); } // ANCHOR_END: main diff --git a/src/pattern-matching/destructuring-enums.md b/src/pattern-matching/destructuring-enums.md index cb82b0062ab..f9e30c4253b 100644 --- a/src/pattern-matching/destructuring-enums.md +++ b/src/pattern-matching/destructuring-enums.md @@ -47,12 +47,5 @@ arm, `half` is bound to the value inside the `Ok` variant. In the second arm, matched. - Demonstrate what happens when the search is inexhaustive. Note the advantage the Rust compiler provides by confirming when all cases are handled. -- Save the result of `divide_in_two` in the `result` variable and `match` it in - a loop. That won't compile because `msg` is consumed when matched. To fix it, - match `&result` instead of `result`. That will make `msg` a reference so it - won't be consumed. This - ["match ergonomics"](https://rust-lang.github.io/rfcs/2005-match-ergonomics.html) - appeared in Rust 2018. If you want to support older Rust, replace `msg` with - `ref msg` in the pattern. diff --git a/src/pattern-matching/exercise.rs b/src/pattern-matching/exercise.rs index 84a516314c3..1dfb3f498c4 100644 --- a/src/pattern-matching/exercise.rs +++ b/src/pattern-matching/exercise.rs @@ -44,11 +44,11 @@ fn eval(e: Expression) -> Result { Expression::Op { op, left, right } => { let left = match eval(*left) { Ok(v) => v, - e @ Err(_) => return e, + Err(e) => return Err(e), }; let right = match eval(*right) { Ok(v) => v, - e @ Err(_) => return e, + Err(e) => return Err(e), }; Ok(match op { Operation::Add => left + right, diff --git a/src/pattern-matching/match.md b/src/pattern-matching/match.md index fcd4a779811..a8f75d94f82 100644 --- a/src/pattern-matching/match.md +++ b/src/pattern-matching/match.md @@ -56,4 +56,24 @@ Key Points: - The condition defined in the guard applies to every expression in a pattern with an `|`. +# More To Explore + +- Another piece of pattern syntax you can show students is the `@` syntax which + binds a part of a pattern to a variable. For example: + + ```rust + let opt = Some(123); + match opt { + outer @ Some(inner) => { + println!("outer: {outer:?}, inner: {inner}"); + } + None => {} + } + ``` + + In this example `inner` has the value 123 which it pulled from the `Option` + via destructuring, `outer` captures the entire `Some(inner)` expression, so it + contains the full `Option::Some(123)`. This is rarely used but can be useful + in more complex patterns. + diff --git a/src/references/shared.md b/src/references/shared.md index a85497ff9eb..2fa0c846b04 100644 --- a/src/references/shared.md +++ b/src/references/shared.md @@ -38,6 +38,8 @@ fn x_axis(x: &i32) -> &(i32, i32) {
+- References can never be null in Rust, so null checking is not necessary. + - A reference is said to "borrow" the value it refers to, and this is a good model for students not familiar with pointers: code can use the reference to access the value, but is still "owned" by the original variable. The course diff --git a/src/references/slices.md b/src/references/slices.md index 5f8855f9d2b..825a167734e 100644 --- a/src/references/slices.md +++ b/src/references/slices.md @@ -20,7 +20,6 @@ fn main() { ``` - Slices borrow data from the sliced type. -- Question: What happens if you modify `a[3]` right before printing `s`?
@@ -43,10 +42,4 @@ fn main() { - Slices always borrow from another object. In this example, `a` has to remain 'alive' (in scope) for at least as long as our slice. -- The question about modifying `a[3]` can spark an interesting discussion, but - the answer is that for memory safety reasons you cannot do it through `a` at - this point in the execution, but you can read the data from both `a` and `s` - safely. It works before you created the slice, and again after the `println`, - when the slice is no longer used. -
diff --git a/src/std-types/hashmap.md b/src/std-types/hashmap.md index 6399bc15e89..e611902cff2 100644 --- a/src/std-types/hashmap.md +++ b/src/std-types/hashmap.md @@ -67,11 +67,6 @@ fn main() { - Alternatively HashMap can be built from any `Iterator` which yields key-value tuples. -- We are showing `HashMap`, and avoid using `&str` as key to make - examples easier. Using references in collections can, of course, be done, but - it can lead into complications with the borrow checker. - - Try removing `to_string()` from the example above and see if it still - compiles. Where do you think we might run into issues? - This type has several "method-specific" return types, such as `std::collections::hash_map::Keys`. These types often appear in searches of