You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-06-27 19:18:59 +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:
committed by
GitHub
parent
ea204774b6
commit
6d19292f16
9
src/std-types/Cargo.toml
Normal file
9
src/std-types/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "std-types"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "hashset"
|
||||
path = "exercise.rs"
|
45
src/std-types/docs.md
Normal file
45
src/std-types/docs.md
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
minutes: 5
|
||||
---
|
||||
|
||||
# Language Docs
|
||||
|
||||
Rust comes with extensive documentation of the language and the standard library.
|
||||
|
||||
For example:
|
||||
* All of the details about [loops](https://doc.rust-lang.org/stable/reference/expressions/loop-expr.html).
|
||||
* Primitive types like [`u8`](https://doc.rust-lang.org/stable/std/primitive.u8.html).
|
||||
* Standard-library items like [`Option`](https://doc.rust-lang.org/stable/std/option/enum.Option.html) or [`BinaryHeap`](https://doc.rust-lang.org/stable/std/collections/struct.BinaryHeap.html).
|
||||
|
||||
In fact, you can document your own code:
|
||||
|
||||
```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;
|
||||
}
|
||||
lhs % rhs == 0
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
To document an item from inside the item (such as inside a module), use `//!`
|
||||
or `/*! .. */`, called "inner doc comments":
|
||||
|
||||
```rust,editable
|
||||
//! This module contains functionality relating to divisibility of integers.
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Show students the generated docs for the `rand` crate at
|
||||
[`docs.rs/rand`](https://docs.rs/rand).
|
||||
|
||||
</details>
|
54
src/std-types/exercise.md
Normal file
54
src/std-types/exercise.md
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# Exercise: Counter
|
||||
|
||||
In this exercise you will take a very simple data structure and make it generic.
|
||||
It uses a
|
||||
[`std::collections::HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html)
|
||||
to keep track of which values have been seen and how many times each one has
|
||||
appeared.
|
||||
|
||||
The initial version of `Counter` is hard coded to only work for `u32` values.
|
||||
Make the struct and its methods generic over the type of value being tracked,
|
||||
that way `Counter` can track any type of value.
|
||||
|
||||
If you finish early, try using the
|
||||
[`entry`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html#method.entry)
|
||||
method to halve the number of hash lookups required to implement the `count`
|
||||
method.
|
||||
|
||||
```rust,compile_fail,editable
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Counter counts the number of times each value of type T has been seen.
|
||||
struct Counter {
|
||||
values: HashMap<u32, u64>,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
/// Create a new Counter.
|
||||
fn new() -> Self {
|
||||
Counter {
|
||||
values: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Count an occurrence of the given value.
|
||||
fn count(&mut self, value: u32) {
|
||||
if self.values.contains_key(&value) {
|
||||
*self.values.get_mut(&value).unwrap() += 1;
|
||||
} else {
|
||||
self.values.insert(value, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of times the given value has been seen.
|
||||
fn times_seen(&self, value: u32) -> u64 {
|
||||
self.values.get(&value).copied().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
{{#include exercise.rs:main}}
|
||||
```
|
64
src/std-types/exercise.rs
Normal file
64
src/std-types/exercise.rs
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
#![allow(unused_variables, dead_code)]
|
||||
// ANCHOR: solution
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Counter counts the number of times each value of type T has been seen.
|
||||
struct Counter<T: Eq + Hash> {
|
||||
values: HashMap<T, u64>,
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash> Counter<T> {
|
||||
/// Create a new Counter.
|
||||
fn new() -> Self {
|
||||
Counter {
|
||||
values: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Count an occurrence of the given value.
|
||||
fn count(&mut self, value: T) {
|
||||
*self.values.entry(value).or_default() += 1;
|
||||
}
|
||||
|
||||
/// Return the number of times the given value has been seen.
|
||||
fn times_seen(&self, value: T) -> u64 {
|
||||
self.values.get(&value).copied().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: main
|
||||
fn main() {
|
||||
let mut ctr = Counter::new();
|
||||
ctr.count(13);
|
||||
ctr.count(14);
|
||||
ctr.count(16);
|
||||
ctr.count(14);
|
||||
ctr.count(14);
|
||||
ctr.count(11);
|
||||
|
||||
for i in 10..20 {
|
||||
println!("saw {} values equal to {}", ctr.times_seen(i), i);
|
||||
}
|
||||
|
||||
let mut strctr = Counter::new();
|
||||
strctr.count("apple");
|
||||
strctr.count("orange");
|
||||
strctr.count("apple");
|
||||
println!("got {} apples", strctr.times_seen("apple"));
|
||||
}
|
||||
// ANCHOR_END: main
|
72
src/std-types/hashmap.md
Normal file
72
src/std-types/hashmap.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# `HashMap`
|
||||
|
||||
Standard hash map with protection against HashDoS attacks:
|
||||
|
||||
```rust,editable
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let mut page_counts = HashMap::new();
|
||||
page_counts.insert("Adventures of Huckleberry Finn".to_string(), 207);
|
||||
page_counts.insert("Grimms' Fairy Tales".to_string(), 751);
|
||||
page_counts.insert("Pride and Prejudice".to_string(), 303);
|
||||
|
||||
if !page_counts.contains_key("Les Misérables") {
|
||||
println!("We know about {} books, but not Les Misérables.",
|
||||
page_counts.len());
|
||||
}
|
||||
|
||||
for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
|
||||
match page_counts.get(book) {
|
||||
Some(count) => println!("{book}: {count} pages"),
|
||||
None => println!("{book} is unknown.")
|
||||
}
|
||||
}
|
||||
|
||||
// Use the .entry() method to insert a value if nothing is found.
|
||||
for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
|
||||
let page_count: &mut i32 = page_counts.entry(book.to_string()).or_insert(0);
|
||||
*page_count += 1;
|
||||
}
|
||||
|
||||
println!("{page_counts:#?}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* `HashMap` is not defined in the prelude and needs to be brought into scope.
|
||||
* Try the following lines of code. The first line will see if a book is in the hashmap and if not return an alternative value. The second line will insert the alternative value in the hashmap if the book is not found.
|
||||
|
||||
```rust,ignore
|
||||
let pc1 = page_counts
|
||||
.get("Harry Potter and the Sorcerer's Stone")
|
||||
.unwrap_or(&336);
|
||||
let pc2 = page_counts
|
||||
.entry("The Hunger Games".to_string())
|
||||
.or_insert(374);
|
||||
```
|
||||
* Unlike `vec!`, there is unfortunately no standard `hashmap!` macro.
|
||||
* Although, since Rust 1.56, HashMap implements [`From<[(K, V); N]>`][1], which allows us to easily initialize a hash map from a literal array:
|
||||
|
||||
```rust,ignore
|
||||
let page_counts = HashMap::from([
|
||||
("Harry Potter and the Sorcerer's Stone".to_string(), 336),
|
||||
("The Hunger Games".to_string(), 374),
|
||||
]);
|
||||
```
|
||||
|
||||
* Alternatively HashMap can be built from any `Iterator` which yields key-value tuples.
|
||||
* We are showing `HashMap<String, i32>`, 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 the Rust docs. Show students the docs for this type, and the helpful link back to the `keys` method.
|
||||
|
||||
[1]: https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.html#impl-From%3C%5B(K,+V);+N%5D%3E-for-HashMap%3CK,+V,+RandomState%3E
|
||||
|
||||
</details>
|
32
src/std-types/option.md
Normal file
32
src/std-types/option.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# Option
|
||||
|
||||
We have already seen some use of `Option<T>`. It stores either a
|
||||
value of type `T` or nothing. For example,
|
||||
[`String::find`](https://doc.rust-lang.org/stable/std/string/struct.String.html#method.find)
|
||||
returns an `Option<usize>`.
|
||||
|
||||
```rust,editable,should_panic
|
||||
fn main() {
|
||||
let name = "Löwe 老虎 Léopard Gepardi";
|
||||
let mut position: Option<usize> = name.find('é');
|
||||
println!("find returned {position:?}");
|
||||
assert_eq!(position.unwrap(), 14);
|
||||
position = name.find('Z');
|
||||
println!("find returned {position:?}");
|
||||
assert_eq!(position.expect("Character not found"), 0);
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* `Option` is widely used, not just in the standard library.
|
||||
* `unwrap` will return the value in an `Option`, or panic. `expect` is similar but takes an error message.
|
||||
* You can panic on None, but you can't "accidentally" forget to check for None.
|
||||
* It's common to `unwrap`/`expect` all over the place when hacking something together, but production code typically handles `None` in a nicer fashion.
|
||||
* The niche optimization means that `Option<T>` often has the same size in memory as `T`.
|
||||
|
||||
</details>
|
43
src/std-types/result.md
Normal file
43
src/std-types/result.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# Result
|
||||
|
||||
`Result` is similar to `Option`, but indicates the success or failure of an
|
||||
operation, each with a different type. This is similar to the `Res` defined
|
||||
in the expression exercise, but generic: `Result<T, E>` where `T` is used in
|
||||
the `Ok` variant and `E` appears in the `Err` variant.
|
||||
|
||||
```rust,editable
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
fn main() {
|
||||
let file: Result<File, std::io::Error> = File::open("diary.txt");
|
||||
match file {
|
||||
Ok(mut file) => {
|
||||
let mut contents = String::new();
|
||||
if let Ok(bytes) = file.read_to_string(&mut contents) {
|
||||
println!("Dear diary: {contents} ({bytes} bytes)");
|
||||
} else {
|
||||
println!("Could not read file content");
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
println!("The diary could not be opened: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* As with `Option`, the successful value sits inside of `Result`, forcing the developer to
|
||||
explicitly extract it. This encourages error checking. In the case where an error should never happen,
|
||||
`unwrap()` or `expect()` can be called, and this is a signal of the developer intent too.
|
||||
* `Result` documentation is a recommended read. Not during the course, but it is worth mentioning.
|
||||
It contains a lot of convenience methods and functions that help functional-style programming.
|
||||
* `Result` is the standard type to implement error handling as we will see on Day 3.
|
||||
|
||||
</details>
|
5
src/std-types/solution.md
Normal file
5
src/std-types/solution.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Solution
|
||||
|
||||
```rust,editable
|
||||
{{#include exercise.rs:solution}}
|
||||
```
|
15
src/std-types/std.md
Normal file
15
src/std-types/std.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
minutes: 3
|
||||
---
|
||||
|
||||
# Standard Library
|
||||
|
||||
Rust comes with a standard library which helps establish a set of common types
|
||||
used by Rust libraries and programs. This way, two libraries can work together
|
||||
smoothly because they both use the same `String` type.
|
||||
|
||||
In fact, Rust contains several layers of the Standard Library: `core`, `alloc` and `std`.
|
||||
* `core` includes the most basic types and functions that don't depend on `libc`, allocator or
|
||||
even the presence of an operating system.
|
||||
* `alloc` includes types which require a global heap allocator, such as `Vec`, `Box` and `Arc`.
|
||||
* Embedded Rust applications often only use `core`, and sometimes `alloc`.
|
47
src/std-types/string.md
Normal file
47
src/std-types/string.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# String
|
||||
|
||||
[`String`][1] is the standard heap-allocated growable UTF-8 string buffer:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let mut s1 = String::new();
|
||||
s1.push_str("Hello");
|
||||
println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity());
|
||||
|
||||
let mut s2 = String::with_capacity(s1.len() + 1);
|
||||
s2.push_str(&s1);
|
||||
s2.push('!');
|
||||
println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity());
|
||||
|
||||
let s3 = String::from("🇨🇭");
|
||||
println!("s3: len = {}, number of chars = {}", s3.len(),
|
||||
s3.chars().count());
|
||||
}
|
||||
```
|
||||
|
||||
`String` implements [`Deref<Target = str>`][2], which means that you can call all
|
||||
`str` methods on a `String`.
|
||||
|
||||
[1]: https://doc.rust-lang.org/std/string/struct.String.html
|
||||
[2]: https://doc.rust-lang.org/std/string/struct.String.html#deref-methods-str
|
||||
|
||||
<details>
|
||||
|
||||
* `String::new` returns a new empty string, use `String::with_capacity` when you know how much data you want to push to the string.
|
||||
* `String::len` returns the size of the `String` in bytes (which can be different from its length in characters).
|
||||
* `String::chars` returns an iterator over the actual characters. Note that a `char` can be different from what a human will consider a "character" due to [grapheme clusters](https://docs.rs/unicode-segmentation/latest/unicode_segmentation/struct.Graphemes.html).
|
||||
* When people refer to strings they could either be talking about `&str` or `String`.
|
||||
* When a type implements `Deref<Target = T>`, the compiler will let you transparently call methods from `T`.
|
||||
* We haven't discussed the `Deref` trait yet, so at this point this mostly explains the structure of the sidebar in the documentation.
|
||||
* `String` implements `Deref<Target = str>` which transparently gives it access to `str`'s methods.
|
||||
* Write and compare `let s3 = s1.deref();` and `let s3 = &*s1;`.
|
||||
* `String` is implemented as a wrapper around a vector of bytes, many of the operations you see supported on vectors are also supported on `String`, but with some extra guarantees.
|
||||
* Compare the different ways to index a `String`:
|
||||
* To a character by using `s3.chars().nth(i).unwrap()` where `i` is in-bound, out-of-bounds.
|
||||
* To a substring by using `s3[0..4]`, where that slice is on character boundaries or not.
|
||||
|
||||
</details>
|
53
src/std-types/vec.md
Normal file
53
src/std-types/vec.md
Normal file
@ -0,0 +1,53 @@
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# `Vec`
|
||||
|
||||
[`Vec`][1] is the standard resizable heap-allocated buffer:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let mut v1 = Vec::new();
|
||||
v1.push(42);
|
||||
println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity());
|
||||
|
||||
let mut v2 = Vec::with_capacity(v1.len() + 1);
|
||||
v2.extend(v1.iter());
|
||||
v2.push(9999);
|
||||
println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity());
|
||||
|
||||
// Canonical macro to initialize a vector with elements.
|
||||
let mut v3 = vec![0, 0, 1, 2, 3, 4];
|
||||
|
||||
// Retain only the even elements.
|
||||
v3.retain(|x| x % 2 == 0);
|
||||
println!("{v3:?}");
|
||||
|
||||
// Remove consecutive duplicates.
|
||||
v3.dedup();
|
||||
println!("{v3:?}");
|
||||
}
|
||||
```
|
||||
|
||||
`Vec` implements [`Deref<Target = [T]>`][2], which means that you can call slice
|
||||
methods on a `Vec`.
|
||||
|
||||
[1]: https://doc.rust-lang.org/std/vec/struct.Vec.html
|
||||
[2]: https://doc.rust-lang.org/std/vec/struct.Vec.html#deref-methods-%5BT%5D
|
||||
|
||||
<details>
|
||||
|
||||
* `Vec` is a type of collection, along with `String` and `HashMap`. The data it contains is stored
|
||||
on the heap. This means the amount of data doesn't need to be known at compile time. It can grow
|
||||
or shrink at runtime.
|
||||
* Notice how `Vec<T>` is a generic type too, but you don't have to specify `T` explicitly. As always
|
||||
with Rust type inference, the `T` was established during the first `push` call.
|
||||
* `vec![...]` is a canonical macro to use instead of `Vec::new()` and it supports adding initial
|
||||
elements to the vector.
|
||||
* To index the vector you use `[` `]`, but they will panic if out of bounds. Alternatively, using
|
||||
`get` will return an `Option`. The `pop` function will remove the last element.
|
||||
* Slices are covered on day 3. For now, students only need to know that a value
|
||||
of type `Vec` gives access to all of the documented read-only slice methods, too.
|
||||
|
||||
</details>
|
Reference in New Issue
Block a user