From 3f0febadec4b724b3e90416e479d7485817820a9 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Thu, 7 Sep 2023 21:06:06 +0000 Subject: [PATCH] WIP control-flow-basics --- src/SUMMARY.md | 6 +- src/control-flow-basics.md | 6 +- src/control-flow-basics/blocks-and-scopes.md | 83 +++------ src/control-flow-basics/break-continue.md | 59 ++++++ src/control-flow-basics/conditionals.md | 58 ++++++ src/control-flow-basics/control-flow.md | 170 ------------------ src/control-flow-basics/exercise.md | 28 ++- src/control-flow-basics/functions.md | 155 ++-------------- src/control-flow-basics/loops.md | 57 ++++++ src/control-flow-basics/println-and-dbg.md | 23 +++ .../exercise.md | 28 +++ 11 files changed, 280 insertions(+), 393 deletions(-) create mode 100644 src/control-flow-basics/break-continue.md create mode 100644 src/control-flow-basics/conditionals.md delete mode 100644 src/control-flow-basics/control-flow.md create mode 100644 src/control-flow-basics/loops.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index bbdbb0d5c855..870f1fd4e8e6 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -24,11 +24,13 @@ - [Arithmetic](hello-world/arithmetic.md) - [Exercise: Hello, World](hello-world/exercise.md) - [Control Flow Basics](control-flow-basics.md) - - [Control Flow](control-flow-basics/control-flow.md) + - [Conditionals](control-flow-basics/conditionals.md) + - [Loops](control-flow-basics/loops.md) + - [`break` and `continue`](control-flow-basics/break-continue.md) - [Blocks and Scopes](control-flow-basics/blocks-and-scopes.md) - [Functions](control-flow-basics/functions.md) - [println! and dbg!](control-flow-basics/println-and-dbg.md) - - [Exercise: Collatz conjecture](control-flow-basics/exercise.md) + - [Exercise: Collatz Sequence](control-flow-basics/exercise.md) - [Type Inference and Conversions](type-inference-and-conversions.md) - [Type Inference](type-inference-and-conversions/inference.md) - [Simple Type Conversions](type-inference-and-conversions/simple-type-conversions.md) diff --git a/src/control-flow-basics.md b/src/control-flow-basics.md index e34135b5b4d2..3f958c881a54 100644 --- a/src/control-flow-basics.md +++ b/src/control-flow-basics.md @@ -2,8 +2,10 @@ In this segment: -* [Control Flow](control-flow-basics/control-flow.md) +* [Conditionals](control-flow-basics/conditionals.md) +* [Loops](control-flow-basics/loops.md) +* [`break` and `continue`](control-flow-basics/break-continue.md) * [Blocks and Scopes](control-flow-basics/blocks-and-scopes.md) * [Functions](control-flow-basics/functions.md) * [println! and dbg!](control-flow-basics/println-and-dbg.md) -* [Exercise: Collatz conjecture](control-flow-basics/exercise.md) +* [Exercise: Collatz Sequence](control-flow-basics/exercise.md) diff --git a/src/control-flow-basics/blocks-and-scopes.md b/src/control-flow-basics/blocks-and-scopes.md index e87f5fbdd977..0cbb8ca871aa 100644 --- a/src/control-flow-basics/blocks-and-scopes.md +++ b/src/control-flow-basics/blocks-and-scopes.md @@ -2,51 +2,9 @@ minutes: 10 --- - # Blocks and Scopes -# Scopes and Shadowing - -You can shadow variables, both those from outer scopes and variables from the -same scope: - -```rust,editable -fn main() { - let a = 10; - println!("before: {a}"); - - { - let a = "hello"; - println!("inner scope: {a}"); - - let a = true; - println!("shadowed in inner scope: {a}"); - } - - println!("after: {a}"); -} -``` - -
- -* Definition: Shadowing is different from mutation, because after shadowing both variable's memory locations exist at the same time. Both are available under the same name, depending where you use it in the code. -* A shadowing variable can have a different type. -* Shadowing looks obscure at first, but is convenient for holding on to values after `.unwrap()`. -* The following code demonstrates why the compiler can't simply reuse memory locations when shadowing an immutable variable in a scope, even if the type does not change. - -```rust,editable -fn main() { - let a = 1; - let b = &a; - let a = a + 1; - println!("{a} {b}"); -} -``` - -
-# Blocks +## Blocks A block in Rust contains a sequence of expressions. Each block has a value and a type, @@ -54,17 +12,10 @@ which are those of the last expression of the block: ```rust,editable fn main() { + let z = 13; let x = { let y = 10; println!("y: {y}"); - let z = { - let w = { - 3 + 4 - }; - println!("w: {w}"); - y * w - }; - println!("z: {z}"); z - y }; println!("x: {x}"); @@ -73,23 +24,35 @@ fn main() { If the last expression ends with `;`, then the resulting value and type is `()`. -The same rule is used for functions: the value of the function body is the -return value: +## Scopes and Shadowing -```rust,editable -fn double(x: i32) -> i32 { - x + x -} +A variable's scope is limited to the enclosing block. + +You can shadow variables, both those from outer scopes and variables from the +same scope: +```rust,editable fn main() { - println!("doubled: {}", double(7)); + let a = 10; + println!("before: {a}"); + { + let a = "hello"; + println!("inner scope: {a}"); + + let a = true; + println!("shadowed in inner scope: {a}"); + } + + println!("after: {a}"); } ```
-Key Points: -* The point of this slide is to show that blocks have a type and value in Rust. * You can show how the value of the block changes by changing the last line in the block. For instance, adding/removing a semicolon or using a `return`. +* Show that a variable's scope is limited by adding a b` in the inner block in the last example, and then trying to access it outside that block. +* Shadowing is different from mutation, because after shadowing both variable's memory locations exist at the same time. Both are available under the same name, depending where you use it in the code. +* A shadowing variable can have a different type. +* Shadowing looks obscure at first, but is convenient for holding on to values after `.unwrap()`.
diff --git a/src/control-flow-basics/break-continue.md b/src/control-flow-basics/break-continue.md new file mode 100644 index 000000000000..da77f5aa723e --- /dev/null +++ b/src/control-flow-basics/break-continue.md @@ -0,0 +1,59 @@ +--- +minutes: 5 +--- + +# `break` and `continue` + +If you want to exit any kind of loop early, use +[`break`](https://doc.rust-lang.org/reference/expressions/loop-expr.html#break-expressions). +For `loop`, this can take an optional expression that becomes the value of the `loop` expression. + +If you want to immediately start +the next iteration use [`continue`](https://doc.rust-lang.org/reference/expressions/loop-expr.html#continue-expressions). + +```rust,editable +fn main() { + let (mut a, mut b) = (100, 52); + let result = loop { + if a == b { + break a; + } + if a < b { + b -= a; + } else { + a -= b; + } + }; + println!("{result}"); +} +``` + +Both `continue` and `break` can optionally take a label argument which is used +to break out of nested loops: + +```rust,editable +fn main() { + 'outer: for x in 1..5 { + println!("x: {x}"); + let mut i = 0; + while i < x { + println!("x: {x}, i: {i}"); + i += 1; + if i == 3 { + break 'outer; + } + } + } +} +``` + +In this case we break the outer loop after 3 iterations of the inner loop. + +
+ +* Note that `loop` is the only looping construct which returns a non-trivial + value. This is because it's guaranteed to be entered at least once (unlike + `while` and `for` loops). + +
+ diff --git a/src/control-flow-basics/conditionals.md b/src/control-flow-basics/conditionals.md new file mode 100644 index 000000000000..7492f175c53c --- /dev/null +++ b/src/control-flow-basics/conditionals.md @@ -0,0 +1,58 @@ +--- +minutes: 5 +--- + +# Conditionals + +Much of the Rust syntax will be familiar to you from C, C++ or Java: + +* Blocks are delimited by curly braces. +* Line comments are started with `//`, block comments are delimited by `/* ... + */`. +* Keywords like `if` and `while` work the same. +* Variable assignment is done with `=`, comparison is done with `==`. + +## `if` expressions + +You use [`if` +expressions](https://doc.rust-lang.org/reference/expressions/if-expr.html#if-expressions) +exactly like `if` statements in other languages: + +```rust,editable +fn main() { + let x = 10; + if x < 20 { + println!("small"); + } else if x < 100 { + println!("biggish"); + } else { + println!("huge"); + } +} +``` + +In addition, you can use `if` as an expression. The last expression of each +block becomes the value of the `if` expression: + + +```rust,editable +fn main() { + let x = 10; + let size = if x < 20 { + "small" + } else { + "large" + }; + println!("number size: {}", size); +} +``` + +
+ +Because `if` is an expression and must have a particular type, both of its branch blocks must have the same type. Show what happens if you add `;` after `"small"` in the second example. + +When `if` is used in an expression, the expression must have a `;` to separate +it from the next statement. Remove the `;` before `println!` to see the compiler +error. + +
diff --git a/src/control-flow-basics/control-flow.md b/src/control-flow-basics/control-flow.md deleted file mode 100644 index 18bb48f4095e..000000000000 --- a/src/control-flow-basics/control-flow.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -minutes: 15 ---- - - -# Control Flow - -# Basic Syntax - -Much of the Rust syntax will be familiar to you from C, C++ or Java: - -* Blocks and scopes are delimited by curly braces. -* Line comments are started with `//`, block comments are delimited by `/* ... - */`. -* Keywords like `if` and `while` work the same. -* Variable assignment is done with `=`, comparison is done with `==`. -# Control Flow - -As we have seen, `if` is an expression in Rust. It is used to conditionally -evaluate one of two blocks, but the blocks can have a value which then becomes -the value of the `if` expression. Other control flow expressions work similarly -in Rust. -# `if` expressions - -You use [`if` -expressions](https://doc.rust-lang.org/reference/expressions/if-expr.html#if-expressions) -exactly like `if` statements in other languages: - -```rust,editable -fn main() { - let mut x = 10; - if x % 2 == 0 { - x = x / 2; - } else { - x = 3 * x + 1; - } -} -``` - -In addition, you can use `if` as an expression. The last expression of each -block becomes the value of the `if` expression: - - -```rust,editable -fn main() { - let mut x = 10; - x = if x % 2 == 0 { - x / 2 - } else { - 3 * x + 1 - }; -} -``` - -
- -Because `if` is an expression and must have a particular type, both of its branch blocks must have the same type. Consider showing what happens if you add `;` after `x / 2` in the second example. - -
-# `while` loops - -The [`while` keyword](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-loops) -works very similar to other languages: - -```rust,editable -fn main() { - let mut x = 10; - while x != 1 { - x = if x % 2 == 0 { - x / 2 - } else { - 3 * x + 1 - }; - } - println!("Final x: {x}"); -} -``` - -# `for` loops - -The [`for` loop](https://doc.rust-lang.org/std/keyword.for.html) is closely -related to the [`while let` loop](while-let-expressions.md). It will -automatically call `into_iter()` on the expression and then iterate over it: - -```rust,editable -fn main() { - let v = vec![10, 20, 30]; - - for x in v { - println!("x: {x}"); - } - - for i in (0..10).step_by(2) { - println!("i: {i}"); - } -} -``` - -You can use `break` and `continue` here as usual. - -
- -* Index iteration is not a special syntax in Rust for just that case. -* `(0..10)` is a range that implements an `Iterator` trait. -* `step_by` is a method that returns another `Iterator` that skips every other element. -* Modify the elements in the vector and explain the compiler errors. Change vector `v` to be mutable and the for loop to `for x in v.iter_mut()`. - -
-# `break` and `continue` - -- If you want to exit a loop early, use [`break`](https://doc.rust-lang.org/reference/expressions/loop-expr.html#break-expressions), -- If you want to immediately start -the next iteration use [`continue`](https://doc.rust-lang.org/reference/expressions/loop-expr.html#continue-expressions). - -Both `continue` and `break` can optionally take a label argument which is used -to break out of nested loops: - -```rust,editable -fn main() { - let v = vec![10, 20, 30]; - let mut iter = v.into_iter(); - 'outer: while let Some(x) = iter.next() { - println!("x: {x}"); - let mut i = 0; - while i < x { - println!("x: {x}, i: {i}"); - i += 1; - if i == 3 { - break 'outer; - } - } - } -} -``` - -In this case we break the outer loop after 3 iterations of the inner loop. -# `loop` expressions - -Finally, there is a [`loop` keyword](https://doc.rust-lang.org/reference/expressions/loop-expr.html#infinite-loops) -which creates an endless loop. - -Here you must either `break` or `return` to stop the loop: - -```rust,editable -fn main() { - let mut x = 10; - loop { - x = if x % 2 == 0 { - x / 2 - } else { - 3 * x + 1 - }; - if x == 1 { - break; - } - } - println!("Final x: {x}"); -} -``` - -
- -* Break the `loop` with a value (e.g. `break 8`) and print it out. -* Note that `loop` is the only looping construct which returns a non-trivial - value. This is because it's guaranteed to be entered at least once (unlike - `while` and `for` loops). - -
diff --git a/src/control-flow-basics/exercise.md b/src/control-flow-basics/exercise.md index 8f3999a01751..3520c6ef3562 100644 --- a/src/control-flow-basics/exercise.md +++ b/src/control-flow-basics/exercise.md @@ -2,26 +2,24 @@ minutes: 15 --- -# Exercise: Collatz conjecture +# Exercise: Collatz Sequence +The [Collatz Sequence](https://en.wikipedia.org/wiki/Collatz_conjecture) is +defined as follows, for an aribtrary n1 greater than zero: -Compute number of steps before an integer `n` becomes `1` following two rules: +- If _ni_ is 1, then the sequence terminates at _ni_. +- If _ni_ is even, then _ni+1 = ni / 2_. +- If _ni_ is odd, then _ni+1 = 3 * ni + 1_. -- If `n` is even, set `n = n/2` -- If `n` is odd, set `n = 3 * n + 1` +Write a function to calcluate the length of the collatz sequence for a given +initial `n`. -Given - -fn collatz(n: i32) -> u32 { +```rust,editable +fn collatz_length(n: i32) -> u32 { todo!("Implement") } -#[test] -fn test_collatz() { - assert_eq!(collatz(1), 0); - assert_eq!(collatz(5), 5); - assert_eq!(collatz(50), 24); +fn main() { + todo!("Implement this") } - -fill in the collatz function. - +``` diff --git a/src/control-flow-basics/functions.md b/src/control-flow-basics/functions.md index e28aadfff035..557ca8b27bd4 100644 --- a/src/control-flow-basics/functions.md +++ b/src/control-flow-basics/functions.md @@ -2,163 +2,30 @@ minutes: 3 --- - # Functions -# Functions - -A Rust version of the famous [FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz) interview question: - ```rust,editable -fn main() { - print_fizzbuzz_to(20); -} - -fn is_divisible(n: u32, divisor: u32) -> bool { - if divisor == 0 { - return false; - } - n % divisor == 0 -} - -fn fizzbuzz(n: u32) -> String { - let fizz = if is_divisible(n, 3) { "fizz" } else { "" }; - let buzz = if is_divisible(n, 5) { "buzz" } else { "" }; - if fizz.is_empty() && buzz.is_empty() { - return format!("{n}"); +fn gcd(a: u32, b: u32) -> u32 { + if b > 0 { + gcd(b, a % b) + } else { + a } - format!("{fizz}{buzz}") } -fn print_fizzbuzz_to(n: u32) { - for i in 1..=n { - println!("{}", fizzbuzz(i)); - } +fn main() { + println!("gcd: {}", gcd(143, 52)); } ```
-* We refer in `main` to a function written below. Neither forward declarations nor headers are necessary. * Declaration parameters are followed by a type (the reverse of some programming languages), then a return type. * The last expression in a function body (or any block) becomes the return value. Simply omit the `;` at the end of the expression. + The `return` keyword can be used for early return, but the "bare value" form is idiomatic at the end of a function (refactor `gcd` to use a `return`). * Some functions have no return value, and return the 'unit type', `()`. The compiler will infer this if the `-> ()` return type is omitted. -* The range expression in the `for` loop in `print_fizzbuzz_to()` contains `=n`, which causes it to include the upper bound. +* Overloading is not supported -- each function has a single implementation. + * Always takes a fixed number of parameters. Default arguments are not supported. Macros can be used to support variadic functions. + * Always takes a single set of parameter types. These types can be generic, which will be covered later.
-# Rustdoc - -All language items in Rust can be documented using special `///` syntax. - -```rust,editable -/// Determine whether the first argument is divisible by the second argument. -/// -/// If the second argument is zero, the result is false. -fn is_divisible_by(lhs: u32, rhs: u32) -> bool { - if rhs == 0 { - return false; // Corner case, early return - } - lhs % rhs == 0 // The last expression in a block is the return value -} -``` - -The contents are treated as Markdown. All published Rust library crates are -automatically documented at [`docs.rs`](https://docs.rs) using the -[rustdoc](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html) tool. It is -idiomatic to document all public items in an API using this pattern. - -
- -* Show students the generated docs for the `rand` crate at - [`docs.rs/rand`](https://docs.rs/rand). - -* This course does not include rustdoc on slides, just to save space, but in - real code they should be present. - -* Inner doc comments are discussed later (in the page on modules) and need not - be addressed here. - -* Rustdoc comments can contain code snippets that we can run and test using `cargo test`. - We will discuss these tests in the [Testing section](../testing/doc-tests.html). - -
-# Methods - -Methods are functions associated with a type. The `self` argument of a method is -an instance of the type it is associated with: - -```rust,editable -struct Rectangle { - width: u32, - height: u32, -} - -impl Rectangle { - fn area(&self) -> u32 { - self.width * self.height - } - - fn inc_width(&mut self, delta: u32) { - self.width += delta; - } -} - -fn main() { - let mut rect = Rectangle { width: 10, height: 5 }; - println!("old area: {}", rect.area()); - rect.inc_width(5); - println!("new area: {}", rect.area()); -} -``` - -* We will look much more at methods in today's exercise and in tomorrow's class. - -
- -- Add a static method called `Rectangle::new` and call this from `main`: - - ```rust,editable,compile_fail - fn new(width: u32, height: u32) -> Rectangle { - Rectangle { width, height } - } - ``` - -- While _technically_, Rust does not have custom constructors, static methods are commonly used to initialize structs (but don't have to). - The actual constructor, `Rectangle { width, height }`, could be called directly. See the [Rustnomicon](https://doc.rust-lang.org/nomicon/constructors.html). - -- Add a `Rectangle::square(width: u32)` constructor to illustrate that such static methods can take arbitrary parameters. - -
-# Function Overloading - -Overloading is not supported: - -* Each function has a single implementation: - * Always takes a fixed number of parameters. - * Always takes a single set of parameter types. -* Default values are not supported: - * All call sites have the same number of arguments. - * Macros are sometimes used as an alternative. - -However, function parameters can be generic: - -```rust,editable -fn pick_one(a: T, b: T) -> T { - if std::process::id() % 2 == 0 { a } else { b } -} - -fn main() { - println!("coin toss: {}", pick_one("heads", "tails")); - println!("cash prize: {}", pick_one(500, 1000)); -} -``` - -
- -* When using generics, the standard library's `Into` can provide a kind of limited - polymorphism on argument types. We will see more details in a later section. - -
- diff --git a/src/control-flow-basics/loops.md b/src/control-flow-basics/loops.md new file mode 100644 index 000000000000..7662164df45a --- /dev/null +++ b/src/control-flow-basics/loops.md @@ -0,0 +1,57 @@ +--- +minutes: 5 +--- + +# Loops + +There are three looping keywords in Rust: `while`, `loop`, and `for`: + +## `while` + +The [`while` keyword](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-loops) +works much like in other langages: + +```rust,editable +fn main() { + let mut x = 200; + while x >= 10 { + x = x / 2; + } + println!("Final x: {x}"); +} +``` + +## `for` + +The [`for` loop](https://doc.rust-lang.org/std/keyword.for.html) iterates over +ranges of values: + +```rust,editable +fn main() { + for x in 1..=5 { + println!("x: {x}"); + } +} +``` + +## `loop` + +The [`loop` statement](https://doc.rust-lang.org/std/keyword.loop.html) just +loops forever, until a `break`. + +```rust,editable +fn main() { + let mut i = 0; + loop { + i += 1; + println!("{i}"); + } +} +``` + +
+ +* We will discuss iteration later; for now, just stick to range expressions, + highlighting the `a..=b` syntax for an inclusive range. + +
diff --git a/src/control-flow-basics/println-and-dbg.md b/src/control-flow-basics/println-and-dbg.md index d0f36692097b..95351c225247 100644 --- a/src/control-flow-basics/println-and-dbg.md +++ b/src/control-flow-basics/println-and-dbg.md @@ -4,3 +4,26 @@ minutes: 2 # println! and dbg! +These macros are useful for basic output and debugging. + +``rust,editable +fn factorial(n: u32) -> u32 { + let mut product = 1; + for i in 1..=n { + product *= dbg!(i); + } + product +} + +fn main() { + println!("13!: {}", factorial(13)); +} +``` + +The `println!` macro formats and prints a line. The `{..}` syntax can contain a +variable name (but not an expression) or refer to a later argument to the macro. +The template syntax is documented in +[`std::fmt`](https://doc.rust-lang.org/std/fmt/index.html). + +The `dbg!` macro prints its argument to stdout and evalutes to its value. This +is a quick way to inspect a value without changing program execution. diff --git a/src/type-inference-and-conversions/exercise.md b/src/type-inference-and-conversions/exercise.md index 4e3e68ce6c3d..5399f38e83f3 100644 --- a/src/type-inference-and-conversions/exercise.md +++ b/src/type-inference-and-conversions/exercise.md @@ -4,3 +4,31 @@ minutes: 15 # Exercise: Fizz Buzz + +```rust,editable +fn main() { + print_fizzbuzz_to(20); +} + +fn is_divisible(n: u32, divisor: u32) -> bool { + if divisor == 0 { + return false; + } + n % divisor == 0 +} + +fn fizzbuzz(n: u32) -> String { + let fizz = if is_divisible(n, 3) { "fizz" } else { "" }; + let buzz = if is_divisible(n, 5) { "buzz" } else { "" }; + if fizz.is_empty() && buzz.is_empty() { + return format!("{n}"); + } + format!("{fizz}{buzz}") +} + +fn print_fizzbuzz_to(n: u32) { + for i in 1..=n { + println!("{}", fizzbuzz(i)); + } +} +```