1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-06-17 14:47:35 +02:00

Comprehensive Rust v2 (#1073)

I've taken some work by @fw-immunant and others on the new organization
of the course and condensed it into a form amenable to a text editor and
some computational analysis. You can see the inputs in `course.py` but
the interesting bits are the output: `outline.md` and `slides.md`.

The idea is to break the course into more, smaller segments with
exercises at the ends and breaks in between. So `outline.md` lists the
segments, their duration, and sums those durations up per-day. It shows
we're about an hour too long right now! There are more details of the
segments in `slides.md`, or you can see mostly the same stuff in
`course.py`.

This now contains all of the content from the v1 course, ensuring both
that we've covered everything and that we'll have somewhere to redirect
every page.

Fixes #1082.
Fixes #1465.

---------

Co-authored-by: Nicole LeGare <dlegare.1001@gmail.com>
Co-authored-by: Martin Geisler <mgeisler@google.com>
This commit is contained in:
Dustin J. Mitchell
2023-11-29 10:39:24 -05:00
committed by GitHub
parent ea204774b6
commit 6d19292f16
309 changed files with 6807 additions and 4281 deletions

View File

@ -0,0 +1,10 @@
[package]
name = "tuples-and-arrays"
version = "0.1.0"
edition = "2021"
publish = false
[[bin]]
name = "transpose"
path = "exercise.rs"

View File

@ -0,0 +1,44 @@
---
minutes: 5
---
# Destructuring
Destructuring is a way of extracting data from a data structure by writing a
pattern that is matched up to the data structure, binding variables to
subcomponents of the data structure.
You can destructure tuples and arrays by matching on their elements:
## Tuples
```rust,editable
fn main() {
describe_point((1, 0));
}
fn describe_point(point: (i32, i32)) {
match point {
(0, _) => println!("on Y axis"),
(_, 0) => println!("on X axis"),
(x, _) if x < 0 => println!("left of Y axis"),
(_, y) if y < 0 => println!("below X axis"),
_ => println!("first quadrant"),
}
}
```
## Arrays
```rust,editable
{{#include ../../third_party/rust-by-example/destructuring-arrays.rs}}
```
<details>
* Create a new array pattern using `_` to represent an element.
* Add more values to the array.
* Point out that how `..` will expand to account for different number of elements.
* Show matching against the tail with patterns `[.., b]` and `[a@..,b]`
</details>

View File

@ -0,0 +1,47 @@
---
minutes: 30
---
# Exercise: Nested Arrays
Arrays can contain other arrays:
```rust
let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
```
What is the type of this variable?
Use an array such as the above to write a function `transpose` which will
transpose a matrix (turn rows into columns):
<!-- mdbook-xgettext: skip -->
```bob
⎛⎡1 2 3⎤⎞ ⎡1 4 7⎤
"transpose"⎜⎢4 5 6⎥⎟ "=="⎢2 5 8⎥
⎝⎣7 8 9⎦⎠ ⎣3 6 9⎦
```
Hard-code both functions to operate on 3 × 3 matrices.
Copy the code below to <https://play.rust-lang.org/> and implement the
functions:
```rust,should_panic
// TODO: remove this when you're done with your implementation.
#![allow(unused_variables, dead_code)]
{{#include exercise.rs:transpose}}
unimplemented!()
}
{{#include exercise.rs:main}}
```
<details>
The `transpose` function takes its argument by value, but we haven't covered
ownership yet. Try printing a matrix after it has been transposed, to show the
"value has been moved" error, as a preview of ownership and move semantics.
</details>

View File

@ -0,0 +1,60 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: solution
// ANCHOR: transpose
fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
// ANCHOR_END: transpose
let mut result = [[0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
result[j][i] = matrix[i][j];
}
}
result
}
// ANCHOR: tests
#[test]
fn test_transpose() {
let matrix = [
[101, 102, 103], //
[201, 202, 203],
[301, 302, 303],
];
let transposed = transpose(matrix);
assert_eq!(
transposed,
[
[101, 201, 301], //
[102, 202, 302],
[103, 203, 303],
]
);
}
// ANCHOR_END: tests
// ANCHOR: main
fn main() {
let matrix = [
[101, 102, 103], // <-- the comment makes rustfmt add a newline
[201, 202, 203],
[301, 302, 303],
];
println!("matrix: {:#?}", matrix);
let transposed = transpose(matrix);
println!("transposed: {:#?}", transposed);
}
// ANCHOR_END: main

View File

@ -0,0 +1,28 @@
---
minutes: 3
---
# Array Iteration
The `for` statement supports iterating over arrays (but not tuples).
```rust,editable
fn main() {
let primes = [2, 3, 5, 7, 11, 13, 17, 19];
for prime in primes {
for i in 2..prime {
assert_ne!(prime % i, 0);
}
}
}
```
<details>
This functionality uses the `IntoIterator` trait, but we haven't covered that yet.
The `assert_ne!` macro is new here. There are also `assert_eq!` and `assert!`
macros. These are always checked while, debug-only variants like
`debug_assert!` compile to nothing in release builds.
</details>

View File

@ -0,0 +1,50 @@
---
minutes: 10
---
# Pattern Matching
The `match` keyword lets you match a value against one or more _patterns_. The
comparisons are done from top to bottom and the first match wins.
The patterns can be simple values, similarly to `switch` in C and C++:
```rust,editable
fn main() {
let input = 'x';
match input {
'q' => println!("Quitting"),
'a' | 's' | 'w' | 'd' => println!("Moving around"),
'0'..='9' => println!("Number input"),
key if key.is_lowercase() => println!("Lowercase: {key}"),
_ => println!("Something else"),
}
}
```
The `_` pattern is a wildcard pattern which matches any value. The expressions
_must_ be irrefutable, meaning that it covers every possibility, so `_` is
often used as the final catch-all case.
Match can be used as an expression. Just like like `if`, each match arm must have the same type. The type is the last
expression of the block, if any. In the example above, the type is `()`.
A variable in the pattern (`key` in this example) will create a binding that
can be used within the match arm.
A match guard causes the arm to match only if the condition is true.
<details>
Key Points:
* You might point out how some specific characters are being used when in a pattern
* `|` as an `or`
* `..` can expand as much as it needs to be
* `1..=5` represents an inclusive range
* `_` is a wild card
* Match guards as a separate syntax feature are important and necessary when we wish to concisely express more complex ideas than patterns alone would allow.
* They are not the same as separate `if` expression inside of the match arm. An `if` expression inside of the branch block (after `=>`) happens after the match arm is selected. Failing the `if` condition inside of that block won't result in other arms
of the original `match` expression being considered.
* The condition defined in the guard applies to every expression in a pattern with an `|`.
</details>

View File

@ -0,0 +1,5 @@
# Solution
```rust,editable
{{#include exercise.rs:solution}}
```

View File

@ -0,0 +1,77 @@
---
minutes: 10
---
# Tuples and Arrays
Tuples and arrays are the first "compound" types we have seen. All elements of
an array have the same type, while tuples can accommodate different types.
Both types have a size fixed at compile time.
| | Types | Literals |
|--------|-------------------------------|-----------------------------------|
| Arrays | `[T; N]` | `[20, 30, 40]`, `[0; 3]` |
| Tuples | `()`, `(T,)`, `(T1, T2)`, ... | `()`, `('x',)`, `('x', 1.2)`, ... |
Array assignment and access:
<!-- mdbook-xgettext: skip -->
```rust,editable
fn main() {
let mut a: [i8; 10] = [42; 10];
a[5] = 0;
println!("a: {a:?}");
}
```
Tuple assignment and access:
<!-- mdbook-xgettext: skip -->
```rust,editable
fn main() {
let t: (i8, bool) = (7, true);
println!("t.0: {}", t.0);
println!("t.1: {}", t.1);
}
```
<details>
Key points:
Arrays:
* A value of the array type `[T; N]` holds `N` (a compile-time constant) elements of the same type `T`.
Note that the length of the array is *part of its type*, which means that `[u8; 3]` and
`[u8; 4]` are considered two different types. Slices, which have a size determined at runtime,
are covered later.
* Try accessing an out-of-bounds array element. Array accesses are checked at
runtime. Rust can usually optimize these checks away, and they can be avoided
using unsafe Rust.
* We can use literals to assign values to arrays.
* The `println!` macro asks for the debug implementation with the `?` format
parameter: `{}` gives the default output, `{:?}` gives the debug output. Types such as
integers and strings implement the default output, but arrays only implement the debug output.
This means that we must use debug output here.
* Adding `#`, eg `{a:#?}`, invokes a "pretty printing" format, which can be easier to read.
Tuples:
* Like arrays, tuples have a fixed length.
* Tuples group together values of different types into a compound type.
* Fields of a tuple can be accessed by the period and the index of the value, e.g. `t.0`, `t.1`.
* The empty tuple `()` is also known as the "unit type". It is both a type, and
the only valid value of that type --- that is to say both the type and its value
are expressed as `()`. It is used to indicate, for example, that a function or
expression has no return value, as we'll see in a future slide.
* You can think of it as `void` that can be familiar to you from other
programming languages.
</details>