mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-01 17:34:25 +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:
parent
ea204774b6
commit
6d19292f16
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,3 +15,4 @@ po/*.po~
|
||||
.idea/
|
||||
.iml
|
||||
.iws
|
||||
count.dat
|
||||
|
105
Cargo.lock
generated
105
Cargo.lock
generated
@ -176,6 +176,10 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borrowing"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.8.0"
|
||||
@ -314,6 +318,10 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "control-flow-basics"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
@ -541,6 +549,14 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-handling"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.1"
|
||||
@ -689,6 +705,10 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generics"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
@ -985,6 +1005,10 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iterators"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
@ -1139,11 +1163,15 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"matter",
|
||||
"mdbook",
|
||||
"pretty_env_logger",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1163,6 +1191,14 @@ version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "memory-management"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "methods-and-traits"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@ -1374,6 +1410,10 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pattern-matching"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
@ -1642,6 +1682,10 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "references"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
@ -1880,6 +1924,19 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servo_arc"
|
||||
version = "0.3.0"
|
||||
@ -1947,12 +2004,23 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slices-and-lifetimes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
|
||||
[[package]]
|
||||
name = "smart-pointers"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.10"
|
||||
@ -1988,6 +2056,14 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "std-traits"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "std-types"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.7"
|
||||
@ -2106,6 +2182,10 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "testing"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.50"
|
||||
@ -2312,12 +2392,20 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tuples-and-arrays"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "types-and-values"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.6"
|
||||
@ -2360,6 +2448,19 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-rust"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
@ -2371,6 +2472,10 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "user-defined-types"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
|
18
Cargo.toml
18
Cargo.toml
@ -2,6 +2,24 @@
|
||||
members = [
|
||||
"mdbook-exerciser",
|
||||
"mdbook-course",
|
||||
"src/types-and-values",
|
||||
"src/control-flow-basics",
|
||||
"src/tuples-and-arrays",
|
||||
"src/references",
|
||||
"src/user-defined-types",
|
||||
"src/pattern-matching",
|
||||
"src/methods-and-traits",
|
||||
"src/generics",
|
||||
"src/std-types",
|
||||
"src/std-traits",
|
||||
"src/iterators",
|
||||
"src/testing",
|
||||
"src/memory-management",
|
||||
"src/smart-pointers",
|
||||
"src/borrowing",
|
||||
"src/slices-and-lifetimes",
|
||||
"src/error-handling",
|
||||
"src/unsafe-rust",
|
||||
"src/exercises",
|
||||
"src/bare-metal/useful-crates/allocator-example",
|
||||
"src/bare-metal/useful-crates/zerocopy-example",
|
||||
|
39
NOTES.txt
Normal file
39
NOTES.txt
Normal file
@ -0,0 +1,39 @@
|
||||
# Before Merging:
|
||||
* Remove the timing / existing-content information from view
|
||||
* Remove cr2-organization/
|
||||
|
||||
# Plans for Later:
|
||||
|
||||
## High Priority
|
||||
|
||||
* Address slides that are too large for the aspect ratio, either by breaking
|
||||
them up, shrinking them down, or some other creative solution.
|
||||
|
||||
## Other Fixes
|
||||
|
||||
* Can the segment headers (src/segname.md) be auto-generated, or done with a
|
||||
macro? (#1415)
|
||||
* Can these include timing information
|
||||
* Can the per-day and overall-course sections also include timing info?
|
||||
|
||||
* Can CI detect deletion of a slide without corresponding book.toml entry?
|
||||
(#1417)
|
||||
* Simplify the GUI exercise (methods-and-traits)
|
||||
* Better examples for trait-bounds and impl trait, since students haven't yet
|
||||
seen std traits
|
||||
* Traits slide should mention associated types, trait parameters, trait
|
||||
"inheritance"
|
||||
* Improve iterator example (per suggestions from reader)
|
||||
* Modify `Interior Mutability` to use Mutex instead of Cell/RefCell, as noted
|
||||
in the comment in that file.
|
||||
* Replace the health-statistics exercise, which really has little to do with
|
||||
borowing. Maybe something that tempts students to use &mut self at the same
|
||||
time as an exclusive reference to a field of self?
|
||||
* Talk about `ref` and `mut` modifiers in pattern matching
|
||||
* Add "recaps" at the beginning of each day
|
||||
* "9.5. Type Aliases: The example shows how to declare, but on how it looks in
|
||||
practice is not clear enough (there is a use of one type alias but it's
|
||||
hidden in a complex type). Ideally I would add some function signatures or
|
||||
custom structs that showcase the cleanliness of this." (@deavid)
|
||||
* Cover trait objects after memory management, since they require some
|
||||
understanding of Box.
|
142
book.toml
142
book.toml
@ -34,7 +34,6 @@ additional-css = [
|
||||
"theme/css/svgbob.css",
|
||||
"theme/css/speaker-notes.css",
|
||||
"theme/css/language-picker.css",
|
||||
"theme/css/frontmatter.css",
|
||||
"theme/css/rtl.css",
|
||||
]
|
||||
site-url = "/comprehensive-rust/"
|
||||
@ -68,8 +67,6 @@ line-numbers = true
|
||||
"exercises/day-4/solutions-morning.html" = "../concurrency/solutions-morning.html"
|
||||
"exercises/concurrency/elevator.html" = "chat-app.html"
|
||||
"generics/closures.html" = "../traits/closures.html"
|
||||
"generics/impl-trait.html" = "../traits/impl-trait.html"
|
||||
"generics/trait-bounds.html" = "../traits/trait-bounds.html"
|
||||
"generics/trait-objects.html" = "../traits/trait-objects.html"
|
||||
"outros-recursos.html" = "other-resources.html"
|
||||
"running-the-course/day-4.html" = "course-structure.html"
|
||||
@ -78,7 +75,6 @@ line-numbers = true
|
||||
"unsafe/mutable-static-variables.md" = "mutable-static-variables.html"
|
||||
"unsafe/unsafe-functions.html" = "calling-unsafe-functions.html"
|
||||
"welcome-bare-metal.html" = "bare-metal.html"
|
||||
"welcome-day-4.html" = "concurrency.html"
|
||||
# Moving exercises between days
|
||||
"exercises/day-2/luhn.html" = "../day-1/luhn.html"
|
||||
"exercises/day-2/points-polygons.html" = "../day-3/points-polygons.html"
|
||||
@ -90,5 +86,143 @@ line-numbers = true
|
||||
[output.html.search]
|
||||
use-boolean-and = true
|
||||
|
||||
# redirects from course v1 to v2
|
||||
'welcome-day-1.html' = 'hello-world/welcome.html'
|
||||
'welcome-day-1/what-is-rust.html' = '../hello-world/what-is-rust.html'
|
||||
'hello-world/small-example.html' = '../hello-world/hello-world.html'
|
||||
'why-rust.html' = 'hello-world/benefits.html'
|
||||
'why-rust/compile-time.html' = '../hello-world/benefits.html'
|
||||
'why-rust/runtime.html' = '../hello-world/benefits.html'
|
||||
'why-rust/modern.html' = '../hello-world/benefits.html'
|
||||
'why-rust/an-example-in-c.html' = '../hello-world/example.html'
|
||||
'basic-syntax/variables.html' = '../types-and-values/variables.html'
|
||||
'basic-syntax/scalar-types.html' = '../types-and-values/values.html'
|
||||
'basic-syntax/type-inference.html' = '../types-and-values/inference.html'
|
||||
'basic-syntax.html' = 'control-flow-basics.html'
|
||||
'control-flow.html' = 'control-flow-basics.html'
|
||||
'control-flow/if-expressions.html' = '../control-flow-basics/conditionals.html'
|
||||
'control-flow/while-expressions.html' = '../control-flow-basics/loops.html'
|
||||
'control-flow/for-expressions.html' = '../control-flow-basics/loops.html'
|
||||
'control-flow/break-continue.html' = '../control-flow-basics/break-continue.html'
|
||||
'control-flow/loop-expressions.html' = '../control-flow-basics/loops.html'
|
||||
'basic-syntax/scopes-shadowing.html' = '../control-flow-basics/blocks-and-scopes.html'
|
||||
'control-flow/blocks.html' = '../control-flow-basics/blocks-and-scopes.html'
|
||||
'basic-syntax/functions.html' = '../control-flow-basics/functions.html'
|
||||
'basic-syntax/rustdoc.html' = '../std-types/docs.html'
|
||||
'basic-syntax/methods.html' = '../control-flow-basics/functions.html'
|
||||
'basic-syntax/functions-interlude.html' = '../control-flow-basics/functions.html'
|
||||
'exercises/day-1/morning.html' = '../../control-flow-basics/exercise.html'
|
||||
'exercises/day-1/afternoon.html' = '../../control-flow-basics/exercise.html'
|
||||
'exercises/day-2/morning.html' = '../../control-flow-basics/exercise.html'
|
||||
'exercises/day-2/afternoon.html' = '../../control-flow-basics/exercise.html'
|
||||
'exercises/day-3/morning.html' = '../../control-flow-basics/exercise.html'
|
||||
'exercises/day-3/afternoon.html' = '../../control-flow-basics/exercise.html'
|
||||
'basic-syntax/compound-types.html' = '../tuples-and-arrays/tuples-and-arrays.html'
|
||||
'control-flow/match-expressions.html' = '../tuples-and-arrays/match.html'
|
||||
'pattern-matching.html' = 'tuples-and-arrays/match.html'
|
||||
'pattern-matching/match-guards.html' = '../tuples-and-arrays/match.html'
|
||||
'pattern-matching/destructuring-arrays.html' = '../tuples-and-arrays/destructuring.html'
|
||||
'exercises/day-1/for-loops.html' = '../../tuples-and-arrays/exercise.html'
|
||||
'basic-syntax/references.html' = '../references/shared.html'
|
||||
'basic-syntax/references-dangling.html' = '../references/shared.html'
|
||||
'exercises/day-3/points-polygons.html' = '../../references/exercise.html'
|
||||
'structs.html' = 'user-defined-types/named-structs.html'
|
||||
'structs/field-shorthand.html' = '../user-defined-types/named-structs.html'
|
||||
'structs/tuple-structs.html' = '../user-defined-types/tuple-structs.html'
|
||||
'enums.html' = 'user-defined-types/enums.html'
|
||||
'enums/variant-payloads.html' = '../user-defined-types/enums.html'
|
||||
'enums/sizes.html' = '../user-defined-types/enums.html'
|
||||
'traits/deriving-traits.html' = '../methods-and-traits/deriving.html'
|
||||
'pattern-matching/destructuring-structs.html' = '../pattern-matching/destructuring.html'
|
||||
'pattern-matching/destructuring-enums.html' = '../pattern-matching/destructuring.html'
|
||||
'control-flow/novel.html' = '../pattern-matching/let-control-flow.html'
|
||||
'control-flow/if-let-expressions.html' = '../pattern-matching/let-control-flow.html'
|
||||
'control-flow/while-let-expressions.html' = '../pattern-matching/let-control-flow.html'
|
||||
'exercises/day-1/pattern-matching.html' = '../../user-defined-types/exercise.html'
|
||||
'welcome-day-2.html' = 'methods-and-traits/welcome.html'
|
||||
'methods.html' = 'methods-and-traits/methods.html'
|
||||
'methods/receiver.html' = '../methods-and-traits/methods.html'
|
||||
'methods/example.html' = '../methods-and-traits/methods.html'
|
||||
'traits.html' = 'methods-and-traits/traits.html'
|
||||
'traits/default-methods.html' = '../methods-and-traits/traits.html'
|
||||
'traits/trait-objects.html' = '../methods-and-traits/trait-objects.html'
|
||||
'exercises/day-3/simple-gui.html' = '../../methods-and-traits/exercise.html'
|
||||
'generics.html' = 'generics/generic-functions.html'
|
||||
'generics/monomorphization.html' = '../generics/generic-functions.html'
|
||||
'generics/data-types.html' = '../generics/generic-data.html'
|
||||
'generics/methods.html' = '../generics/generic-data.html'
|
||||
'traits/trait-bounds.html' = '../generics/trait-bounds.html'
|
||||
'traits/impl-trait.html' = '../generics/impl-trait.html'
|
||||
'std.html' = 'std-types/std.html'
|
||||
'std/option-result.html' = '../std-types/option.html'
|
||||
'error-handling/result.html' = '../std-types/result.html'
|
||||
'std/string.html' = '../std-types/string.html'
|
||||
'std/vec.html' = '../std-types/vec.html'
|
||||
'std/hashmap.html' = '../std-types/hashmap.html'
|
||||
'exercises/day-2/book-library.html' = '../../std-types/exercise.html'
|
||||
'traits/important-traits.html' = '../std-traits/comparisons.html'
|
||||
'traits/operators.html' = '../std-traits/operators.html'
|
||||
'traits/from-into.html' = '../std-traits/from-and-into.html'
|
||||
'traits/read-write.html' = '../std-traits/read-and-write.html'
|
||||
'traits/default.html' = '../std-traits/default.html'
|
||||
'traits/closures.html' = '../std-traits/closures.html'
|
||||
'exercises/day-1/implicit-conversions.html' = '../../std-traits/exercise.html'
|
||||
'traits/iterator.html' = '../iterators/iterators.html'
|
||||
'exercises/day-2/iterators-and-ownership.html' = '../../iterators/intoiterator.html'
|
||||
'traits/from-iterator.html' = '../iterators/fromiterator.html'
|
||||
'exercises/day-2/strings-iterators.html' = '../../iterators/exercise.html'
|
||||
'modules.html' = 'modules/modules.html'
|
||||
'testing.html' = 'testing/unit-tests.html'
|
||||
'testing/unit-tests.html' = '../testing/unit-tests.html'
|
||||
'testing/integration-tests.html' = '../testing/other.html'
|
||||
'testing/doc-tests.html' = '../testing/other.html'
|
||||
'exercises/day-1/luhn.html' = '../../testing/exercise.html'
|
||||
'welcome-day-3.html' = 'memory-management/welcome.html'
|
||||
'memory-management/stack-vs-heap.html' = '../memory-management/review.html'
|
||||
'memory-management/stack.html' = '../memory-management/review.html'
|
||||
'memory-management.html' = 'memory-management/approaches.html'
|
||||
'memory-management/manual.html' = '../memory-management/approaches.html'
|
||||
'memory-management/scope-based.html' = '../memory-management/approaches.html'
|
||||
'memory-management/garbage-collection.html' = '../memory-management/approaches.html'
|
||||
'ownership.html' = 'memory-management/ownership.html'
|
||||
'memory-management/rust.html' = '../memory-management/ownership.html'
|
||||
'ownership/move-semantics.html' = '../memory-management/move.html'
|
||||
'ownership/moved-strings-rust.html' = '../memory-management/move.html'
|
||||
'ownership/double-free-modern-cpp.html' = '../memory-management/move.html'
|
||||
'ownership/moves-function-calls.html' = '../memory-management/move.html'
|
||||
'ownership/copy-clone.html' = '../memory-management/copy-types.html'
|
||||
'traits/drop.html' = '../memory-management/drop.html'
|
||||
'std/box.html' = '../smart-pointers/box.html'
|
||||
'std/box-recursive.html' = '../smart-pointers/box.html'
|
||||
'std/box-niche.html' = '../smart-pointers/box.html'
|
||||
'std/rc.html' = '../smart-pointers/rc.html'
|
||||
'ownership/borrowing.html' = '../borrowing/shared.html'
|
||||
'ownership/shared-unique-borrows.html' = '../borrowing/shared.html'
|
||||
'std/cell.html' = '../borrowing/interior-mutability.html'
|
||||
'exercises/day-2/health-statistics.html' = '../../borrowing/exercise.html'
|
||||
'error-handling.html' = 'error-handling/panics.html'
|
||||
'error-handling/panic-unwind.html' = '../error-handling/panics.html'
|
||||
'error-handling/try-operator.html' = '../error-handling/try.html'
|
||||
'error-handling/converting-error-types.html' = '../error-handling/try-conversions.html'
|
||||
'error-handling/converting-error-types-example.html' = '../error-handling/try-conversions.html'
|
||||
'error-handling/deriving-error-enums.html' = '../error-handling/error.html'
|
||||
'error-handling/error-contexts.html' = '../error-handling/thiserror-and-anyhow.html'
|
||||
'error-handling/dynamic-errors.html' = '../error-handling/thiserror-and-anyhow.html'
|
||||
'basic-syntax/slices.html' = '../slices-and-lifetimes/slices.html'
|
||||
'basic-syntax/string-slices.html' = '../slices-and-lifetimes/str.html'
|
||||
'ownership/lifetimes.html' = '../slices-and-lifetimes/lifetime-annotations.html'
|
||||
'ownership/lifetimes-function-calls.html' = '../slices-and-lifetimes/lifetime-elision.html'
|
||||
'ownership/lifetimes-data-structures.html' = '../slices-and-lifetimes/struct-lifetimes.html'
|
||||
'unsafe.html' = 'unsafe-rust/unsafe.html'
|
||||
'unsafe/raw-pointers.html' = '../unsafe-rust/dereferencing.html'
|
||||
'basic-syntax/static-and-const.html' = '../unsafe-rust/static-and-const.html'
|
||||
'unsafe/mutable-static-variables.html' = '../unsafe-rust/mutable-static.html'
|
||||
'unsafe/unions.html' = '../unsafe-rust/unions.html'
|
||||
'unsafe/calling-unsafe-functions.html' = '../unsafe-rust/unsafe-functions.html'
|
||||
'unsafe/writing-unsafe-functions.html' = '../unsafe-rust/unsafe-functions.html'
|
||||
'unsafe/extern-functions.html' = '../unsafe-rust/unsafe-functions.html'
|
||||
'unsafe/unsafe-traits.html' = '../unsafe-rust/unsafe-traits.html'
|
||||
'exercises/day-3/safe-ffi-wrapper.html' = '../../unsafe-rust/exercise.html'
|
||||
|
||||
[output.exerciser]
|
||||
output-directory = "comprehensive-rust-exercises"
|
||||
|
@ -11,8 +11,12 @@ description = "An mdbook preprocessor for comprehensive-rust."
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
clap = "4.4.4"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4.17"
|
||||
matter = "0.1.0-alpha4"
|
||||
mdbook = "0.4.25"
|
||||
pretty_env_logger = "0.5.0"
|
||||
regex = "1.10"
|
||||
serde = "1.0.192"
|
||||
serde_json = "1.0.107"
|
||||
serde_yaml = "0.9"
|
||||
|
@ -6,12 +6,70 @@ Rust.
|
||||
## Frontmatter
|
||||
|
||||
The preprocessor parses "frontmatter" -- YAML between `---` at the beginning of
|
||||
a Markdown file -- and removes it from the rendered result. At the moment, to
|
||||
aid review of the new course, it places this content in a `<pre>` block.
|
||||
a Markdown file -- and removes it from the rendered result.
|
||||
|
||||
## Future Work
|
||||
Frontmatter is optional, and can contain any of the following fields, defined
|
||||
below:
|
||||
|
||||
- Parse the `minutes` property from frontmatter and
|
||||
- Generate a course timeline
|
||||
- Include timing information in the speaker notes
|
||||
- Generate per-segment tables of contents.
|
||||
```yaml
|
||||
minutes: NNN
|
||||
course: COURSE NAME
|
||||
session: SESSION NAME
|
||||
```
|
||||
|
||||
## Course Structure
|
||||
|
||||
A book can contain multiple _courses_. Each course is made up of _sessions_,
|
||||
which are blocks of instructional time (and include breaks). Typically two
|
||||
sessions are taught per day, morning and afternoon.
|
||||
|
||||
Each session is comprised of _segments_, which are slides on a related theme.
|
||||
Breaks are scheduled between segments.
|
||||
|
||||
Each segment is comprised of _slides_. A slide can be made up of one or more
|
||||
mdBook chapters.
|
||||
|
||||
The course structure is derived from the mdBook structure. Each top-level mdBook
|
||||
"section" is treated as a segment, and may optionally begin a new session or
|
||||
course. Within each section, the first chapter and subsequent second-level
|
||||
chapters are each treated as a slide. Any further-nested chapters are treated as
|
||||
parts of the parent slide. For example:
|
||||
|
||||
```ignore
|
||||
- [Frobnication](frobnication.md)
|
||||
- [Integer Frobnication](frobnication/integers.md)
|
||||
- [Frob Expansion](frobnication/expansion.md)
|
||||
- [Structs](frobnication/expansion-structs.md)
|
||||
- [Enums](frobnication/expansion-structs.md)
|
||||
- [Exercise](frobnication/exercise.md)
|
||||
- [Solution](frobnication/Solution.md)
|
||||
```
|
||||
|
||||
In this segment, there are four slides: "Frobnication", "Integer Frobnication",
|
||||
"Frob Expansion", and "Exercise". The last two slides are made up of multiple
|
||||
chapters.
|
||||
|
||||
The first chapter of a segment can use the `course` and `session` fields in its
|
||||
frontmatter to indicate that it is the first segment in a session or course.
|
||||
|
||||
## Timing
|
||||
|
||||
Each chapter should specify an estimate of the instructional time it will
|
||||
require in the `minutes` field. This information is summed, with breaks
|
||||
automatically added between segments, to give time estimates for segments,
|
||||
sessions, and courses.
|
||||
|
||||
## Directives
|
||||
|
||||
Within the course material, the following directives can be used:
|
||||
|
||||
```
|
||||
{{%segment outline}}
|
||||
{{%session outline}}
|
||||
{{%course outline}}
|
||||
{{%course outline COURSENAME}}
|
||||
```
|
||||
|
||||
These will be replaced with a markdown outline of the current segment, session,
|
||||
or course. The last directive can refer to another course by name and is used in
|
||||
the "Running the Course" section.
|
||||
|
451
mdbook-course/src/course.rs
Normal file
451
mdbook-course/src/course.rs
Normal file
@ -0,0 +1,451 @@
|
||||
// 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.
|
||||
|
||||
//! Representation of Comprehensive Rust as a hierarchy of types.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! Courses -- a collection of courses
|
||||
//! Course -- the level of content at which students enroll (fundamentals, android, etc.)
|
||||
//! Session -- a block of instructional time (typically morning or afternoon)
|
||||
//! Segment -- a collection of slides with a related theme
|
||||
//! Slide -- a single topic (may be represented by multiple mdBook chapters)
|
||||
//! ```
|
||||
//!
|
||||
//! This structure is parsed from the format of the book using a combination of the order in which
|
||||
//! chapters are listed in `SUMMARY.md` and annotations in the frontmatter of each chapter.
|
||||
//!
|
||||
//! A book contains a sequence of BookItems, each of which can contain sub-items. A top-level item
|
||||
//! can potentially introduce a new course, session, segment, and slide all in the same item. If
|
||||
//! the item has a `course` property in its frontmatter, then it introduces a new course. If it has
|
||||
//! a `session` property, then it introduces a new session. A top-level item always corresponds
|
||||
//! 1-to-1 with a segment (as long as it is a chapter), and that item becomes the first slide in
|
||||
//! that segment. Any other sub-items of the top-level item are treated as further slides in the
|
||||
//! same segment.
|
||||
|
||||
use crate::frontmatter::{split_frontmatter, Frontmatter};
|
||||
use crate::markdown::{duration, relative_link};
|
||||
use mdbook::book::{Book, BookItem, Chapter};
|
||||
use std::fmt::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Duration, in minutes, of breaks between segments in the course.
|
||||
const BREAK_DURATION: u64 = 10;
|
||||
|
||||
/// Courses is simply a collection of Courses.
|
||||
///
|
||||
/// Non-instructional material (such as the introduction) has `course: none` and
|
||||
/// is not included in this data structure.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Courses {
|
||||
pub courses: Vec<Course>,
|
||||
}
|
||||
|
||||
/// A Course is the level of content at which students enroll.
|
||||
///
|
||||
/// Courses are identified by the `course` property in a session's frontmatter. All
|
||||
/// sessions with the same value for `course` are grouped into a Course.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Course {
|
||||
pub name: String,
|
||||
pub sessions: Vec<Session>,
|
||||
}
|
||||
|
||||
/// A Session is a block of instructional time, containing segments. Typically a full day of
|
||||
/// instruction contains two sessions: morning and afternoon.
|
||||
///
|
||||
/// A session is identified by the `session` property in the session's frontmatter. There can be
|
||||
/// only one session with a given name in a course.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Session {
|
||||
pub name: String,
|
||||
pub segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
/// A Segment is a collection of slides with a related theme.
|
||||
///
|
||||
/// A segment is identified as a top-level chapter within a session.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Segment {
|
||||
pub name: String,
|
||||
pub slides: Vec<Slide>,
|
||||
}
|
||||
|
||||
/// A Slide presents a single topic. It may contain multiple mdBook chapters.
|
||||
///
|
||||
/// A slide is identified as an sub-chapter of a segment. Any sub-items of
|
||||
/// that chapter are also included in the slide.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Slide {
|
||||
pub name: String,
|
||||
/// Minutes this slide should take to teach.
|
||||
pub minutes: u64,
|
||||
/// Source paths (`.md` files) in this slide.
|
||||
pub source_paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Courses {
|
||||
/// Extract the course structure from the book. As a side-effect, the frontmatter is stripped
|
||||
/// from each slide.
|
||||
pub fn extract_structure(mut book: Book) -> anyhow::Result<(Self, Book)> {
|
||||
let mut courses = Courses::default();
|
||||
let mut current_course_name = None;
|
||||
let mut current_session_name = None;
|
||||
|
||||
for item in &mut book.sections {
|
||||
// We only want to process chapters, omitting part titles and separators.
|
||||
let BookItem::Chapter(chapter) = item else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let (frontmatter, content) = split_frontmatter(chapter)?;
|
||||
chapter.content = content;
|
||||
|
||||
// If 'course' is given, use that course (if not 'none') and reset the session.
|
||||
if let Some(course_name) = &frontmatter.course {
|
||||
current_session_name = None;
|
||||
if course_name == "none" {
|
||||
current_course_name = None;
|
||||
} else {
|
||||
current_course_name = Some(course_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// If 'session' is given, use that session.
|
||||
if let Some(session_name) = &frontmatter.session {
|
||||
current_session_name = Some(session_name.clone());
|
||||
}
|
||||
|
||||
if current_course_name.is_some() && current_session_name.is_none() {
|
||||
anyhow::bail!(
|
||||
"{:?}: 'session' must appear in frontmatter when 'course' appears",
|
||||
chapter.path
|
||||
);
|
||||
}
|
||||
|
||||
// If we have a course and session, then add this chapter to it as a segment.
|
||||
if let (Some(course_name), Some(session_name)) =
|
||||
(¤t_course_name, ¤t_session_name)
|
||||
{
|
||||
let course = courses.course_mut(course_name);
|
||||
let session = course.session_mut(session_name);
|
||||
session.add_segment(frontmatter, chapter)?;
|
||||
}
|
||||
}
|
||||
Ok((courses, book))
|
||||
}
|
||||
|
||||
/// Get a reference to a course, adding a new one if none by this name exists.
|
||||
fn course_mut(&mut self, name: impl AsRef<str>) -> &mut Course {
|
||||
let name = name.as_ref();
|
||||
if let Some(found_idx) =
|
||||
self.courses.iter().position(|course| &course.name == name)
|
||||
{
|
||||
return &mut self.courses[found_idx];
|
||||
}
|
||||
let course = Course::new(name);
|
||||
self.courses.push(course);
|
||||
self.courses.last_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Find a course by name.
|
||||
pub fn find_course(&self, name: impl AsRef<str>) -> Option<&Course> {
|
||||
let name = name.as_ref();
|
||||
self.courses.iter().find(|c| c.name == name)
|
||||
}
|
||||
|
||||
/// Find the slide generated from the given Chapter within these courses, returning the "path"
|
||||
/// to that slide.
|
||||
pub fn find_slide(
|
||||
&self,
|
||||
chapter: &Chapter,
|
||||
) -> Option<(&Course, &Session, &Segment, &Slide)> {
|
||||
let Some(ref source_path) = chapter.source_path else {
|
||||
return None;
|
||||
};
|
||||
|
||||
for course in self {
|
||||
for session in course {
|
||||
for segment in session {
|
||||
for slide in segment {
|
||||
if slide.source_paths.contains(source_path) {
|
||||
return Some((course, session, segment, slide));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Courses {
|
||||
type Item = &'a Course;
|
||||
type IntoIter = std::slice::Iter<'a, Course>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
(&self.courses).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Course {
|
||||
fn new(name: impl Into<String>) -> Self {
|
||||
Course {
|
||||
name: name.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to a session, adding a new one if none by this name exists.
|
||||
fn session_mut(&mut self, name: impl AsRef<str>) -> &mut Session {
|
||||
let name = name.as_ref();
|
||||
if let Some(found_idx) = self
|
||||
.sessions
|
||||
.iter()
|
||||
.position(|session| &session.name == name)
|
||||
{
|
||||
return &mut self.sessions[found_idx];
|
||||
}
|
||||
let session = Session::new(name);
|
||||
self.sessions.push(session);
|
||||
self.sessions.last_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Return the total duration of this course, as the sum of all segment durations.
|
||||
///
|
||||
/// This includes breaks between segments, but does not count time between between
|
||||
/// sessions.
|
||||
pub fn minutes(&self) -> u64 {
|
||||
self.into_iter().map(|s| s.minutes()).sum()
|
||||
}
|
||||
|
||||
/// Generate a Markdown schedule for this course, for placement at the given path.
|
||||
pub fn schedule(&self, at_source_path: impl AsRef<Path>) -> String {
|
||||
let mut outline = String::from("Course schedule:\n");
|
||||
for session in self {
|
||||
writeln!(
|
||||
&mut outline,
|
||||
" * {} ({}, including breaks)",
|
||||
session.name,
|
||||
duration(session.minutes())
|
||||
)
|
||||
.unwrap();
|
||||
for segment in session {
|
||||
if segment.minutes() == 0 {
|
||||
continue;
|
||||
}
|
||||
writeln!(
|
||||
&mut outline,
|
||||
" * [{}]({}) ({})",
|
||||
segment.name,
|
||||
relative_link(&at_source_path, &segment.slides[0].source_paths[0]),
|
||||
duration(segment.minutes())
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
outline
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Course {
|
||||
type Item = &'a Session;
|
||||
type IntoIter = std::slice::Iter<'a, Session>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
(&self.sessions).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
fn new(name: impl Into<String>) -> Self {
|
||||
Session {
|
||||
name: name.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new segment to the session, representing sub-items as slides.
|
||||
fn add_segment(
|
||||
&mut self,
|
||||
frontmatter: Frontmatter,
|
||||
chapter: &mut Chapter,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut segment = Segment::new(&chapter.name);
|
||||
segment.add_slide(frontmatter, chapter, false)?;
|
||||
for sub_chapter in &mut chapter.sub_items {
|
||||
let BookItem::Chapter(sub_chapter) = sub_chapter else {
|
||||
continue;
|
||||
};
|
||||
let (frontmatter, content) = split_frontmatter(sub_chapter)?;
|
||||
sub_chapter.content = content;
|
||||
|
||||
segment.add_slide(frontmatter, sub_chapter, true)?;
|
||||
}
|
||||
self.segments.push(segment);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a Markdown outline for this session, for placement at the given path.
|
||||
pub fn outline(&self, at_source_path: impl AsRef<Path>) -> String {
|
||||
let mut outline = String::from("In this session:\n");
|
||||
for segment in self {
|
||||
// Skip short segments (welcomes, wrap-up, etc.)
|
||||
if segment.minutes() == 0 {
|
||||
continue;
|
||||
}
|
||||
writeln!(
|
||||
&mut outline,
|
||||
" * [{}]({}) ({})",
|
||||
segment.name,
|
||||
relative_link(&at_source_path, &segment.slides[0].source_paths[0]),
|
||||
duration(segment.minutes())
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(&mut outline,"\nIncluding {BREAK_DURATION} minute breaks, this session should take about {}", duration(self.minutes())).unwrap();
|
||||
outline
|
||||
}
|
||||
|
||||
/// Return the total duration of this session.
|
||||
pub fn minutes(&self) -> u64 {
|
||||
let instructional_time: u64 = self.into_iter().map(|s| s.minutes()).sum();
|
||||
let breaks = (self.into_iter().filter(|s| s.minutes() > 0).count() - 1) as u64
|
||||
* BREAK_DURATION;
|
||||
instructional_time + breaks
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Session {
|
||||
type Item = &'a Segment;
|
||||
type IntoIter = std::slice::Iter<'a, Segment>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
(&self.segments).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
fn new(name: impl Into<String>) -> Self {
|
||||
Segment {
|
||||
name: name.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a slide from a chapter. If `recurse` is true, sub-items of this chapter are
|
||||
/// included in this slide as well.
|
||||
fn add_slide(
|
||||
&mut self,
|
||||
frontmatter: Frontmatter,
|
||||
chapter: &mut Chapter,
|
||||
recurse: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut slide = Slide::new(frontmatter, chapter);
|
||||
|
||||
if recurse {
|
||||
slide.add_sub_chapters(chapter)?;
|
||||
}
|
||||
self.slides.push(slide);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the total duration of this segment (the sum of the durations of the enclosed
|
||||
/// slides).
|
||||
pub fn minutes(&self) -> u64 {
|
||||
self.into_iter().map(|s| s.minutes()).sum()
|
||||
}
|
||||
|
||||
pub fn outline(&self, at_source_path: impl AsRef<Path>) -> String {
|
||||
let mut outline = String::from("In this segment:\n");
|
||||
for slide in self {
|
||||
if slide.minutes() == 0 {
|
||||
continue;
|
||||
}
|
||||
writeln!(
|
||||
&mut outline,
|
||||
" * [{}]({}) ({})",
|
||||
slide.name,
|
||||
relative_link(&at_source_path, &slide.source_paths[0]),
|
||||
duration(slide.minutes())
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(
|
||||
&mut outline,
|
||||
"\nThis segment should take about {}",
|
||||
duration(self.minutes())
|
||||
)
|
||||
.unwrap();
|
||||
outline
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Segment {
|
||||
type Item = &'a Slide;
|
||||
type IntoIter = std::slice::Iter<'a, Slide>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
(&self.slides).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Slide {
|
||||
fn new(frontmatter: Frontmatter, chapter: &Chapter) -> Self {
|
||||
let mut slide = Self {
|
||||
name: chapter.name.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
slide.add_frontmatter(&frontmatter);
|
||||
slide.push_source_path(&chapter.source_path);
|
||||
slide
|
||||
}
|
||||
|
||||
fn add_frontmatter(&mut self, frontmatter: &Frontmatter) {
|
||||
self.minutes += frontmatter.minutes.unwrap_or(0);
|
||||
}
|
||||
|
||||
fn push_source_path(&mut self, source_path: &Option<PathBuf>) {
|
||||
if let Some(source_path) = &source_path {
|
||||
self.source_paths.push(source_path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Add sub-chapters of this chapter to this slide (recursively).
|
||||
fn add_sub_chapters(&mut self, chapter: &mut Chapter) -> anyhow::Result<()> {
|
||||
for sub_slide in &mut chapter.sub_items {
|
||||
let BookItem::Chapter(sub_slide) = sub_slide else {
|
||||
continue;
|
||||
};
|
||||
let (frontmatter, content) = split_frontmatter(sub_slide)?;
|
||||
sub_slide.content = content;
|
||||
|
||||
if frontmatter.course.is_some() || frontmatter.session.is_some() {
|
||||
anyhow::bail!(
|
||||
"{:?}: sub-slides may not have 'course' or 'session' set",
|
||||
sub_slide.path
|
||||
);
|
||||
}
|
||||
self.add_frontmatter(&frontmatter);
|
||||
self.push_source_path(&sub_slide.source_path);
|
||||
self.add_sub_chapters(sub_slide)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the total duration of this slide.
|
||||
pub fn minutes(&self) -> u64 {
|
||||
self.minutes
|
||||
}
|
||||
}
|
@ -12,30 +12,28 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use anyhow::Context;
|
||||
use matter::matter;
|
||||
use mdbook::book::{Book, BookItem};
|
||||
use mdbook::preprocess::PreprocessorContext;
|
||||
use mdbook::book::Chapter;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn remove_frontmatter(
|
||||
ctx: &PreprocessorContext,
|
||||
book: &mut Book,
|
||||
) -> anyhow::Result<()> {
|
||||
let is_html = ctx.renderer == "html";
|
||||
book.for_each_mut(|chapter| {
|
||||
let BookItem::Chapter(chapter) = chapter else {
|
||||
return;
|
||||
};
|
||||
if let Some((frontmatter, content)) = matter(&chapter.content) {
|
||||
if is_html {
|
||||
// For the moment, include the frontmatter in the slide in a floating <pre>, for review
|
||||
// purposes.
|
||||
let pre = format!(r#"<pre class="frontmatter">{frontmatter}</pre>"#);
|
||||
chapter.content = format!("{pre}\n\n{content}");
|
||||
} else {
|
||||
// For non-HTML renderers, just strip the frontmatter.
|
||||
chapter.content = content;
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Frontmatter {
|
||||
pub minutes: Option<u64>,
|
||||
pub course: Option<String>,
|
||||
pub session: Option<String>,
|
||||
}
|
||||
|
||||
/// Split a chapter's contents into frontmatter and the remaining contents.
|
||||
pub fn split_frontmatter(chapter: &Chapter) -> anyhow::Result<(Frontmatter, String)> {
|
||||
if let Some((frontmatter, content)) = matter(&chapter.content) {
|
||||
let frontmatter: Frontmatter =
|
||||
serde_yaml::from_str(&frontmatter).with_context(|| {
|
||||
format!("error parsing frontmatter in {:?}", chapter.source_path)
|
||||
})?;
|
||||
|
||||
Ok((frontmatter, content))
|
||||
} else {
|
||||
Ok((Frontmatter::default(), chapter.content.clone()))
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod course;
|
||||
mod frontmatter;
|
||||
mod markdown;
|
||||
mod replacements;
|
||||
mod timing_info;
|
||||
|
||||
use crate::course::Courses;
|
||||
use crate::markdown::duration;
|
||||
use clap::{Arg, Command};
|
||||
use mdbook::book::BookItem;
|
||||
use mdbook::preprocess::CmdPreprocessor;
|
||||
use mdbook_course::frontmatter::remove_frontmatter;
|
||||
use std::io::{stdin, stdout};
|
||||
use std::process;
|
||||
|
||||
@ -37,9 +45,65 @@ fn main() {
|
||||
}
|
||||
|
||||
fn preprocess() -> anyhow::Result<()> {
|
||||
let (ctx, mut book) = CmdPreprocessor::parse_input(stdin())?;
|
||||
let (_, book) = CmdPreprocessor::parse_input(stdin())?;
|
||||
|
||||
remove_frontmatter(&ctx, &mut book)?;
|
||||
let (courses, mut book) = Courses::extract_structure(book)?;
|
||||
|
||||
book.for_each_mut(|chapter| {
|
||||
if let BookItem::Chapter(chapter) = chapter {
|
||||
if let Some((course, session, segment, slide)) = courses.find_slide(chapter) {
|
||||
timing_info::insert_timing_info(slide, chapter);
|
||||
replacements::replace(
|
||||
&courses,
|
||||
Some(course),
|
||||
Some(session),
|
||||
Some(segment),
|
||||
chapter,
|
||||
);
|
||||
} else {
|
||||
// Outside of a course, just perform replacements.
|
||||
replacements::replace(&courses, None, None, None, chapter);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let timediff = |actual, target| {
|
||||
if actual > target {
|
||||
format!(
|
||||
"{}: {} OVER TARGET {}",
|
||||
duration(actual),
|
||||
duration(actual - target),
|
||||
duration(target)
|
||||
)
|
||||
} else if actual < target {
|
||||
format!(
|
||||
"{}: {} shorter than target {}",
|
||||
duration(actual),
|
||||
duration(target - actual),
|
||||
duration(target)
|
||||
)
|
||||
} else {
|
||||
format!("{}: right on time", duration(actual))
|
||||
}
|
||||
};
|
||||
// Print a summary of times for the "Fundamentals" course.
|
||||
let fundamentals = courses.find_course("Fundamentals").unwrap();
|
||||
eprintln!(
|
||||
"Fundamentals: {}",
|
||||
timediff(fundamentals.minutes(), 8 * 3 * 60)
|
||||
);
|
||||
|
||||
eprintln!("Sessions:");
|
||||
for session in fundamentals {
|
||||
eprintln!(
|
||||
" {}: {}",
|
||||
session.name,
|
||||
timediff(session.minutes(), 3 * 60)
|
||||
);
|
||||
for segment in session {
|
||||
eprintln!(" {}: {}", segment.name, duration(segment.minutes()));
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::to_writer(stdout(), &book)?;
|
||||
Ok(())
|
||||
|
149
mdbook-course/src/markdown.rs
Normal file
149
mdbook-course/src/markdown.rs
Normal file
@ -0,0 +1,149 @@
|
||||
// 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.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// Given a source_path for the markdown file being rendered and a source_path for the target,
|
||||
/// generate a relative link.
|
||||
pub fn relative_link(
|
||||
doc_path: impl AsRef<Path>,
|
||||
target_path: impl AsRef<Path>,
|
||||
) -> String {
|
||||
let doc_path = doc_path.as_ref();
|
||||
let target_path = target_path.as_ref();
|
||||
|
||||
let mut dotdot = -1;
|
||||
for parent in doc_path.ancestors() {
|
||||
if target_path.starts_with(parent) {
|
||||
break;
|
||||
}
|
||||
dotdot += 1;
|
||||
}
|
||||
if dotdot > 0 {
|
||||
format!("{}{}", "../".repeat(dotdot as usize), target_path.display())
|
||||
} else {
|
||||
format!("./{}", target_path.display())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent the given duration in a human-readable way.
|
||||
///
|
||||
/// This will round times longer than 5 minutes to the next 5-minute interval.
|
||||
pub fn duration(mut minutes: u64) -> String {
|
||||
if minutes > 5 {
|
||||
minutes += 4;
|
||||
minutes -= minutes % 5;
|
||||
}
|
||||
|
||||
let (hours, minutes) = (minutes / 60, minutes % 60);
|
||||
match (hours, minutes) {
|
||||
(0, 1) => "1 minute".into(),
|
||||
(0, m) => format!("{m} minutes"),
|
||||
(1, 0) => "1 hour".into(),
|
||||
(1, m) => format!("1 hour and {m} minutes"),
|
||||
(h, 0) => format!("{h} hours"),
|
||||
(h, m) => format!("{h} hours and {m} minutes"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn relative_link_same_dir() {
|
||||
assert_eq!(
|
||||
relative_link(Path::new("welcome.md"), Path::new("hello-world.md")),
|
||||
"./hello-world.md".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_link_subdir() {
|
||||
assert_eq!(
|
||||
relative_link(Path::new("hello-world.md"), Path::new("hello-world/foo.md")),
|
||||
"./hello-world/foo.md".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_link_parent_dir() {
|
||||
assert_eq!(
|
||||
relative_link(Path::new("references/foo.md"), Path::new("hello-world.md")),
|
||||
"../hello-world.md".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_link_deep_parent_dir() {
|
||||
assert_eq!(
|
||||
relative_link(
|
||||
Path::new("references/foo/bar.md"),
|
||||
Path::new("hello-world.md")
|
||||
),
|
||||
"../../hello-world.md".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_link_peer_dir() {
|
||||
assert_eq!(
|
||||
relative_link(
|
||||
Path::new("references/foo.md"),
|
||||
Path::new("hello-world/foo.md")
|
||||
),
|
||||
"../hello-world/foo.md".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_no_time() {
|
||||
assert_eq!(duration(0), "0 minutes")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_single_minute() {
|
||||
assert_eq!(duration(1), "1 minute")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_two_minutes() {
|
||||
assert_eq!(duration(2), "2 minutes")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_seven_minutes() {
|
||||
assert_eq!(duration(7), "10 minutes")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_hour() {
|
||||
assert_eq!(duration(60), "1 hour")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_hour_mins() {
|
||||
assert_eq!(duration(61), "1 hour and 5 minutes")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_hours() {
|
||||
assert_eq!(duration(120), "2 hours")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_hours_mins() {
|
||||
assert_eq!(duration(130), "2 hours and 10 minutes")
|
||||
}
|
||||
}
|
61
mdbook-course/src/replacements.rs
Normal file
61
mdbook-course/src/replacements.rs
Normal file
@ -0,0 +1,61 @@
|
||||
// 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.
|
||||
|
||||
use crate::course::{Course, Courses, Segment, Session};
|
||||
use mdbook::book::Chapter;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref DIRECTIVE: Regex = Regex::new(r#"\{\{%([^}]*)}}"#).unwrap();
|
||||
}
|
||||
|
||||
/// Replace supported directives with the relevant content.
|
||||
///
|
||||
/// See the mdbook-course README for details.
|
||||
#[allow(unused_variables)]
|
||||
pub fn replace(
|
||||
courses: &Courses,
|
||||
course: Option<&Course>,
|
||||
session: Option<&Session>,
|
||||
segment: Option<&Segment>,
|
||||
chapter: &mut Chapter,
|
||||
) {
|
||||
let Some(source_path) = &chapter.source_path else {
|
||||
return;
|
||||
};
|
||||
chapter.content = DIRECTIVE
|
||||
.replace(&chapter.content, |captures: ®ex::Captures| {
|
||||
let directive_str = captures[1].trim();
|
||||
let directive: Vec<_> = directive_str.split_whitespace().collect();
|
||||
match directive.as_slice() {
|
||||
["session", "outline"] if session.is_some() => {
|
||||
session.unwrap().outline(source_path)
|
||||
}
|
||||
["segment", "outline"] if segment.is_some() => {
|
||||
segment.unwrap().outline(source_path)
|
||||
}
|
||||
["course", "outline"] if course.is_some() => {
|
||||
course.unwrap().schedule(source_path)
|
||||
}
|
||||
["course", "outline", course_name] => {
|
||||
let Some(course) = courses.find_course(course_name) else {
|
||||
return captures[0].to_string();
|
||||
};
|
||||
course.schedule(source_path)
|
||||
}
|
||||
_ => directive_str.to_owned(),
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
}
|
38
mdbook-course/src/timing_info.rs
Normal file
38
mdbook-course/src/timing_info.rs
Normal file
@ -0,0 +1,38 @@
|
||||
// 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.
|
||||
|
||||
use crate::course::Slide;
|
||||
use mdbook::book::Chapter;
|
||||
|
||||
/// Insert timing information for this slide into the speaker notes.
|
||||
pub fn insert_timing_info(slide: &Slide, chapter: &mut Chapter) {
|
||||
if slide.minutes > 0 && chapter.content.contains("<details>") {
|
||||
// Include the minutes in the speaker notes.
|
||||
let minutes = slide.minutes;
|
||||
let plural = if slide.minutes == 1 {
|
||||
"minute"
|
||||
} else {
|
||||
"minutes"
|
||||
};
|
||||
let mut subslides = "";
|
||||
if slide.source_paths.len() > 1 {
|
||||
subslides = "and its sub-slides ";
|
||||
}
|
||||
let timing_message =
|
||||
format!("This slide {subslides}should take about {minutes} {plural}. ");
|
||||
chapter.content = chapter
|
||||
.content
|
||||
.replace("<details>", &format!("<details>\n{timing_message}"));
|
||||
}
|
||||
}
|
333
src/SUMMARY.md
333
src/SUMMARY.md
@ -10,178 +10,192 @@
|
||||
- [Running Cargo Locally](cargo/running-locally.md)
|
||||
|
||||
|
||||
# Day 1: Morning
|
||||
|
||||
----
|
||||
|
||||
# Day 1: Morning
|
||||
|
||||
- [Welcome](welcome-day-1.md)
|
||||
- [What is Rust?](welcome-day-1/what-is-rust.md)
|
||||
- [Hello World!](hello-world.md)
|
||||
- [Small Example](hello-world/small-example.md)
|
||||
- [Why Rust?](why-rust.md)
|
||||
- [Compile Time Guarantees](why-rust/compile-time.md)
|
||||
- [Runtime Guarantees](why-rust/runtime.md)
|
||||
- [Modern Features](why-rust/modern.md)
|
||||
- [Basic Syntax](basic-syntax.md)
|
||||
- [Scalar Types](basic-syntax/scalar-types.md)
|
||||
- [Compound Types](basic-syntax/compound-types.md)
|
||||
- [References](basic-syntax/references.md)
|
||||
- [Dangling References](basic-syntax/references-dangling.md)
|
||||
- [Slices](basic-syntax/slices.md)
|
||||
- [String vs str](basic-syntax/string-slices.md)
|
||||
- [Functions](basic-syntax/functions.md)
|
||||
- [Rustdoc](basic-syntax/rustdoc.md)
|
||||
- [Methods](basic-syntax/methods.md)
|
||||
- [Overloading](basic-syntax/functions-interlude.md)
|
||||
- [Exercises](exercises/day-1/morning.md)
|
||||
- [Implicit Conversions](exercises/day-1/implicit-conversions.md)
|
||||
- [Arrays and for Loops](exercises/day-1/for-loops.md)
|
||||
- [Hello, World](hello-world.md)
|
||||
- [What is Rust?](hello-world/what-is-rust.md)
|
||||
- [Hello, World](hello-world/hello-world.md)
|
||||
- [Benefits of Rust](hello-world/benefits.md)
|
||||
- [Playground](hello-world/playground.md)
|
||||
- [Types and Values](types-and-values.md)
|
||||
- [Variables](types-and-values/variables.md)
|
||||
- [Values](types-and-values/values.md)
|
||||
- [Arithmetic](types-and-values/arithmetic.md)
|
||||
- [Strings](types-and-values/strings.md)
|
||||
- [Type Inference](types-and-values/inference.md)
|
||||
- [Exercise: Fibonacci](types-and-values/exercise.md)
|
||||
- [Solution](types-and-values/solution.md)
|
||||
- [Control Flow Basics](control-flow-basics.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)
|
||||
- [Macros](control-flow-basics/macros.md)
|
||||
- [Exercise: Collatz conjecture](control-flow-basics/exercise.md)
|
||||
- [Solution](control-flow-basics/solution.md)
|
||||
|
||||
# Day 1: Afternoon
|
||||
|
||||
- [Control Flow](control-flow.md)
|
||||
- [Blocks](control-flow/blocks.md)
|
||||
- [if expressions](control-flow/if-expressions.md)
|
||||
- [for expressions](control-flow/for-expressions.md)
|
||||
- [while expressions](control-flow/while-expressions.md)
|
||||
- [break & continue](control-flow/break-continue.md)
|
||||
- [loop expressions](control-flow/loop-expressions.md)
|
||||
- [Welcome](welcome-day-1-afternoon.md)
|
||||
- [Tuples and Arrays](tuples-and-arrays.md)
|
||||
- [Tuples and Arrays](tuples-and-arrays/tuples-and-arrays.md)
|
||||
- [Array Iteration](tuples-and-arrays/iteration.md)
|
||||
- [Pattern Matching](tuples-and-arrays/match.md)
|
||||
- [Destructuring](tuples-and-arrays/destructuring.md)
|
||||
- [Exercise: Nested Arrays](tuples-and-arrays/exercise.md)
|
||||
- [Solution](tuples-and-arrays/solution.md)
|
||||
- [References](references.md)
|
||||
- [Shared References](references/shared.md)
|
||||
- [Exclusive References](references/exclusive.md)
|
||||
- [Exercise: Geometry](references/exercise.md)
|
||||
- [Solution](references/solution.md)
|
||||
- [User-Defined Types](user-defined-types.md)
|
||||
- [Named Structs](user-defined-types/named-structs.md)
|
||||
- [Tuple Structs](user-defined-types/tuple-structs.md)
|
||||
- [Enums](user-defined-types/enums.md)
|
||||
- [Static and Const](user-defined-types/static-and-const.md)
|
||||
- [Type Aliases](user-defined-types/aliases.md)
|
||||
- [Exercise: Elevator Events](user-defined-types/exercise.md)
|
||||
- [Solution](user-defined-types/solution.md)
|
||||
|
||||
- [Variables](basic-syntax/variables.md)
|
||||
- [Type Inference](basic-syntax/type-inference.md)
|
||||
- [static & const](basic-syntax/static-and-const.md)
|
||||
- [Scopes and Shadowing](basic-syntax/scopes-shadowing.md)
|
||||
- [Enums](enums.md)
|
||||
- [Variant Payloads](enums/variant-payloads.md)
|
||||
- [Enum Sizes](enums/sizes.md)
|
||||
|
||||
- [Novel Control Flow](control-flow/novel.md)
|
||||
- [if let expressions](control-flow/if-let-expressions.md)
|
||||
- [while let expressions](control-flow/while-let-expressions.md)
|
||||
- [match expressions](control-flow/match-expressions.md)
|
||||
|
||||
- [Pattern Matching](pattern-matching.md)
|
||||
- [Destructuring Enums](pattern-matching/destructuring-enums.md)
|
||||
- [Destructuring Structs](pattern-matching/destructuring-structs.md)
|
||||
- [Destructuring Arrays](pattern-matching/destructuring-arrays.md)
|
||||
- [Match Guards](pattern-matching/match-guards.md)
|
||||
|
||||
- [Exercises](exercises/day-1/afternoon.md)
|
||||
- [Luhn Algorithm](exercises/day-1/luhn.md)
|
||||
- [Pattern Matching](exercises/day-1/pattern-matching.md)
|
||||
----
|
||||
|
||||
# Day 2: Morning
|
||||
|
||||
----
|
||||
|
||||
- [Welcome](welcome-day-2.md)
|
||||
|
||||
- [Memory Management](memory-management.md)
|
||||
- [Stack vs Heap](memory-management/stack-vs-heap.md)
|
||||
- [Stack Memory](memory-management/stack.md)
|
||||
- [Manual Memory Management](memory-management/manual.md)
|
||||
- [Scope-Based Memory Management](memory-management/scope-based.md)
|
||||
- [Garbage Collection](memory-management/garbage-collection.md)
|
||||
- [Rust Memory Management](memory-management/rust.md)
|
||||
- [Ownership](ownership.md)
|
||||
- [Move Semantics](ownership/move-semantics.md)
|
||||
- [Moved Strings in Rust](ownership/moved-strings-rust.md)
|
||||
- [Double Frees in Modern C++](ownership/double-free-modern-cpp.md)
|
||||
- [Moves in Function Calls](ownership/moves-function-calls.md)
|
||||
- [Copying and Cloning](ownership/copy-clone.md)
|
||||
- [Borrowing](ownership/borrowing.md)
|
||||
- [Shared and Unique Borrows](ownership/shared-unique-borrows.md)
|
||||
- [Lifetimes](ownership/lifetimes.md)
|
||||
- [Lifetimes in Function Calls](ownership/lifetimes-function-calls.md)
|
||||
- [Lifetimes in Data Structures](ownership/lifetimes-data-structures.md)
|
||||
- [Structs](structs.md)
|
||||
- [Tuple Structs](structs/tuple-structs.md)
|
||||
- [Field Shorthand Syntax](structs/field-shorthand.md)
|
||||
- [Methods](methods.md)
|
||||
- [Method Receiver](methods/receiver.md)
|
||||
- [Example](methods/example.md)
|
||||
- [Exercises](exercises/day-2/morning.md)
|
||||
- [Storing Books](exercises/day-2/book-library.md)
|
||||
- [Health Statistics](exercises/day-2/health-statistics.md)
|
||||
- [Pattern Matching](pattern-matching.md)
|
||||
- [Destructuring](pattern-matching/destructuring.md)
|
||||
- [Let Control Flow](pattern-matching/let-control-flow.md)
|
||||
- [Exercise: Expression Evaluation](pattern-matching/exercise.md)
|
||||
- [Solution](pattern-matching/solution.md)
|
||||
- [Methods and Traits](methods-and-traits.md)
|
||||
- [Methods](methods-and-traits/methods.md)
|
||||
- [Traits](methods-and-traits/traits.md)
|
||||
- [Deriving](methods-and-traits/deriving.md)
|
||||
- [Trait Objects](methods-and-traits/trait-objects.md)
|
||||
- [Exercise: GUI Library](methods-and-traits/exercise.md)
|
||||
- [Solution](methods-and-traits/solution.md)
|
||||
- [Generics](generics.md)
|
||||
- [Generic Functions](generics/generic-functions.md)
|
||||
- [Generic Data types](generics/generic-data.md)
|
||||
- [Trait Bounds](generics/trait-bounds.md)
|
||||
- [Impl Trait](generics/impl-trait.md)
|
||||
- [Exercise: Generic `min`](generics/exercise.md)
|
||||
- [Solution](generics/solution.md)
|
||||
|
||||
# Day 2: Afternoon
|
||||
|
||||
- [Standard Library](std.md)
|
||||
- [Option and Result](std/option-result.md)
|
||||
- [String](std/string.md)
|
||||
- [Vec](std/vec.md)
|
||||
- [HashMap](std/hashmap.md)
|
||||
- [Box](std/box.md)
|
||||
- [Recursive Data Types](std/box-recursive.md)
|
||||
- [Niche Optimization](std/box-niche.md)
|
||||
- [Rc](std/rc.md)
|
||||
- [Cell/RefCell](std/cell.md)
|
||||
- [Modules](modules.md)
|
||||
- [Visibility](modules/visibility.md)
|
||||
- [Paths](modules/paths.md)
|
||||
- [Filesystem Hierarchy](modules/filesystem.md)
|
||||
- [Exercises](exercises/day-2/afternoon.md)
|
||||
- [Iterators and Ownership](exercises/day-2/iterators-and-ownership.md)
|
||||
- [Strings and Iterators](exercises/day-2/strings-iterators.md)
|
||||
|
||||
|
||||
# Day 3: Morning
|
||||
- [Welcome](welcome-day-2-afternoon.md)
|
||||
- [Standard Library Types](std-types.md)
|
||||
- [Standard Library](std-types/std.md)
|
||||
- [Language Docs](std-types/docs.md)
|
||||
- [Option](std-types/option.md)
|
||||
- [Result](std-types/result.md)
|
||||
- [String](std-types/string.md)
|
||||
- [Vec](std-types/vec.md)
|
||||
- [HashMap](std-types/hashmap.md)
|
||||
- [Exercise: Counter](std-types/exercise.md)
|
||||
- [Solution](std-types/solution.md)
|
||||
- [Standard Library Traits](std-traits.md)
|
||||
- [Comparisons](std-traits/comparisons.md)
|
||||
- [Operators](std-traits/operators.md)
|
||||
- [From and Into](std-traits/from-and-into.md)
|
||||
- [Casting](std-traits/casting.md)
|
||||
- [Read and Write](std-traits/read-and-write.md)
|
||||
- [`Default`, struct update syntax](std-traits/default.md)
|
||||
- [Closures](std-traits/closures.md)
|
||||
- [Exercise: ROT13](std-traits/exercise.md)
|
||||
- [Solution](std-traits/solution.md)
|
||||
|
||||
----
|
||||
|
||||
# Day 3: Morning
|
||||
|
||||
- [Welcome](welcome-day-3.md)
|
||||
- [Generics](generics.md)
|
||||
- [Generic Data Types](generics/data-types.md)
|
||||
- [Generic Methods](generics/methods.md)
|
||||
- [Monomorphization](generics/monomorphization.md)
|
||||
- [Traits](traits.md)
|
||||
- [Trait Objects](traits/trait-objects.md)
|
||||
- [Deriving Traits](traits/deriving-traits.md)
|
||||
- [Default Methods](traits/default-methods.md)
|
||||
- [Trait Bounds](traits/trait-bounds.md)
|
||||
- [impl Trait](traits/impl-trait.md)
|
||||
- [Important Traits](traits/important-traits.md)
|
||||
- [Iterator](traits/iterator.md)
|
||||
- [FromIterator](traits/from-iterator.md)
|
||||
- [From and Into](traits/from-into.md)
|
||||
- [Read and Write](traits/read-write.md)
|
||||
- [Drop](traits/drop.md)
|
||||
- [Default](traits/default.md)
|
||||
- [Operators: Add, Mul, ...](traits/operators.md)
|
||||
- [Closures: Fn, FnMut, FnOnce](traits/closures.md)
|
||||
- [Exercises](exercises/day-3/morning.md)
|
||||
- [A Simple GUI Library](exercises/day-3/simple-gui.md)
|
||||
- [Points and Polygons](exercises/day-3/points-polygons.md)
|
||||
- [Memory Management](memory-management.md)
|
||||
- [Review of Program Memory](memory-management/review.md)
|
||||
- [Approaches to Memory Management](memory-management/approaches.md)
|
||||
- [Ownership](memory-management/ownership.md)
|
||||
- [Move semantics](memory-management/move.md)
|
||||
- [Clone](memory-management/clone.md)
|
||||
- [Copy Types](memory-management/copy-types.md)
|
||||
- [Drop](memory-management/drop.md)
|
||||
- [Exercise: Builder Type](memory-management/exercise.md)
|
||||
- [Solution](memory-management/solution.md)
|
||||
- [Smart Pointers](smart-pointers.md)
|
||||
- [Box<T>](smart-pointers/box.md)
|
||||
- [Rc](smart-pointers/rc.md)
|
||||
- [Exercise: Binary Tree](smart-pointers/exercise.md)
|
||||
- [Solution](smart-pointers/solution.md)
|
||||
|
||||
# Day 3: Afternoon
|
||||
|
||||
- [Welcome](welcome-day-3-afternoon.md)
|
||||
- [Borrowing](borrowing.md)
|
||||
- [Borrowing a Value](borrowing/shared.md)
|
||||
- [Borrow Checking](borrowing/borrowck.md)
|
||||
- [Interior Mutability](borrowing/interior-mutability.md)
|
||||
- [Exercise: Health Statistics](borrowing/exercise.md)
|
||||
- [Solution](borrowing/solution.md)
|
||||
- [Slices and Lifetimes](slices-and-lifetimes.md)
|
||||
- [Slices: `&[T]`](slices-and-lifetimes/slices.md)
|
||||
- [String References](slices-and-lifetimes/str.md)
|
||||
- [Lifetime Annotations](slices-and-lifetimes/lifetime-annotations.md)
|
||||
- [Lifetime Elision](slices-and-lifetimes/lifetime-elision.md)
|
||||
- [Struct Lifetimes](slices-and-lifetimes/struct-lifetimes.md)
|
||||
- [Exercise: Protobuf Parsing](slices-and-lifetimes/exercise.md)
|
||||
- [Solution](slices-and-lifetimes/solution.md)
|
||||
|
||||
---
|
||||
|
||||
# Day 4: Morning
|
||||
|
||||
- [Welcome](welcome-day-4.md)
|
||||
- [Iterators](iterators.md)
|
||||
- [Iterators](iterators/iterators.md)
|
||||
- [IntoIterator](iterators/intoiterator.md)
|
||||
- [FromIterator](iterators/fromiterator.md)
|
||||
- [Exercise: Iterator Method Chaining](iterators/exercise.md)
|
||||
- [Solution](iterators/solution.md)
|
||||
- [Modules](modules.md)
|
||||
- [Modules](modules/modules.md)
|
||||
- [Filesystem Hierarchy](modules/filesystem.md)
|
||||
- [Visibility](modules/visibility.md)
|
||||
- [use, super, self](modules/paths.md)
|
||||
- [Exercise: Modules for the GUI Library](modules/exercise.md)
|
||||
- [Solution](modules/solution.md)
|
||||
- [Testing](testing.md)
|
||||
- [Test Modules](testing/unit-tests.md)
|
||||
- [Other Types of Tests](testing/other.md)
|
||||
- [Useful Crates](testing/useful-crates.md)
|
||||
- [Compiler lints and Clippy](testing/lints.md)
|
||||
- [Exercise: Luhn Algorithm](testing/exercise.md)
|
||||
- [Solution](testing/solution.md)
|
||||
|
||||
# Day 4: Afternoon
|
||||
|
||||
- [Welcome](welcome-day-4-afternoon.md)
|
||||
- [Error Handling](error-handling.md)
|
||||
- [Panics](error-handling/panics.md)
|
||||
- [Catching Stack Unwinding](error-handling/panic-unwind.md)
|
||||
- [Structured Error Handling](error-handling/result.md)
|
||||
- [Propagating Errors with ?](error-handling/try-operator.md)
|
||||
- [Converting Error Types](error-handling/converting-error-types.md)
|
||||
- [Example](error-handling/converting-error-types-example.md)
|
||||
- [Deriving Error Enums](error-handling/deriving-error-enums.md)
|
||||
- [Dynamic Error Types](error-handling/dynamic-errors.md)
|
||||
- [Adding Context to Errors](error-handling/error-contexts.md)
|
||||
- [Testing](testing.md)
|
||||
- [Unit Tests](testing/unit-tests.md)
|
||||
- [Test Modules](testing/test-modules.md)
|
||||
- [Documentation Tests](testing/doc-tests.md)
|
||||
- [Integration Tests](testing/integration-tests.md)
|
||||
- [Useful crates](testing/useful-crates.md)
|
||||
- [Unsafe Rust](unsafe.md)
|
||||
- [Dereferencing Raw Pointers](unsafe/raw-pointers.md)
|
||||
- [Mutable Static Variables](unsafe/mutable-static-variables.md)
|
||||
- [Unions](unsafe/unions.md)
|
||||
- [Calling Unsafe Functions](unsafe/calling-unsafe-functions.md)
|
||||
- [Writing Unsafe Functions](unsafe/writing-unsafe-functions.md)
|
||||
- [Extern Functions](unsafe/extern-functions.md)
|
||||
- [Implementing Unsafe Traits](unsafe/unsafe-traits.md)
|
||||
- [Exercises](exercises/day-3/afternoon.md)
|
||||
- [Safe FFI Wrapper](exercises/day-3/safe-ffi-wrapper.md)
|
||||
|
||||
- [Try operator](error-handling/try.md)
|
||||
- [Try Conversions](error-handling/try-conversions.md)
|
||||
- [Error Trait](error-handling/error.md)
|
||||
- [thiserror and anyhow](error-handling/thiserror-and-anyhow.md)
|
||||
- [Exercise: Rewriting with Result](error-handling/exercise.md)
|
||||
- [Solution](error-handling/solution.md)
|
||||
- [Unsafe Rust](unsafe-rust.md)
|
||||
- [Unsafe](unsafe-rust/unsafe.md)
|
||||
- [Dereferencing Raw Pointers](unsafe-rust/dereferencing.md)
|
||||
- [Mutable Static Variables](unsafe-rust/mutable-static.md)
|
||||
- [Unions](unsafe-rust/unions.md)
|
||||
- [Unsafe Functions](unsafe-rust/unsafe-functions.md)
|
||||
- [Unsafe Traits](unsafe-rust/unsafe-traits.md)
|
||||
- [Exercise: FFI Wrapper](unsafe-rust/exercise.md)
|
||||
- [Solution](unsafe-rust/solution.md)
|
||||
|
||||
# Android
|
||||
|
||||
@ -277,6 +291,7 @@
|
||||
- [Other Projects](bare-metal/microcontrollers/other-projects.md)
|
||||
- [Exercises](exercises/bare-metal/morning.md)
|
||||
- [Compass](exercises/bare-metal/compass.md)
|
||||
- [Solutions](exercises/bare-metal/solutions-morning.md)
|
||||
|
||||
# Bare Metal: Afternoon
|
||||
|
||||
@ -305,6 +320,7 @@
|
||||
- [vmbase](bare-metal/android/vmbase.md)
|
||||
- [Exercises](exercises/bare-metal/afternoon.md)
|
||||
- [RTC Driver](exercises/bare-metal/rtc.md)
|
||||
- [Solutions](exercises/bare-metal/solutions-afternoon.md)
|
||||
|
||||
|
||||
# Concurrency: Morning
|
||||
@ -328,6 +344,7 @@
|
||||
- [Exercises](exercises/concurrency/morning.md)
|
||||
- [Dining Philosophers](exercises/concurrency/dining-philosophers.md)
|
||||
- [Multi-threaded Link Checker](exercises/concurrency/link-checker.md)
|
||||
- [Solutions](exercises/concurrency/solutions-morning.md)
|
||||
|
||||
# Concurrency: Afternoon
|
||||
|
||||
@ -349,6 +366,7 @@
|
||||
- [Exercises](exercises/concurrency/afternoon.md)
|
||||
- [Dining Philosophers](exercises/concurrency/dining-philosophers-async.md)
|
||||
- [Broadcast Chat Application](exercises/concurrency/chat-app.md)
|
||||
- [Solutions](exercises/concurrency/solutions-afternoon.md)
|
||||
|
||||
|
||||
# Final Words
|
||||
@ -359,20 +377,3 @@
|
||||
- [Glossary](glossary.md)
|
||||
- [Other Resources](other-resources.md)
|
||||
- [Credits](credits.md)
|
||||
|
||||
|
||||
# Solutions
|
||||
|
||||
----
|
||||
|
||||
- [Solutions](exercises/solutions.md)
|
||||
- [Day 1 Morning](exercises/day-1/solutions-morning.md)
|
||||
- [Day 1 Afternoon](exercises/day-1/solutions-afternoon.md)
|
||||
- [Day 2 Morning](exercises/day-2/solutions-morning.md)
|
||||
- [Day 2 Afternoon](exercises/day-2/solutions-afternoon.md)
|
||||
- [Day 3 Morning](exercises/day-3/solutions-morning.md)
|
||||
- [Day 3 Afternoon](exercises/day-3/solutions-afternoon.md)
|
||||
- [Bare Metal Rust Morning](exercises/bare-metal/solutions-morning.md)
|
||||
- [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md)
|
||||
- [Concurrency Morning](exercises/concurrency/solutions-morning.md)
|
||||
- [Concurrency Afternoon](exercises/concurrency/solutions-afternoon.md)
|
||||
|
@ -1,3 +1,6 @@
|
||||
---
|
||||
course: none
|
||||
---
|
||||
# Welcome to Rust in Android
|
||||
|
||||
Rust is supported for system software on Android. This means that
|
||||
|
@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
// ANCHOR: main
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
# 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 `==`.
|
@ -1,31 +0,0 @@
|
||||
# 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<T>(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));
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* When using generics, the standard library's `Into<T>` can provide a kind of limited
|
||||
polymorphism on argument types. We will see more details in a later section.
|
||||
|
||||
</details>
|
||||
|
@ -1,42 +0,0 @@
|
||||
# Functions
|
||||
|
||||
A Rust version of the famous [FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz) interview question:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```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));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
</details>
|
@ -1,47 +0,0 @@
|
||||
# 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.
|
||||
|
||||
<details>
|
||||
|
||||
- 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.
|
||||
|
||||
</details>
|
@ -1,20 +0,0 @@
|
||||
# Dangling References
|
||||
|
||||
Rust will statically forbid dangling references:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable,compile_fail
|
||||
fn main() {
|
||||
let ref_x: &i32;
|
||||
{
|
||||
let x: i32 = 10;
|
||||
ref_x = &x;
|
||||
}
|
||||
println!("ref_x: {ref_x}");
|
||||
}
|
||||
```
|
||||
|
||||
* A reference is said to "borrow" the value it refers to.
|
||||
* Rust is tracking the lifetimes of all references to ensure they live long
|
||||
enough.
|
||||
* We will talk more about borrowing when we get to ownership.
|
@ -1,30 +0,0 @@
|
||||
# References
|
||||
|
||||
Like C++, Rust has references:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let mut x: i32 = 10;
|
||||
let ref_x: &mut i32 = &mut x;
|
||||
*ref_x = 20;
|
||||
println!("x: {x}");
|
||||
}
|
||||
```
|
||||
|
||||
Some notes:
|
||||
|
||||
* We must dereference `ref_x` when assigning to it, similar to C and C++ pointers.
|
||||
* Rust will auto-dereference in some cases, in particular when invoking
|
||||
methods (try `ref_x.count_ones()`).
|
||||
* References that are declared as `mut` can be bound to different values over their lifetime.
|
||||
|
||||
<details>
|
||||
|
||||
Key points:
|
||||
|
||||
* Be sure to note the difference between `let mut ref_x: &i32` and `let ref_x:
|
||||
&mut i32`. The first one represents a mutable reference which can be bound to
|
||||
different values, while the second represents a reference to a mutable value.
|
||||
|
||||
</details>
|
@ -1,42 +0,0 @@
|
||||
# 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.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// assert!(is_divisible_by(42, 2));
|
||||
/// ```
|
||||
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.
|
||||
Code snippets can document usage and will be used as unit tests.
|
||||
|
||||
<details>
|
||||
|
||||
* 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).
|
||||
|
||||
</details>
|
@ -1,40 +0,0 @@
|
||||
# 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}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* 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.
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let a = 1;
|
||||
let b = &a;
|
||||
let a = a + 1;
|
||||
println!("{a} {b}");
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
@ -1,20 +0,0 @@
|
||||
# Variables
|
||||
|
||||
Rust provides type safety via static typing. Variable bindings are immutable by
|
||||
default:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let x: i32 = 10;
|
||||
println!("x: {x}");
|
||||
// x = 20;
|
||||
// println!("x: {x}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Due to type inference the `i32` is optional. We will gradually show the types less and less as the course progresses.
|
||||
|
||||
</details>
|
3
src/borrowing.md
Normal file
3
src/borrowing.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Borrowing
|
||||
|
||||
{{%segment outline}}
|
9
src/borrowing/Cargo.toml
Normal file
9
src/borrowing/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "borrowing"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "borrowing"
|
||||
path = "../../third_party/rust-on-exercism/health-statistics.rs"
|
37
src/borrowing/borrowck.md
Normal file
37
src/borrowing/borrowck.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# Borrow Checking
|
||||
|
||||
Rust's _borrow checker_ puts constraints on the ways you can borrow values. For a given value, at any time:
|
||||
|
||||
* You can have one or more shared references to the value, _or_
|
||||
* You can have exactly one exclusive reference to the value.
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable,compile_fail
|
||||
fn main() {
|
||||
let mut a: i32 = 10;
|
||||
let b: &i32 = &a;
|
||||
|
||||
{
|
||||
let c: &mut i32 = &mut a;
|
||||
*c = 20;
|
||||
}
|
||||
|
||||
println!("a: {a}");
|
||||
println!("b: {b}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Note that the requirement is that conflicting references not _exist_ at the same point. It does not matter where the reference is dereferenced.
|
||||
* The above code does not compile because `a` is borrowed as mutable (through `c`) and as immutable (through `b`) at the same time.
|
||||
* Move the `println!` statement for `b` before the scope that introduces `c` to make the code compile.
|
||||
* After that change, the compiler realizes that `b` is only ever used before the new mutable borrow of `a` through `c`. This is a feature of the borrow checker called "non-lexical lifetimes".
|
||||
* The exclusive reference constraint is quite strong. Rust uses it to ensure that data races do not occur. Rust also _relies_ on this constraint to optimize codes. For example, a value behind a shared reference can be safely cached in a register for the lifetime of that reference.
|
||||
* The borrow checker is designed to accommodate many common patterns, such as taking exclusive references to different fields in a struct at the same time. But, there are some situations where it doesn't quite "get it" and this often results in "fighting with the borrow checker."
|
||||
|
||||
</details>
|
26
src/borrowing/exercise.md
Normal file
26
src/borrowing/exercise.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
minutes: 30
|
||||
---
|
||||
|
||||
# Exercise: Health Statistics
|
||||
|
||||
{{#include ../../third_party/rust-on-exercism/health-statistics.md}}
|
||||
|
||||
Copy the code below to <https://play.rust-lang.org/> and fill in the missing
|
||||
methods:
|
||||
|
||||
```rust
|
||||
// TODO: remove this when you're done with your implementation.
|
||||
#![allow(unused_variables, dead_code)]
|
||||
|
||||
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:setup}}
|
||||
|
||||
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:User_visit_doctor}}
|
||||
todo!("Update a user's statistics based on measurements from a visit to the doctor")
|
||||
}
|
||||
}
|
||||
|
||||
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:main}}
|
||||
|
||||
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:tests}}
|
||||
```
|
@ -1,4 +1,17 @@
|
||||
# `Cell` and `RefCell`
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
<!-- NOTES:
|
||||
Introduce the concept, with an example based on Mutex showing an `&self` method doing mutation; reference Cell/RefCell without detail.
|
||||
-->
|
||||
# Interior Mutability
|
||||
|
||||
Rust provides a few safe means of modifying a value given only a shared
|
||||
reference to that value. All of these replace copmile-time checks with runtime
|
||||
checks.
|
||||
|
||||
## `Cell` and `RefCell`
|
||||
|
||||
[`Cell`](https://doc.rust-lang.org/std/cell/struct.Cell.html) and
|
||||
[`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) implement
|
@ -1,7 +1,11 @@
|
||||
# Borrowing
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
Instead of transferring ownership when calling a function, you can let a
|
||||
function _borrow_ the value:
|
||||
# Borrowing a Value
|
||||
|
||||
As we saw before, instead of transferring ownership when calling a function,
|
||||
you can let a function _borrow_ the value:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
@ -25,7 +29,13 @@ fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
This slide is a review of the material on references from day 1, expanding
|
||||
slightly to include function arguments and return values.
|
||||
|
||||
# More to Explore
|
||||
|
||||
Notes on stack returns:
|
||||
|
||||
* Demonstrate that the return from `add` is cheap because the compiler can eliminate the copy operation. Change the above code to print stack addresses and run it on the [Playground] or look at the assembly in [Godbolt](https://rust.godbolt.org/). In the "DEBUG" optimization level, the addresses should change, while they stay the same when changing to the "RELEASE" setting:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
5
src/borrowing/solution.md
Normal file
5
src/borrowing/solution.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Solution
|
||||
|
||||
```rust,editable
|
||||
{{#include ../../third_party/rust-on-exercism/health-statistics.rs:solution}}
|
||||
```
|
3
src/control-flow-basics.md
Normal file
3
src/control-flow-basics.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Control Flow Basics
|
||||
|
||||
{{%segment outline}}
|
9
src/control-flow-basics/Cargo.toml
Normal file
9
src/control-flow-basics/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "control-flow-basics"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "collatz"
|
||||
path = "exercise.rs"
|
58
src/control-flow-basics/blocks-and-scopes.md
Normal file
58
src/control-flow-basics/blocks-and-scopes.md
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# Blocks and Scopes
|
||||
|
||||
## Blocks
|
||||
|
||||
A block in Rust contains a sequence of expressions.
|
||||
Each block has a value and a type,
|
||||
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}");
|
||||
z - y
|
||||
};
|
||||
println!("x: {x}");
|
||||
}
|
||||
```
|
||||
|
||||
If the last expression ends with `;`, then the resulting value and type is `()`.
|
||||
|
||||
## Scopes and Shadowing
|
||||
|
||||
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() {
|
||||
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}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* 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()`.
|
||||
|
||||
</details>
|
59
src/control-flow-basics/break-continue.md
Normal file
59
src/control-flow-basics/break-continue.md
Normal file
@ -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.
|
||||
|
||||
<details>
|
||||
|
||||
* 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).
|
||||
|
||||
</details>
|
||||
|
58
src/control-flow-basics/conditionals.md
Normal file
58
src/control-flow-basics/conditionals.md
Normal file
@ -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);
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
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.
|
||||
|
||||
</details>
|
35
src/control-flow-basics/exercise.md
Normal file
35
src/control-flow-basics/exercise.md
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
minutes: 30
|
||||
---
|
||||
|
||||
# Exercise: Collatz Sequence
|
||||
|
||||
The [Collatz Sequence](https://en.wikipedia.org/wiki/Collatz_conjecture) is
|
||||
defined as follows, for an arbitrary n<sub>1</sub> greater than zero:
|
||||
|
||||
- If _n<sub>i</sub>_ is 1, then the sequence terminates at _n<sub>i</sub>_.
|
||||
- If _n<sub>i</sub>_ is even, then _n<sub>i+1</sub> = n<sub>i</sub> / 2_.
|
||||
- If _n<sub>i</sub>_ is odd, then _n<sub>i+1</sub> = 3 * n<sub>i</sub> + 1_.
|
||||
|
||||
For example, beginning with _n<sub>1</sub>_ = 3:
|
||||
* 3 is odd, so _n<sub>2</sub>_ = 3 * 3 + 1 = 10;
|
||||
* 10 is even, so _n<sub>3</sub>_ = 10 / 2 = 5;
|
||||
* 5 is odd, so _n<sub>4</sub>_ = 3 * 15 + 1 = 16;
|
||||
* 16 is even, so _n<sub>5</sub>_ = 16 / 2 = 8;
|
||||
* 8 is even, so _n<sub>6</sub>_ = 8 / 2 = 4;
|
||||
* 4 is even, so _n<sub>7</sub>_ = 4 / 2 = 2;
|
||||
* 2 is even, so _n<sub>8</sub>_ = 1; and
|
||||
* the sequence terminates.
|
||||
|
||||
Write a function to calculate the length of the collatz sequence for a given
|
||||
initial `n`.
|
||||
|
||||
```rust,should_panic
|
||||
{{#include exercise.rs:collatz_length}}
|
||||
todo!("Implement this")
|
||||
}
|
||||
|
||||
{{#include exercise.rs:main}}
|
||||
todo!("Implement this")
|
||||
}
|
||||
```
|
39
src/control-flow-basics/exercise.rs
Normal file
39
src/control-flow-basics/exercise.rs
Normal file
@ -0,0 +1,39 @@
|
||||
// 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.
|
||||
|
||||
// ANCHOR: solution
|
||||
// ANCHOR: collatz_length
|
||||
/// Determine the length of the collatz sequence beginning at `n`.
|
||||
fn collatz_length(mut n: i32) -> u32 {
|
||||
// ANCHOR_END: collatz_length
|
||||
let mut len = 1;
|
||||
while n > 1 {
|
||||
n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
|
||||
len += 1;
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
// ANCHOR: tests
|
||||
#[test]
|
||||
fn test_collatz_length() {
|
||||
assert_eq!(collatz_length(11), 15);
|
||||
}
|
||||
// ANCHOR_END: tests
|
||||
|
||||
// ANCHOR: main
|
||||
fn main() {
|
||||
// ANCHOR_END: main
|
||||
println!("Length: {}", collatz_length(11));
|
||||
}
|
32
src/control-flow-basics/functions.md
Normal file
32
src/control-flow-basics/functions.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
minutes: 3
|
||||
---
|
||||
|
||||
# Functions
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn gcd(a: u32, b: u32) -> u32 {
|
||||
if b > 0 {
|
||||
gcd(b, a % b)
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("gcd: {}", gcd(143, 52));
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
</details>
|
61
src/control-flow-basics/loops.md
Normal file
61
src/control-flow-basics/loops.md
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
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 languages, executing the loop body as long as the
|
||||
condition is true.
|
||||
|
||||
```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}");
|
||||
if i > 100 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* We will discuss iteration later; for now, just stick to range expressions.
|
||||
* Note that the `for` loop only iterates to `4`. Show the `1..=5` syntax for an inclusive range.
|
||||
|
||||
</details>
|
45
src/control-flow-basics/macros.md
Normal file
45
src/control-flow-basics/macros.md
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
minutes: 2
|
||||
---
|
||||
|
||||
# Macros
|
||||
|
||||
Macros are expanded into Rust code during compilation, and can take a variable
|
||||
number of arguments. They are distinguished by a `!` at the end. The Rust
|
||||
standard library includes an assortment of useful macros.
|
||||
|
||||
* `println!(format, ..)` prints a line to standard output, applying formatting described in [`std::fmt`](https://doc.rust-lang.org/std/fmt/index.html).
|
||||
* `format!(format, ..)` works just like `println!` but returns the result as a string.
|
||||
* `dbg!(expression)` logs the value of the expression and returns it.
|
||||
* `todo!()` marks a bit of code as not-yet-implemented. If executed, it will panic.
|
||||
* `unreachable!()` marks a bit of code as unreachable. If executed, it will panic.
|
||||
|
||||
```rust,editable
|
||||
fn factorial(n: u32) -> u32 {
|
||||
let mut product = 1;
|
||||
for i in 1..=n {
|
||||
product *= dbg!(i);
|
||||
}
|
||||
product
|
||||
}
|
||||
|
||||
fn fizzbuzz(n: u32) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let n = 13;
|
||||
println!("{n}! = {}", factorial(4));
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
The takeaway from this section is that these common conveniences exist, and how
|
||||
to use them. Why they are defined as macros, and what they expand to, is not
|
||||
especially critical.
|
||||
|
||||
The course does not cover defining macros, but a later section will describe
|
||||
use of derive macros.
|
||||
|
||||
</details>
|
5
src/control-flow-basics/solution.md
Normal file
5
src/control-flow-basics/solution.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Solution
|
||||
|
||||
```rust,editable
|
||||
{{#include exercise.rs:solution}}
|
||||
```
|
@ -1,6 +0,0 @@
|
||||
# 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.
|
@ -1,49 +0,0 @@
|
||||
# Blocks
|
||||
|
||||
A block in Rust contains a sequence of expressions.
|
||||
Each block has a value and a type,
|
||||
which are those of the last expression of the block:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn main() {
|
||||
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}");
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn double(x: i32) -> i32 {
|
||||
x + x
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("double: {}", double(7));
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
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`.
|
||||
|
||||
</details>
|
@ -1,29 +0,0 @@
|
||||
# `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:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```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.
|
@ -1,31 +0,0 @@
|
||||
# `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:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```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.
|
||||
|
||||
<details>
|
||||
|
||||
* 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()`.
|
||||
|
||||
</details>
|
@ -1,37 +0,0 @@
|
||||
# `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
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
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.
|
||||
|
||||
</details>
|
@ -1,42 +0,0 @@
|
||||
# `if let` expressions
|
||||
|
||||
The [`if let`
|
||||
expression](https://doc.rust-lang.org/reference/expressions/if-expr.html#if-let-expressions)
|
||||
lets you execute different code depending on whether a value matches a pattern:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let arg = std::env::args().next();
|
||||
if let Some(value) = arg {
|
||||
println!("Program name: {value}");
|
||||
} else {
|
||||
println!("Missing name?");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [pattern matching](../pattern-matching.md) for more details on patterns in
|
||||
Rust.
|
||||
|
||||
<details>
|
||||
|
||||
* Unlike `match`, `if let` does not have to cover all branches. This can make it more concise than `match`.
|
||||
* A common usage is handling `Some` values when working with `Option`.
|
||||
* Unlike `match`, `if let` does not support guard clauses for pattern matching.
|
||||
* Since 1.65, a similar [let-else](https://doc.rust-lang.org/rust-by-example/flow_control/let_else.html) construct allows to do a destructuring assignment, or if it fails, execute a block which is required to abort normal control flow (with `panic`/`return`/`break`/`continue`):
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn main() {
|
||||
println!("{:?}", second_word_to_upper("foo bar"));
|
||||
}
|
||||
|
||||
fn second_word_to_upper(s: &str) -> Option<String> {
|
||||
let mut it = s.split(' ');
|
||||
let (Some(_), Some(item)) = (it.next(), it.next()) else {
|
||||
return None;
|
||||
};
|
||||
Some(item.to_uppercase())
|
||||
}
|
||||
|
||||
</details>
|
@ -1,33 +0,0 @@
|
||||
# `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:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```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!("x: {x}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* 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).
|
||||
|
||||
</details>
|
@ -1,33 +0,0 @@
|
||||
# `match` expressions
|
||||
|
||||
The [`match` keyword](https://doc.rust-lang.org/reference/expressions/match-expr.html)
|
||||
is used to match a value against one or more patterns. In that sense, it works
|
||||
like a series of `if let` expressions:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
match std::env::args().next().as_deref() {
|
||||
Some("cat") => println!("Will do cat things"),
|
||||
Some("ls") => println!("Will ls some files"),
|
||||
Some("mv") => println!("Let's move some files"),
|
||||
Some("rm") => println!("Uh, dangerous!"),
|
||||
None => println!("Hmm, no program name?"),
|
||||
_ => println!("Unknown program name!"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Like `if let`, 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 `()`.
|
||||
|
||||
See [pattern matching](../pattern-matching.md) for more details on patterns in
|
||||
Rust.
|
||||
|
||||
<details>
|
||||
|
||||
* Save the match expression to a variable and print it out.
|
||||
* Remove `.as_deref()` and explain the error.
|
||||
* `std::env::args().next()` returns an `Option<String>`, but we cannot match against `String`.
|
||||
* `as_deref()` transforms an `Option<T>` to `Option<&T::Target>`. In our case, this turns `Option<String>` into `Option<&str>`.
|
||||
* We can now use pattern matching to match against the `&str` inside `Option`.
|
||||
</details>
|
@ -1,8 +0,0 @@
|
||||
# Novel Control Flow
|
||||
|
||||
Rust has a few control flow constructs which differ from other languages. They
|
||||
are used for pattern matching:
|
||||
|
||||
- `if let` expressions
|
||||
- `while let` expressions
|
||||
- `match` expressions
|
@ -1,20 +0,0 @@
|
||||
# `while` loops
|
||||
|
||||
The [`while` keyword](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-loops)
|
||||
works very similar to other languages:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let mut x = 10;
|
||||
while x != 1 {
|
||||
x = if x % 2 == 0 {
|
||||
x / 2
|
||||
} else {
|
||||
3 * x + 1
|
||||
};
|
||||
}
|
||||
println!("x: {x}");
|
||||
}
|
||||
```
|
||||
|
@ -1,30 +0,0 @@
|
||||
# `while let` loops
|
||||
|
||||
Like with `if let`, there is a [`while let`](https://doc.rust-lang.org/reference/expressions/loop-expr.html#predicate-pattern-loops)
|
||||
variant which repeatedly tests a value against a pattern:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let v = vec![10, 20, 30];
|
||||
let mut iter = v.into_iter();
|
||||
|
||||
while let Some(x) = iter.next() {
|
||||
println!("x: {x}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here the iterator returned by `v.into_iter()` will return a `Option<i32>` on every
|
||||
call to `next()`. It returns `Some(x)` until it is done, after which it will
|
||||
return `None`. The `while let` lets us keep iterating through all items.
|
||||
|
||||
See [pattern matching](../pattern-matching.md) for more details on patterns in
|
||||
Rust.
|
||||
|
||||
<details>
|
||||
|
||||
* Point out that the `while let` loop will keep going as long as the value matches the pattern.
|
||||
* You could rewrite the `while let` loop as an infinite loop with an if statement that breaks when there is no value to unwrap for `iter.next()`. The `while let` provides syntactic sugar for the above scenario.
|
||||
|
||||
</details>
|
42
src/enums.md
42
src/enums.md
@ -1,42 +0,0 @@
|
||||
# Enums
|
||||
|
||||
The `enum` keyword allows the creation of a type which has a few
|
||||
different variants:
|
||||
|
||||
```rust,editable
|
||||
fn generate_random_number() -> i32 {
|
||||
// Implementation based on https://xkcd.com/221/
|
||||
4 // Chosen by fair dice roll. Guaranteed to be random.
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CoinFlip {
|
||||
Heads,
|
||||
Tails,
|
||||
}
|
||||
|
||||
fn flip_coin() -> CoinFlip {
|
||||
let random_number = generate_random_number();
|
||||
if random_number % 2 == 0 {
|
||||
return CoinFlip::Heads;
|
||||
} else {
|
||||
return CoinFlip::Tails;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("You got: {:?}", flip_coin());
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
Key Points:
|
||||
|
||||
* Enumerations allow you to collect a set of values under one type
|
||||
* This page offers an enum type `CoinFlip` with two variants `Heads` and `Tails`. You might note the namespace when using variants.
|
||||
* This might be a good time to compare Structs and Enums:
|
||||
* In both, you can have a simple version without fields (unit struct) or one with different types of fields (variant payloads).
|
||||
* In both, associated functions are defined within an `impl` block.
|
||||
* You could even implement the different variants of an enum with separate structs but then they wouldn’t be the same type as they would if they were all defined in an enum.
|
||||
</details>
|
@ -1,22 +0,0 @@
|
||||
# Variant Payloads
|
||||
|
||||
You can define richer enums where the variants carry data. You can then use the
|
||||
`match` statement to extract the data from each variant:
|
||||
|
||||
```rust,editable
|
||||
{{#include ../../third_party/rust-by-example/webevent.rs}}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* The values in the enum variants can only be accessed after being pattern matched. The pattern binds references to the fields in the "match arm" after the `=>`.
|
||||
* The expression is matched against the patterns from top to bottom. There is no fall-through like in C or C++.
|
||||
* The match expression has a value. The value is the last expression in the match arm which was executed.
|
||||
* Starting from the top we look for what pattern matches the value then run the code following the arrow. Once we find a match, we stop.
|
||||
* Demonstrate what happens when the search is inexhaustive. Note the advantage the Rust compiler provides by confirming when all cases are handled.
|
||||
* `match` inspects a hidden discriminant field in the `enum`.
|
||||
* It is possible to retrieve the discriminant by calling `std::mem::discriminant()`
|
||||
* This is useful, for example, if implementing `PartialEq` for structs where comparing field values doesn't affect equality.
|
||||
* `WebEvent::Click { ... }` is not exactly the same as `WebEvent::Click(Click)` with a top level `struct Click { ... }`. The inlined version cannot implement traits, for example.
|
||||
|
||||
</details>
|
@ -1,7 +1,3 @@
|
||||
# Error Handling
|
||||
|
||||
Error handling in Rust is done using explicit control flow:
|
||||
|
||||
* Functions that can have errors list this in their return type,
|
||||
* There are no exceptions.
|
||||
|
||||
{{%segment outline}}
|
||||
|
13
src/error-handling/Cargo.toml
Normal file
13
src/error-handling/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "error-handling"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
thiserror = "*"
|
||||
anyhow = "*"
|
||||
|
||||
[[bin]]
|
||||
name = "parser"
|
||||
path = "exercise.rs"
|
@ -1,19 +0,0 @@
|
||||
# Converting Error Types
|
||||
|
||||
The effective expansion of `?` is a little more complicated than previously indicated:
|
||||
|
||||
```rust,ignore
|
||||
expression?
|
||||
```
|
||||
|
||||
works the same as
|
||||
|
||||
```rust,ignore
|
||||
match expression {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(From::from(err)),
|
||||
}
|
||||
```
|
||||
|
||||
The `From::from` call here means we attempt to convert the error type to the
|
||||
type returned by the function.
|
@ -1,45 +0,0 @@
|
||||
# Deriving Error Enums
|
||||
|
||||
The [thiserror](https://docs.rs/thiserror/) crate is a popular way to create an
|
||||
error enum like we did on the previous page:
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use std::{fs, io};
|
||||
use std::io::Read;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum ReadUsernameError {
|
||||
#[error("Could not read: {0}")]
|
||||
IoError(#[from] io::Error),
|
||||
#[error("Found no username in {0}")]
|
||||
EmptyUsername(String),
|
||||
}
|
||||
|
||||
fn read_username(path: &str) -> Result<String, ReadUsernameError> {
|
||||
let mut username = String::new();
|
||||
fs::File::open(path)?.read_to_string(&mut username)?;
|
||||
if username.is_empty() {
|
||||
return Err(ReadUsernameError::EmptyUsername(String::from(path)));
|
||||
}
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
//fs::write("config.dat", "").unwrap();
|
||||
match read_username("config.dat") {
|
||||
Ok(username) => println!("Username: {username}"),
|
||||
Err(err) => println!("Error: {err}"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
`thiserror`'s derive macro automatically implements `std::error::Error`, and optionally `Display`
|
||||
(if the `#[error(...)]` attributes are provided) and `From` (if the `#[from]` attribute is added).
|
||||
It also works for structs.
|
||||
|
||||
It doesn't affect your public API, which makes it good for libraries.
|
||||
|
||||
</details>
|
@ -1,41 +0,0 @@
|
||||
# Dynamic Error Types
|
||||
|
||||
Sometimes we want to allow any type of error to be returned without writing our own enum covering
|
||||
all the different possibilities. `std::error::Error` makes this easy.
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use thiserror::Error;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Error, PartialEq)]
|
||||
#[error("Found no username in {0}")]
|
||||
struct EmptyUsernameError(String);
|
||||
|
||||
fn read_username(path: &str) -> Result<String, Box<dyn Error>> {
|
||||
let mut username = String::new();
|
||||
fs::File::open(path)?.read_to_string(&mut username)?;
|
||||
if username.is_empty() {
|
||||
return Err(EmptyUsernameError(String::from(path)).into());
|
||||
}
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
//fs::write("config.dat", "").unwrap();
|
||||
match read_username("config.dat") {
|
||||
Ok(username) => println!("Username: {username}"),
|
||||
Err(err) => println!("Error: {err}"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
This saves on code, but gives up the ability to cleanly handle different error cases differently in
|
||||
the program. As such it's generally not a good idea to use `Box<dyn Error>` in the public API of a
|
||||
library, but it can be a good option in a program where you just want to display the error message
|
||||
somewhere.
|
||||
|
||||
</details>
|
42
src/error-handling/error.md
Normal file
42
src/error-handling/error.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
minutes: 5
|
||||
---
|
||||
|
||||
# Dynamic Error Types
|
||||
|
||||
Sometimes we want to allow any type of error to be returned without writing our
|
||||
own enum covering all the different possibilities. The `std::error::Error`
|
||||
trait makes it easy to create a trait object that can contain any error.
|
||||
|
||||
```rust,editable
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
|
||||
fn read_count(path: &str) -> Result<i32, Box<dyn Error>> {
|
||||
let mut count_str = String::new();
|
||||
fs::File::open(path)?.read_to_string(&mut count_str)?;
|
||||
let count: i32 = count_str.parse()?;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fs::write("count.dat", "1i3").unwrap();
|
||||
match read_count("count.dat") {
|
||||
Ok(count) => println!("Count: {count}"),
|
||||
Err(err) => println!("Error: {err}"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
The `read_count` function can return `std::io::Error` (from file operations) or
|
||||
`std::num::ParseIntError` (from `String::parse`).
|
||||
|
||||
Boxing errors saves on code, but gives up the ability to cleanly handle different error cases differently in
|
||||
the program. As such it's generally not a good idea to use `Box<dyn Error>` in the public API of a
|
||||
library, but it can be a good option in a program where you just want to display the error message
|
||||
somewhere.
|
||||
|
||||
</details>
|
20
src/error-handling/exercise.md
Normal file
20
src/error-handling/exercise.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
minutes: 20
|
||||
---
|
||||
|
||||
# Exercise: Rewriting with Result
|
||||
|
||||
The following implements a very simple parser for an expression language.
|
||||
However, it handles errors by panicking. Rewrite it to instead use idiomatic
|
||||
error handling and propagate errors to a return from `main`. Feel free to use
|
||||
`thiserror` and `anyhow`.
|
||||
|
||||
HINT: start by fixing error handling in the `parse` function. Once that is
|
||||
working correctly, update `Tokenizer` to implement
|
||||
`Iterator<Item=Result<Token, TokenizerError>>` and handle that in the parser.
|
||||
|
||||
```rust,editable
|
||||
{{#include exercise.rs:types}}
|
||||
|
||||
{{#include exercise.rs:panics}}
|
||||
```
|
211
src/error-handling/exercise.rs
Normal file
211
src/error-handling/exercise.rs
Normal file
@ -0,0 +1,211 @@
|
||||
// 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.
|
||||
|
||||
// ANCHOR: solution
|
||||
use thiserror::Error;
|
||||
// ANCHOR: types
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
|
||||
/// An arithmetic operator.
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
enum Op {
|
||||
Add,
|
||||
Sub,
|
||||
}
|
||||
|
||||
/// A token in the expression language.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Token {
|
||||
Number(String),
|
||||
Identifier(String),
|
||||
Operator(Op),
|
||||
}
|
||||
|
||||
/// An expression in the expression language.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Expression {
|
||||
/// A reference to a variable.
|
||||
Var(String),
|
||||
/// A literal number.
|
||||
Number(u32),
|
||||
/// A binary operation.
|
||||
Operation(Box<Expression>, Op, Box<Expression>),
|
||||
}
|
||||
// ANCHOR_END: types
|
||||
|
||||
fn tokenize(input: &str) -> Tokenizer {
|
||||
return Tokenizer(input.chars().peekable());
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum TokenizerError {
|
||||
#[error("Unexpected character '{0}' in input")]
|
||||
UnexpectedCharacter(char),
|
||||
}
|
||||
|
||||
struct Tokenizer<'a>(Peekable<Chars<'a>>);
|
||||
|
||||
impl<'a> Iterator for Tokenizer<'a> {
|
||||
type Item = Result<Token, TokenizerError>;
|
||||
|
||||
fn next(&mut self) -> Option<Result<Token, TokenizerError>> {
|
||||
let Some(c) = self.0.next() else {
|
||||
return None;
|
||||
};
|
||||
match c {
|
||||
'0'..='9' => {
|
||||
let mut num = String::from(c);
|
||||
while let Some(c @ '0'..='9') = self.0.peek() {
|
||||
num.push(*c);
|
||||
self.0.next();
|
||||
}
|
||||
Some(Ok(Token::Number(num)))
|
||||
}
|
||||
'a'..='z' => {
|
||||
let mut ident = String::from(c);
|
||||
while let Some(c @ 'a'..='z' | c @ '_' | c @ '0'..='9') = self.0.peek() {
|
||||
ident.push(*c);
|
||||
self.0.next();
|
||||
}
|
||||
Some(Ok(Token::Identifier(ident)))
|
||||
}
|
||||
'+' => Some(Ok(Token::Operator(Op::Add))),
|
||||
'-' => Some(Ok(Token::Operator(Op::Sub))),
|
||||
_ => Some(Err(TokenizerError::UnexpectedCharacter(c))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum ParserError {
|
||||
#[error("Tokenizer error: {0}")]
|
||||
TokenizerError(#[from] TokenizerError),
|
||||
#[error("Unexpected end of input")]
|
||||
UnexpectedEOF,
|
||||
#[error("Unexpected token {0:?}")]
|
||||
UnexpectedToken(Token),
|
||||
#[error("Invalid number")]
|
||||
InvalidNumber(#[from] std::num::ParseIntError),
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Result<Expression, ParserError> {
|
||||
let mut tokens = tokenize(input);
|
||||
|
||||
fn parse_expr<'a>(tokens: &mut Tokenizer<'a>) -> Result<Expression, ParserError> {
|
||||
let Some(tok) = tokens.next().transpose()? else {
|
||||
return Err(ParserError::UnexpectedEOF);
|
||||
};
|
||||
let expr = match tok {
|
||||
Token::Number(num) => {
|
||||
let v = num.parse()?;
|
||||
Expression::Number(v)
|
||||
}
|
||||
Token::Identifier(ident) => Expression::Var(ident),
|
||||
Token::Operator(_) => return Err(ParserError::UnexpectedToken(tok)),
|
||||
};
|
||||
// Look ahead to parse a binary operation if present.
|
||||
Ok(match tokens.next() {
|
||||
None => expr,
|
||||
Some(Ok(Token::Operator(op))) => {
|
||||
Expression::Operation(Box::new(expr), op, Box::new(parse_expr(tokens)?))
|
||||
}
|
||||
Some(Err(e)) => return Err(e.into()),
|
||||
Some(Ok(tok)) => return Err(ParserError::UnexpectedToken(tok)),
|
||||
})
|
||||
}
|
||||
|
||||
parse_expr(&mut tokens)
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let expr = parse("10+foo+20-30")?;
|
||||
println!("{expr:?}");
|
||||
Ok(())
|
||||
}
|
||||
// ANCHOR_END: solution
|
||||
|
||||
/*
|
||||
// ANCHOR: panics
|
||||
fn tokenize(input: &str) -> Tokenizer {
|
||||
return Tokenizer(input.chars().peekable());
|
||||
}
|
||||
|
||||
struct Tokenizer<'a>(Peekable<Chars<'a>>);
|
||||
|
||||
impl<'a> Iterator for Tokenizer<'a> {
|
||||
type Item = Token;
|
||||
|
||||
fn next(&mut self) -> Option<Token> {
|
||||
let Some(c) = self.0.next() else {
|
||||
return None;
|
||||
};
|
||||
match c {
|
||||
'0'..='9' => {
|
||||
let mut num = String::from(c);
|
||||
while let Some(c @ '0'..='9') = self.0.peek() {
|
||||
num.push(*c);
|
||||
self.0.next();
|
||||
}
|
||||
Some(Token::Number(num))
|
||||
}
|
||||
'a'..='z' => {
|
||||
let mut ident = String::from(c);
|
||||
while let Some(c @ 'a'..='z' | c @ '_' | c @ '0'..='9') = self.0.peek() {
|
||||
ident.push(*c);
|
||||
self.0.next();
|
||||
}
|
||||
Some(Token::Identifier(ident))
|
||||
}
|
||||
'+' => Some(Token::Operator(Op::Add)),
|
||||
'-' => Some(Token::Operator(Op::Sub)),
|
||||
_ => panic!("Unexpected character {c}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Expression {
|
||||
let mut tokens = tokenize(input);
|
||||
|
||||
fn parse_expr<'a>(tokens: &mut Tokenizer<'a>) -> Expression {
|
||||
let Some(tok) = tokens.next() else {
|
||||
panic!("Unexpected end of input");
|
||||
};
|
||||
let expr = match tok {
|
||||
Token::Number(num) => {
|
||||
let v = num.parse().expect("Invalid 32-bit integer'");
|
||||
Expression::Number(v)
|
||||
}
|
||||
Token::Identifier(ident) => Expression::Var(ident),
|
||||
Token::Operator(_) => panic!("Unexpected token {tok:?}"),
|
||||
};
|
||||
// Look ahead to parse a binary operation if present.
|
||||
match tokens.next() {
|
||||
None => expr,
|
||||
Some(Token::Operator(op)) => {
|
||||
Expression::Operation(Box::new(expr), op, Box::new(parse_expr(tokens)))
|
||||
}
|
||||
Some(tok) => panic!("Unexpected token {tok:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
parse_expr(&mut tokens)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let expr = parse("10+foo+20-30");
|
||||
println!("{expr:?}");
|
||||
}
|
||||
// ANCHOR_END: panics
|
||||
*/
|
@ -1,23 +0,0 @@
|
||||
# Catching the Stack Unwinding
|
||||
|
||||
By default, a panic will cause the stack to unwind. The unwinding can be caught:
|
||||
|
||||
```rust,editable
|
||||
use std::panic;
|
||||
|
||||
fn main() {
|
||||
let result = panic::catch_unwind(|| {
|
||||
"No problem here!"
|
||||
});
|
||||
println!("{result:?}");
|
||||
|
||||
let result = panic::catch_unwind(|| {
|
||||
panic!("oh no!");
|
||||
});
|
||||
println!("{result:?}");
|
||||
}
|
||||
```
|
||||
|
||||
- This can be useful in servers which should keep running even if a single
|
||||
request crashes.
|
||||
- This does not work if `panic = 'abort'` is set in your `Cargo.toml`.
|
@ -1,5 +1,11 @@
|
||||
---
|
||||
minutes: 3
|
||||
---
|
||||
|
||||
# Panics
|
||||
|
||||
Rust handles fatal errors with a "panic".
|
||||
|
||||
Rust will trigger a panic if a fatal error happens at runtime:
|
||||
|
||||
```rust,editable,should_panic
|
||||
@ -11,4 +17,35 @@ fn main() {
|
||||
|
||||
* Panics are for unrecoverable and unexpected errors.
|
||||
* Panics are symptoms of bugs in the program.
|
||||
* Runtime failures like failed bounds checks can panic
|
||||
* Assertions (such as `assert!`) panic on failure
|
||||
* Purpose-specific panics can use the `panic!` macro.
|
||||
* A panic will "unwind" the stack, dropping values just as if the functions had returned.
|
||||
* Use non-panicking APIs (such as `Vec::get`) if crashing is not acceptable.
|
||||
|
||||
<details>
|
||||
|
||||
By default, a panic will cause the stack to unwind. The unwinding can be caught:
|
||||
|
||||
```rust,editable
|
||||
use std::panic;
|
||||
|
||||
fn main() {
|
||||
let result = panic::catch_unwind(|| {
|
||||
"No problem here!"
|
||||
});
|
||||
println!("{result:?}");
|
||||
|
||||
let result = panic::catch_unwind(|| {
|
||||
panic!("oh no!");
|
||||
});
|
||||
println!("{result:?}");
|
||||
}
|
||||
```
|
||||
|
||||
- Catching is unusual; do not attempt to implement exceptions with `catch_unwind`!
|
||||
- This can be useful in servers which should keep running even if a single
|
||||
request crashes.
|
||||
- This does not work if `panic = 'abort'` is set in your `Cargo.toml`.
|
||||
|
||||
</details>
|
||||
|
@ -1,33 +0,0 @@
|
||||
# Structured Error Handling with `Result`
|
||||
|
||||
We have already seen the `Result` enum. This is used pervasively when errors are
|
||||
expected as part of normal operation:
|
||||
|
||||
```rust,editable
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
|
||||
fn main() {
|
||||
let file = fs::File::open("diary.txt");
|
||||
match file {
|
||||
Ok(mut file) => {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents);
|
||||
println!("Dear diary: {contents}");
|
||||
},
|
||||
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.
|
||||
|
||||
</details>
|
6
src/error-handling/solution.md
Normal file
6
src/error-handling/solution.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Solution
|
||||
|
||||
<!-- compile_fail because `mdbook test` does not allow use of `thiserror` -->
|
||||
```rust,editable,compile_fail
|
||||
{{#include exercise.rs:solution}}
|
||||
```
|
@ -1,14 +1,23 @@
|
||||
# Adding Context to Errors
|
||||
---
|
||||
minutes: 5
|
||||
---
|
||||
|
||||
The widely used [anyhow](https://docs.rs/anyhow/) crate can help you add
|
||||
contextual information to your errors and allows you to have fewer
|
||||
custom error types:
|
||||
# `thiserror` and `anyhow`
|
||||
|
||||
The [`thiserror`](https://docs.rs/thiserror/) and [`anyhow`](https://docs.rs/anyhow/)
|
||||
crates are widley used to simplify error handling. `thiserror` helps
|
||||
create custom error types that implement `From<T>`. `anyhow` helps with error
|
||||
handling in functions, including adding contextual information to your errors.
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use std::{fs, io};
|
||||
use std::io::Read;
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
#[derive(Clone, Debug, Eq, Error, PartialEq)]
|
||||
#[error("Found no username in {0}")]
|
||||
struct EmptyUsernameError(String);
|
||||
|
||||
fn read_username(path: &str) -> Result<String> {
|
||||
let mut username = String::with_capacity(100);
|
||||
fs::File::open(path)
|
||||
@ -16,7 +25,7 @@ fn read_username(path: &str) -> Result<String> {
|
||||
.read_to_string(&mut username)
|
||||
.context("Failed to read")?;
|
||||
if username.is_empty() {
|
||||
bail!("Found no username in {path}");
|
||||
bail!(EmptyUsernameError(path));
|
||||
}
|
||||
Ok(username)
|
||||
}
|
||||
@ -32,6 +41,8 @@ fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
* The `Error` derive macro is provided by `thiserror`, and has lots of useful
|
||||
attributes like `#[error]` to help define a useful error type.
|
||||
* `anyhow::Result<V>` is a type alias for `Result<V, anyhow::Error>`.
|
||||
* `anyhow::Error` is essentially a wrapper around `Box<dyn Error>`. As such it's again generally not
|
||||
a good choice for the public API of a library, but is widely used in applications.
|
@ -1,4 +1,29 @@
|
||||
# Converting Error Types
|
||||
---
|
||||
minutes: 5
|
||||
---
|
||||
|
||||
# Try Conversions
|
||||
|
||||
The effective expansion of `?` is a little more complicated than previously indicated:
|
||||
|
||||
```rust,ignore
|
||||
expression?
|
||||
```
|
||||
|
||||
works the same as
|
||||
|
||||
```rust,ignore
|
||||
match expression {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Err(From::from(err)),
|
||||
}
|
||||
```
|
||||
|
||||
The `From::from` call here means we attempt to convert the error type to the
|
||||
type returned by the function. This makes it easy to encapsulate errors into
|
||||
higher-level errors.
|
||||
|
||||
## Example
|
||||
|
||||
```rust,editable
|
||||
use std::error::Error;
|
||||
@ -47,10 +72,15 @@ fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
Key points:
|
||||
The return type of the function has to be compatible with the nested functions it calls. For instance,
|
||||
a function returning a `Result<T, Err>` can only apply the `?` operator on a function returning a
|
||||
`Result<AnyT, Err>`. It cannot apply the `?` operator on a function returning an `Option<AnyT>` or `Result<T, OtherErr>`
|
||||
unless `OtherErr` implements `From<Err>`. Reciprocally, a function returning an `Option<T>` can only apply the `?` operator
|
||||
on a function returning an `Option<AnyT>`.
|
||||
|
||||
You can convert incompatible types into one another with the different `Option` and `Result` methods
|
||||
such as `Option::ok_or`, `Result::ok`, `Result::err`.
|
||||
|
||||
* The `username` variable can be either `Ok(string)` or `Err(error)`.
|
||||
* Use the `fs::write` call to test out the different scenarios: no file, empty file, file with username.
|
||||
|
||||
It is good practice for all error types that don't need to be `no_std` to implement `std::error::Error`, which requires `Debug` and `Display`. The `Error` crate for `core` is only available in [nightly](https://github.com/rust-lang/rust/issues/103765), so not fully `no_std` compatible yet.
|
||||
|
||||
@ -58,4 +88,6 @@ It's generally helpful for them to implement `Clone` and `Eq` too where possible
|
||||
life easier for tests and consumers of your library. In this case we can't easily do so, because
|
||||
`io::Error` doesn't implement them.
|
||||
|
||||
A common alternative to a `From` implementation is `Result::map_err`, especially when the conversion only happens in one place.
|
||||
|
||||
</details>
|
@ -1,7 +1,13 @@
|
||||
# Propagating Errors with `?`
|
||||
---
|
||||
minutes: 5
|
||||
---
|
||||
|
||||
The try-operator `?` is used to return errors to the caller. It lets you turn
|
||||
the common
|
||||
# Try Operator
|
||||
|
||||
Runtime errors like connection-refused or file-not-found are handled with the
|
||||
`Result` type, but matching this type on every call can be cumbersome. The
|
||||
try-operator `?` is used to return errors to the caller. It lets you turn the
|
||||
common
|
||||
|
||||
```rust,ignore
|
||||
match some_expression {
|
||||
@ -45,16 +51,12 @@ fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
Simplify the `read_username` function to use `?`.
|
||||
|
||||
Key points:
|
||||
|
||||
* The `username` variable can be either `Ok(string)` or `Err(error)`.
|
||||
* Use the `fs::write` call to test out the different scenarios: no file, empty file, file with username.
|
||||
* The return type of the function has to be compatible with the nested functions it calls. For instance,
|
||||
a function returning a `Result<T, Err>` can only apply the `?` operator on a function returning a
|
||||
`Result<AnyT, Err>`. It cannot apply the `?` operator on a function returning an `Option<AnyT>` or `Result<T, OtherErr>`
|
||||
unless `OtherErr` implements `From<Err>`. Reciprocally, a function returning an `Option<T>` can only apply the `?` operator
|
||||
on a function returning an `Option<AnyT>`.
|
||||
* You can convert incompatible types into one another with the different `Option` and `Result` methods
|
||||
such as `Option::ok_or`, `Result::ok`, `Result::err`.
|
||||
* Note that `main` can return a `Result<(), E>` as long as it implements `std::process:Termination`. In practice, this means that `E` implements `Debug`. The executable will print the `Err` variant and return a nonzero exit status on error.
|
||||
|
||||
</details>
|
@ -4,38 +4,6 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "for-loops"
|
||||
path = "day-1/for-loops.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "luhn"
|
||||
path = "day-1/luhn.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "book-library"
|
||||
path = "day-2/book-library.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "health-statistics"
|
||||
path = "../../third_party/rust-on-exercism/health-statistics.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "strings-iterators"
|
||||
path = "day-2/strings-iterators.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "simple-gui"
|
||||
path = "day-3/simple-gui.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "points-polygons"
|
||||
path = "day-3/points-polygons.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "safe-ffi-wrapper"
|
||||
path = "day-3/safe-ffi-wrapper.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "dining-philosophers"
|
||||
path = "concurrency/dining-philosophers.rs"
|
||||
|
@ -41,8 +41,8 @@ impl Philosopher {
|
||||
fn eat(&self) {
|
||||
// ANCHOR_END: Philosopher-eat
|
||||
println!("{} is trying to eat", &self.name);
|
||||
let left = self.left_fork.lock().unwrap();
|
||||
let right = self.right_fork.lock().unwrap();
|
||||
let _left = self.left_fork.lock().unwrap();
|
||||
let _right = self.right_fork.lock().unwrap();
|
||||
|
||||
// ANCHOR: Philosopher-eat-end
|
||||
println!("{} is eating...", &self.name);
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
// ANCHOR: solution
|
||||
use std::{sync::Arc, sync::Mutex, sync::mpsc, thread};
|
||||
use std::{sync::mpsc, sync::Arc, sync::Mutex, thread};
|
||||
|
||||
// ANCHOR: setup
|
||||
use reqwest::{blocking::Client, Url};
|
||||
@ -138,7 +138,10 @@ fn control_crawl(
|
||||
result_receiver: mpsc::Receiver<CrawlResult>,
|
||||
) -> Vec<Url> {
|
||||
let mut crawl_state = CrawlState::new(&start_url);
|
||||
let start_command = CrawlCommand { url: start_url, extract_links: true };
|
||||
let start_command = CrawlCommand {
|
||||
url: start_url,
|
||||
extract_links: true,
|
||||
};
|
||||
command_sender.send(start_command).unwrap();
|
||||
let mut pending_urls = 1;
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
# Day 1: Afternoon Exercises
|
||||
|
||||
We will look at two things:
|
||||
|
||||
* The Luhn algorithm,
|
||||
|
||||
* An exercise on pattern matching.
|
||||
|
||||
<details>
|
||||
|
||||
After looking at the exercises, you can look at the [solutions] provided.
|
||||
|
||||
[solutions]: solutions-afternoon.md
|
||||
|
||||
</details>
|
@ -1,94 +0,0 @@
|
||||
# Arrays and `for` Loops
|
||||
|
||||
We saw that an array can be declared like this:
|
||||
|
||||
```rust
|
||||
let array = [10, 20, 30];
|
||||
```
|
||||
|
||||
You can print such an array by asking for its debug representation with `{:?}`:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let array = [10, 20, 30];
|
||||
println!("array: {array:?}");
|
||||
}
|
||||
```
|
||||
|
||||
Rust lets you iterate over things like arrays and ranges using the `for`
|
||||
keyword:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let array = [10, 20, 30];
|
||||
print!("Iterating over array:");
|
||||
for n in &array {
|
||||
print!(" {n}");
|
||||
}
|
||||
println!();
|
||||
|
||||
print!("Iterating over range:");
|
||||
for i in 0..3 {
|
||||
print!(" {}", array[i]);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
```
|
||||
|
||||
Use the above to write a function `pretty_print` which pretty-print a matrix and
|
||||
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 for-loops.rs:transpose}}
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
{{#include for-loops.rs:pretty_print}}
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
{{#include for-loops.rs:main}}
|
||||
```
|
||||
|
||||
## Bonus Question
|
||||
|
||||
Could you use `&[i32]` slices instead of hard-coded 3 × 3 matrices for your
|
||||
argument and return types? Something like `&[&[i32]]` for a two-dimensional
|
||||
slice-of-slices. Why or why not?
|
||||
|
||||
|
||||
See the [`ndarray` crate](https://docs.rs/ndarray/) for a production quality
|
||||
implementation.
|
||||
|
||||
<details>
|
||||
|
||||
The solution and the answer to the bonus section are available in the
|
||||
[Solution](solutions-morning.md#arrays-and-for-loops) section.
|
||||
|
||||
The use of the reference `&array` within `for n in &array` is a subtle
|
||||
preview of issues of ownership that will come later in the afternoon.
|
||||
|
||||
Without the `&`...
|
||||
* The loop would have been one that consumes the array. This is a
|
||||
change [introduced in the 2021
|
||||
Edition](https://doc.rust-lang.org/edition-guide/rust-2021/IntoIterator-for-arrays.html).
|
||||
* An implicit array copy would have occurred. Since `i32` is a copy type, then
|
||||
`[i32; 3]` is also a copy type.
|
||||
|
||||
</details>
|
@ -1,46 +0,0 @@
|
||||
# Implicit Conversions
|
||||
|
||||
Rust will not automatically apply _implicit conversions_ between types ([unlike
|
||||
C++][3]). You can see this in a program like this:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable,compile_fail
|
||||
fn multiply(x: i16, y: i16) -> i16 {
|
||||
x * y
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x: i8 = 15;
|
||||
let y: i16 = 1000;
|
||||
|
||||
println!("{x} * {y} = {}", multiply(x, y));
|
||||
}
|
||||
```
|
||||
|
||||
The Rust integer types all implement the [`From<T>`][1] and [`Into<T>`][2]
|
||||
traits to let us convert between them. The `From<T>` trait has a single `from()`
|
||||
method and similarly, the `Into<T>` trait has a single `into()` method.
|
||||
Implementing these traits is how a type expresses that it can be converted into
|
||||
another type.
|
||||
|
||||
The standard library has an implementation of `From<i8> for i16`, which means
|
||||
that we can convert a variable `x` of type `i8` to an `i16` by calling
|
||||
`i16::from(x)`. Or, simpler, with `x.into()`, because `From<i8> for i16`
|
||||
implementation automatically create an implementation of `Into<i16> for i8`.
|
||||
|
||||
The same applies for your own `From` implementations for your own types, so it is
|
||||
sufficient to only implement `From` to get a respective `Into` implementation automatically.
|
||||
|
||||
1. Execute the above program and look at the compiler error.
|
||||
|
||||
2. Update the code above to use `into()` to do the conversion.
|
||||
|
||||
3. Change the types of `x` and `y` to other things (such as `f32`, `bool`,
|
||||
`i128`) to see which types you can convert to which other types. Try
|
||||
converting small types to big types and the other way around. Check the
|
||||
[standard library documentation][1] to see if `From<T>` is implemented for
|
||||
the pairs you check.
|
||||
|
||||
[1]: https://doc.rust-lang.org/std/convert/trait.From.html
|
||||
[2]: https://doc.rust-lang.org/std/convert/trait.Into.html
|
||||
[3]: https://en.cppreference.com/w/cpp/language/implicit_conversion
|
@ -1,95 +0,0 @@
|
||||
// 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: luhn
|
||||
pub fn luhn(cc_number: &str) -> bool {
|
||||
// ANCHOR_END: luhn
|
||||
let mut sum = 0;
|
||||
let mut double = false;
|
||||
let mut digit_seen = 0;
|
||||
|
||||
for c in cc_number.chars().filter(|&f| f != ' ').rev() {
|
||||
if let Some(digit) = c.to_digit(10) {
|
||||
if double {
|
||||
let double_digit = digit * 2;
|
||||
sum += if double_digit > 9 {
|
||||
double_digit - 9
|
||||
} else {
|
||||
double_digit
|
||||
};
|
||||
} else {
|
||||
sum += digit;
|
||||
}
|
||||
double = !double;
|
||||
digit_seen += 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if digit_seen < 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
sum % 10 == 0
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cc_number = "1234 5678 1234 5670";
|
||||
println!(
|
||||
"Is {cc_number} a valid credit card number? {}",
|
||||
if luhn(cc_number) { "yes" } else { "no" }
|
||||
);
|
||||
}
|
||||
|
||||
// ANCHOR: unit-tests
|
||||
#[test]
|
||||
fn test_non_digit_cc_number() {
|
||||
assert!(!luhn("foo"));
|
||||
assert!(!luhn("foo 0 0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_cc_number() {
|
||||
assert!(!luhn(""));
|
||||
assert!(!luhn(" "));
|
||||
assert!(!luhn(" "));
|
||||
assert!(!luhn(" "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_digit_cc_number() {
|
||||
assert!(!luhn("0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_digit_cc_number() {
|
||||
assert!(luhn(" 0 0 "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_cc_number() {
|
||||
assert!(luhn("4263 9826 4026 9299"));
|
||||
assert!(luhn("4539 3195 0343 6467"));
|
||||
assert!(luhn("7992 7398 713"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_cc_number() {
|
||||
assert!(!luhn("4223 9826 4026 9299"));
|
||||
assert!(!luhn("4539 3195 0343 6476"));
|
||||
assert!(!luhn("8273 1232 7352 0569"));
|
||||
}
|
||||
// ANCHOR_END: unit-tests
|
@ -1,28 +0,0 @@
|
||||
# Day 1: Morning Exercises
|
||||
|
||||
In these exercises, we will explore two parts of Rust:
|
||||
|
||||
* Implicit conversions between types.
|
||||
|
||||
* Arrays and `for` loops.
|
||||
|
||||
<details>
|
||||
|
||||
A few things to consider while solving the exercises:
|
||||
|
||||
* Use a local Rust installation, if possible. This way you can get
|
||||
auto-completion in your editor. See the page about [Using Cargo] for details
|
||||
on installing Rust.
|
||||
|
||||
* Alternatively, use the Rust Playground.
|
||||
|
||||
The code snippets are not editable on purpose: the inline code snippets lose
|
||||
their state if you navigate away from the page.
|
||||
|
||||
After looking at the exercises, you can look at the [solutions] provided.
|
||||
|
||||
[solutions]: solutions-morning.md
|
||||
|
||||
[Using Cargo]: ../../cargo.md
|
||||
|
||||
</details>
|
@ -1,15 +0,0 @@
|
||||
# Day 1 Afternoon Exercises
|
||||
|
||||
## Luhn Algorithm
|
||||
|
||||
([back to exercise](luhn.md))
|
||||
|
||||
```rust
|
||||
{{#include luhn.rs:solution}}
|
||||
```
|
||||
|
||||
## Pattern matching
|
||||
|
||||
```rust
|
||||
{{#include pattern-matching.rs:solution}}
|
||||
```
|
@ -1,47 +0,0 @@
|
||||
# Day 1 Morning Exercises
|
||||
|
||||
## Arrays and `for` Loops
|
||||
|
||||
([back to exercise](for-loops.md))
|
||||
|
||||
```rust
|
||||
{{#include for-loops.rs:solution}}
|
||||
```
|
||||
### Bonus question
|
||||
|
||||
It requires more advanced concepts. It might seem that we could use a slice-of-slices (`&[&[i32]]`) as the input type to transpose and thus make our function handle any size of matrix. However, this quickly breaks down: the return type cannot be `&[&[i32]]` since it needs to own the data you return.
|
||||
|
||||
You can attempt to use something like `Vec<Vec<i32>>`, but this doesn't work out-of-the-box either: it's hard to convert from `Vec<Vec<i32>>` to `&[&[i32]]` so now you cannot easily use `pretty_print` either.
|
||||
|
||||
Once we get to traits and generics, we'll be able to use the [`std::convert::AsRef`][1] trait to abstract over anything that can be referenced as a slice.
|
||||
|
||||
```rust
|
||||
use std::convert::AsRef;
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn pretty_print<T, Line, Matrix>(matrix: Matrix)
|
||||
where
|
||||
T: Debug,
|
||||
// A line references a slice of items
|
||||
Line: AsRef<[T]>,
|
||||
// A matrix references a slice of lines
|
||||
Matrix: AsRef<[Line]>
|
||||
{
|
||||
for row in matrix.as_ref() {
|
||||
println!("{:?}", row.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// &[&[i32]]
|
||||
pretty_print(&[&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]);
|
||||
// [[&str; 2]; 2]
|
||||
pretty_print([["a", "b"], ["c", "d"]]);
|
||||
// Vec<Vec<i32>>
|
||||
pretty_print(vec![vec![1, 2], vec![3, 4]]);
|
||||
}
|
||||
```
|
||||
|
||||
In addition, the type itself would not enforce that the child slices are of the same length, so such variable could contain an invalid matrix.
|
||||
|
||||
[1]: https://doc.rust-lang.org/std/convert/trait.AsRef.html
|
@ -1,11 +0,0 @@
|
||||
# Day 2: Afternoon Exercises
|
||||
|
||||
The exercises for this afternoon will focus on strings and iterators.
|
||||
|
||||
<details>
|
||||
|
||||
After looking at the exercises, you can look at the [solutions] provided.
|
||||
|
||||
[solutions]: solutions-afternoon.md
|
||||
|
||||
</details>
|
@ -1,49 +0,0 @@
|
||||
# Storing Books
|
||||
|
||||
We will learn much more about structs and the `Vec<T>` type tomorrow. For now,
|
||||
you just need to know part of its API:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let mut vec = vec![10, 20];
|
||||
vec.push(30);
|
||||
let midpoint = vec.len() / 2;
|
||||
println!("middle value: {}", vec[midpoint]);
|
||||
for item in &vec {
|
||||
println!("item: {item}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use this to model a library's book collection. Copy the code below to
|
||||
<https://play.rust-lang.org/> and update the types to make it compile:
|
||||
|
||||
```rust,should_panic
|
||||
{{#include book-library.rs:setup}}
|
||||
{{#include book-library.rs:Library_new}}
|
||||
todo!("Initialize and return a `Library` value")
|
||||
}
|
||||
|
||||
{{#include book-library.rs:Library_len}}
|
||||
todo!("Return the length of `self.books`")
|
||||
}
|
||||
|
||||
{{#include book-library.rs:Library_is_empty}}
|
||||
todo!("Return `true` if `self.books` is empty")
|
||||
}
|
||||
|
||||
{{#include book-library.rs:Library_add_book}}
|
||||
todo!("Add a new book to `self.books`")
|
||||
}
|
||||
|
||||
{{#include book-library.rs:Library_print_books}}
|
||||
todo!("Iterate over `self.books` and print each book's title and year")
|
||||
}
|
||||
|
||||
{{#include book-library.rs:Library_oldest_book}}
|
||||
todo!("Return a reference to the oldest book (if any)")
|
||||
}
|
||||
}
|
||||
|
||||
{{#include book-library.rs:main}}
|
||||
```
|
@ -1,172 +0,0 @@
|
||||
// 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: setup
|
||||
struct Library {
|
||||
books: Vec<Book>,
|
||||
}
|
||||
|
||||
struct Book {
|
||||
title: String,
|
||||
year: u16,
|
||||
}
|
||||
|
||||
impl Book {
|
||||
// This is a constructor, used below.
|
||||
fn new(title: &str, year: u16) -> Book {
|
||||
Book {
|
||||
title: String::from(title),
|
||||
year,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement the methods below. Notice how the `self` parameter
|
||||
// changes type to indicate the method's required level of ownership
|
||||
// over the object:
|
||||
//
|
||||
// - `&self` for shared read-only access,
|
||||
// - `&mut self` for unique and mutable access,
|
||||
// - `self` for unique access by value.
|
||||
impl Library {
|
||||
// ANCHOR_END: setup
|
||||
|
||||
// ANCHOR: Library_new
|
||||
fn new() -> Library {
|
||||
// ANCHOR_END: Library_new
|
||||
Library { books: Vec::new() }
|
||||
}
|
||||
|
||||
// ANCHOR: Library_len
|
||||
fn len(&self) -> usize {
|
||||
// ANCHOR_END: Library_len
|
||||
self.books.len()
|
||||
}
|
||||
|
||||
// ANCHOR: Library_is_empty
|
||||
fn is_empty(&self) -> bool {
|
||||
// ANCHOR_END: Library_is_empty
|
||||
self.books.is_empty()
|
||||
}
|
||||
|
||||
// ANCHOR: Library_add_book
|
||||
fn add_book(&mut self, book: Book) {
|
||||
// ANCHOR_END: Library_add_book
|
||||
self.books.push(book)
|
||||
}
|
||||
|
||||
// ANCHOR: Library_print_books
|
||||
fn print_books(&self) {
|
||||
// ANCHOR_END: Library_print_books
|
||||
for book in &self.books {
|
||||
println!("{}, published in {}", book.title, book.year);
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: Library_oldest_book
|
||||
fn oldest_book(&self) -> Option<&Book> {
|
||||
// ANCHOR_END: Library_oldest_book
|
||||
// Using a closure and a built-in method:
|
||||
// self.books.iter().min_by_key(|book| book.year)
|
||||
|
||||
// Longer hand-written solution:
|
||||
let mut oldest: Option<&Book> = None;
|
||||
for book in self.books.iter() {
|
||||
if oldest.is_none() || book.year < oldest.unwrap().year {
|
||||
oldest = Some(book);
|
||||
}
|
||||
}
|
||||
|
||||
oldest
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: main
|
||||
fn main() {
|
||||
let mut library = Library::new();
|
||||
|
||||
println!(
|
||||
"The library is empty: library.is_empty() -> {}",
|
||||
library.is_empty()
|
||||
);
|
||||
|
||||
library.add_book(Book::new("Lord of the Rings", 1954));
|
||||
library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));
|
||||
|
||||
println!(
|
||||
"The library is no longer empty: library.is_empty() -> {}",
|
||||
library.is_empty()
|
||||
);
|
||||
|
||||
library.print_books();
|
||||
|
||||
match library.oldest_book() {
|
||||
Some(book) => println!("The oldest book is {}", book.title),
|
||||
None => println!("The library is empty!"),
|
||||
}
|
||||
|
||||
println!("The library has {} books", library.len());
|
||||
library.print_books();
|
||||
}
|
||||
// ANCHOR_END: main
|
||||
|
||||
#[test]
|
||||
fn test_library_len() {
|
||||
let mut library = Library::new();
|
||||
assert_eq!(library.len(), 0);
|
||||
assert!(library.is_empty());
|
||||
|
||||
library.add_book(Book::new("Lord of the Rings", 1954));
|
||||
library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));
|
||||
assert_eq!(library.len(), 2);
|
||||
assert!(!library.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_is_empty() {
|
||||
let mut library = Library::new();
|
||||
assert!(library.is_empty());
|
||||
|
||||
library.add_book(Book::new("Lord of the Rings", 1954));
|
||||
assert!(!library.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_print_books() {
|
||||
let mut library = Library::new();
|
||||
library.add_book(Book::new("Lord of the Rings", 1954));
|
||||
library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));
|
||||
// We could try and capture stdout, but let us just call the
|
||||
// method to start with.
|
||||
library.print_books();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_oldest_book() {
|
||||
let mut library = Library::new();
|
||||
assert!(library.oldest_book().is_none());
|
||||
|
||||
library.add_book(Book::new("Lord of the Rings", 1954));
|
||||
assert_eq!(
|
||||
library.oldest_book().map(|b| b.title.as_str()),
|
||||
Some("Lord of the Rings")
|
||||
);
|
||||
|
||||
library.add_book(Book::new("Alice's Adventures in Wonderland", 1865));
|
||||
assert_eq!(
|
||||
library.oldest_book().map(|b| b.title.as_str()),
|
||||
Some("Alice's Adventures in Wonderland")
|
||||
);
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
# Health Statistics
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.md}}
|
||||
|
||||
Copy the code below to <https://play.rust-lang.org/> and fill in the missing
|
||||
methods:
|
||||
|
||||
```rust,should_panic
|
||||
// TODO: remove this when you're done with your implementation.
|
||||
#![allow(unused_variables, dead_code)]
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:setup}}
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_new}}
|
||||
todo!("Create a new User instance")
|
||||
}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_name}}
|
||||
todo!("Return the user's name")
|
||||
}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_age}}
|
||||
todo!("Return the user's age")
|
||||
}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_height}}
|
||||
todo!("Return the user's height")
|
||||
}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_doctor_visits}}
|
||||
todo!("Return the number of time the user has visited the doctor")
|
||||
}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_set_age}}
|
||||
todo!("Set the user's age")
|
||||
}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_set_height}}
|
||||
todo!("Set the user's height")
|
||||
}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:User_visit_doctor}}
|
||||
todo!("Update a user's statistics based on measurements from a visit to the doctor")
|
||||
}
|
||||
}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:main}}
|
||||
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:tests}}
|
||||
```
|
@ -1,110 +0,0 @@
|
||||
# Iterators and Ownership
|
||||
|
||||
The ownership model of Rust affects many APIs. An example of this is the
|
||||
[`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) and
|
||||
[`IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html)
|
||||
traits.
|
||||
|
||||
## `Iterator`
|
||||
|
||||
Traits are like interfaces: they describe behavior (methods) for a type. The
|
||||
`Iterator` trait simply says that you can call `next` until you get `None` back:
|
||||
|
||||
```rust
|
||||
pub trait Iterator {
|
||||
type Item;
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
}
|
||||
```
|
||||
|
||||
You use this trait like this:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let v: Vec<i8> = vec![10, 20, 30];
|
||||
let mut iter = v.iter();
|
||||
|
||||
println!("v[0]: {:?}", iter.next());
|
||||
println!("v[1]: {:?}", iter.next());
|
||||
println!("v[2]: {:?}", iter.next());
|
||||
println!("No more items: {:?}", iter.next());
|
||||
}
|
||||
```
|
||||
|
||||
What is the type returned by the iterator? Test your answer here:
|
||||
|
||||
```rust,editable,compile_fail
|
||||
fn main() {
|
||||
let v: Vec<i8> = vec![10, 20, 30];
|
||||
let mut iter = v.iter();
|
||||
|
||||
let v0: Option<..> = iter.next();
|
||||
println!("v0: {v0:?}");
|
||||
}
|
||||
```
|
||||
|
||||
Why is this type used?
|
||||
|
||||
## `IntoIterator`
|
||||
|
||||
The `Iterator` trait tells you how to _iterate_ once you have created an
|
||||
iterator. The related trait `IntoIterator` tells you how to create the iterator:
|
||||
|
||||
```rust
|
||||
pub trait IntoIterator {
|
||||
type Item;
|
||||
type IntoIter: Iterator<Item = Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter;
|
||||
}
|
||||
```
|
||||
|
||||
The syntax here means that every implementation of `IntoIterator` must
|
||||
declare two types:
|
||||
|
||||
* `Item`: the type we iterate over, such as `i8`,
|
||||
* `IntoIter`: the `Iterator` type returned by the `into_iter` method.
|
||||
|
||||
Note that `IntoIter` and `Item` are linked: the iterator must have the same
|
||||
`Item` type, which means that it returns `Option<Item>`
|
||||
|
||||
Like before, what is the type returned by the iterator?
|
||||
|
||||
```rust,editable,compile_fail
|
||||
fn main() {
|
||||
let v: Vec<String> = vec![String::from("foo"), String::from("bar")];
|
||||
let mut iter = v.into_iter();
|
||||
|
||||
let v0: Option<..> = iter.next();
|
||||
println!("v0: {v0:?}");
|
||||
}
|
||||
```
|
||||
|
||||
## `for` Loops
|
||||
|
||||
Now that we know both `Iterator` and `IntoIterator`, we can build `for` loops.
|
||||
They call `into_iter()` on an expression and iterates over the resulting
|
||||
iterator:
|
||||
|
||||
```rust,editable
|
||||
fn main() {
|
||||
let v: Vec<String> = vec![String::from("foo"), String::from("bar")];
|
||||
|
||||
for word in &v {
|
||||
println!("word: {word}");
|
||||
}
|
||||
|
||||
for word in v {
|
||||
println!("word: {word}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
What is the type of `word` in each loop?
|
||||
|
||||
Experiment with the code above and then consult the documentation for [`impl
|
||||
IntoIterator for
|
||||
&Vec<T>`](https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-IntoIterator-for-%26'a+Vec%3CT,+A%3E)
|
||||
and [`impl IntoIterator for
|
||||
Vec<T>`](https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-IntoIterator-for-Vec%3CT,+A%3E)
|
||||
to check your answers.
|
@ -1,15 +0,0 @@
|
||||
# Day 2: Morning Exercises
|
||||
|
||||
We will look at implementing methods in two contexts:
|
||||
|
||||
* Storing books and querying the collection
|
||||
|
||||
* Keeping track of health statistics for patients
|
||||
|
||||
<details>
|
||||
|
||||
After looking at the exercises, you can look at the [solutions] provided.
|
||||
|
||||
[solutions]: solutions-morning.md
|
||||
|
||||
</details>
|
@ -1,9 +0,0 @@
|
||||
# Day 2 Afternoon Exercises
|
||||
|
||||
## Strings and Iterators
|
||||
|
||||
([back to exercise](strings-iterators.md))
|
||||
|
||||
```rust
|
||||
{{#include strings-iterators.rs:solution}}
|
||||
```
|
@ -1,17 +0,0 @@
|
||||
# Day 2 Morning Exercises
|
||||
|
||||
## Designing a Library
|
||||
|
||||
([back to exercise](book-library.md))
|
||||
|
||||
```rust
|
||||
{{#include book-library.rs:solution}}
|
||||
```
|
||||
|
||||
## Health Statistics
|
||||
|
||||
([back to exercise](health-statistics.md))
|
||||
|
||||
```rust
|
||||
{{#include ../../../third_party/rust-on-exercism/health-statistics.rs:solution}}
|
||||
```
|
@ -1,21 +0,0 @@
|
||||
# Strings and Iterators
|
||||
|
||||
In this exercise, you are implementing a routing component of a web server. The
|
||||
server is configured with a number of _path prefixes_ which are matched against
|
||||
_request paths_. The path prefixes can contain a wildcard character which
|
||||
matches a full segment. See the unit tests below.
|
||||
|
||||
Copy the following code to <https://play.rust-lang.org/> and make the tests
|
||||
pass. Try avoiding allocating a `Vec` for your intermediate results:
|
||||
|
||||
|
||||
```rust
|
||||
// TODO: remove this when you're done with your implementation.
|
||||
#![allow(unused_variables, dead_code)]
|
||||
|
||||
{{#include strings-iterators.rs:prefix_matches}}
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
{{#include strings-iterators.rs:unit-tests}}
|
||||
```
|
@ -1,75 +0,0 @@
|
||||
// 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: prefix_matches
|
||||
pub fn prefix_matches(prefix: &str, request_path: &str) -> bool {
|
||||
// ANCHOR_END: prefix_matches
|
||||
|
||||
let mut request_segments = request_path.split('/');
|
||||
|
||||
for prefix_segment in prefix.split('/') {
|
||||
let Some(request_segment) = request_segments.next() else {
|
||||
return false;
|
||||
};
|
||||
if request_segment != prefix_segment && prefix_segment != "*" {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
|
||||
// Alternatively, Iterator::zip() lets us iterate simultaneously over prefix
|
||||
// and request segments. The zip() iterator is finished as soon as one of
|
||||
// the source iterators is finished, but we need to iterate over all request
|
||||
// segments. A neat trick that makes zip() work is to use map() and chain()
|
||||
// to produce an iterator that returns Some(str) for each pattern segments,
|
||||
// and then returns None indefinitely.
|
||||
}
|
||||
|
||||
// ANCHOR: unit-tests
|
||||
#[test]
|
||||
fn test_matches_without_wildcard() {
|
||||
assert!(prefix_matches("/v1/publishers", "/v1/publishers"));
|
||||
assert!(prefix_matches("/v1/publishers", "/v1/publishers/abc-123"));
|
||||
assert!(prefix_matches("/v1/publishers", "/v1/publishers/abc/books"));
|
||||
|
||||
assert!(!prefix_matches("/v1/publishers", "/v1"));
|
||||
assert!(!prefix_matches("/v1/publishers", "/v1/publishersBooks"));
|
||||
assert!(!prefix_matches("/v1/publishers", "/v1/parent/publishers"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches_with_wildcard() {
|
||||
assert!(prefix_matches(
|
||||
"/v1/publishers/*/books",
|
||||
"/v1/publishers/foo/books"
|
||||
));
|
||||
assert!(prefix_matches(
|
||||
"/v1/publishers/*/books",
|
||||
"/v1/publishers/bar/books"
|
||||
));
|
||||
assert!(prefix_matches(
|
||||
"/v1/publishers/*/books",
|
||||
"/v1/publishers/foo/books/book1"
|
||||
));
|
||||
|
||||
assert!(!prefix_matches("/v1/publishers/*/books", "/v1/publishers"));
|
||||
assert!(!prefix_matches(
|
||||
"/v1/publishers/*/books",
|
||||
"/v1/publishers/foo/booksByAuthor"
|
||||
));
|
||||
}
|
||||
// ANCHOR_END: unit-tests
|
||||
|
||||
fn main() {}
|
@ -1,18 +0,0 @@
|
||||
# Day 3: Afternoon Exercises
|
||||
|
||||
Let us build a safe wrapper for reading directory content!
|
||||
|
||||
For this exercise, we suggest using a local dev environment instead
|
||||
of the Playground. This will allow you to run your binary on your own machine.
|
||||
|
||||
To get started, follow the [running locally] instructions.
|
||||
|
||||
[running locally]: ../../cargo/running-locally.md
|
||||
|
||||
<details>
|
||||
|
||||
After looking at the exercise, you can look at the [solution] provided.
|
||||
|
||||
[solution]: solutions-afternoon.md
|
||||
|
||||
</details>
|
@ -1,13 +0,0 @@
|
||||
# Day 3: Morning Exercises
|
||||
|
||||
We will design a classical GUI library using traits and trait objects.
|
||||
|
||||
We will also look at enum dispatch with an exercise involving points and polygons.
|
||||
|
||||
<details>
|
||||
|
||||
After looking at the exercises, you can look at the [solutions] provided.
|
||||
|
||||
[solutions]: solutions-morning.md
|
||||
|
||||
</details>
|
@ -1,53 +0,0 @@
|
||||
# Polygon Struct
|
||||
|
||||
We will create a `Polygon` struct which contain some points. Copy the code below
|
||||
to <https://play.rust-lang.org/> and fill in the missing methods to make the
|
||||
tests pass:
|
||||
|
||||
```rust
|
||||
// TODO: remove this when you're done with your implementation.
|
||||
#![allow(unused_variables, dead_code)]
|
||||
|
||||
{{#include points-polygons.rs:Point}}
|
||||
// add fields
|
||||
}
|
||||
|
||||
{{#include points-polygons.rs:Point-impl}}
|
||||
// add methods
|
||||
}
|
||||
|
||||
{{#include points-polygons.rs:Polygon}}
|
||||
// add fields
|
||||
}
|
||||
|
||||
{{#include points-polygons.rs:Polygon-impl}}
|
||||
// add methods
|
||||
}
|
||||
|
||||
{{#include points-polygons.rs:Circle}}
|
||||
// add fields
|
||||
}
|
||||
|
||||
{{#include points-polygons.rs:Circle-impl}}
|
||||
// add methods
|
||||
}
|
||||
|
||||
{{#include points-polygons.rs:Shape}}
|
||||
|
||||
{{#include points-polygons.rs:unit-tests}}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
Since the method signatures are missing from the problem statements, the key part
|
||||
of the exercise is to specify those correctly. You don't have to modify the tests.
|
||||
|
||||
Other interesting parts of the exercise:
|
||||
|
||||
* Derive a `Copy` trait for some structs, as in tests the methods sometimes don't borrow their arguments.
|
||||
* Discover that `Add` trait must be implemented for two objects to be addable via "+". Note that we do not discuss generics until Day 3.
|
||||
|
||||
</details>
|
@ -1,233 +0,0 @@
|
||||
// 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
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
// ANCHOR: Point
|
||||
pub struct Point {
|
||||
// ANCHOR_END: Point
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
// ANCHOR: Point-impl
|
||||
impl Point {
|
||||
// ANCHOR_END: Point-impl
|
||||
pub fn new(x: i32, y: i32) -> Point {
|
||||
Point { x, y }
|
||||
}
|
||||
|
||||
pub fn magnitude(self) -> f64 {
|
||||
f64::from(self.x.pow(2) + self.y.pow(2)).sqrt()
|
||||
}
|
||||
|
||||
pub fn dist(self, other: Point) -> f64 {
|
||||
(self - other).magnitude()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x + other.x,
|
||||
y: self.y + other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x - other.x,
|
||||
y: self.y - other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: Polygon
|
||||
pub struct Polygon {
|
||||
// ANCHOR_END: Polygon
|
||||
points: Vec<Point>,
|
||||
}
|
||||
|
||||
// ANCHOR: Polygon-impl
|
||||
impl Polygon {
|
||||
// ANCHOR_END: Polygon-impl
|
||||
pub fn new() -> Polygon {
|
||||
Polygon { points: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn add_point(&mut self, point: Point) {
|
||||
self.points.push(point);
|
||||
}
|
||||
|
||||
pub fn left_most_point(&self) -> Option<Point> {
|
||||
self.points.iter().min_by_key(|p| p.x).copied()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Point> {
|
||||
self.points.iter()
|
||||
}
|
||||
|
||||
pub fn length(&self) -> f64 {
|
||||
if self.points.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut result = 0.0;
|
||||
let mut last_point = self.points[0];
|
||||
for point in &self.points[1..] {
|
||||
result += last_point.dist(*point);
|
||||
last_point = *point;
|
||||
}
|
||||
result += last_point.dist(self.points[0]);
|
||||
result
|
||||
// Alternatively, Iterator::zip() lets us iterate over the points as pairs
|
||||
// but we need to pair each point with the next one, and the last point
|
||||
// with the first point. The zip() iterator is finished as soon as one of
|
||||
// the source iterators is finished, a neat trick is to combine Iterator::cycle
|
||||
// with Iterator::skip to create the second iterator for the zip and using map
|
||||
// and sum to calculate the total length.
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: Circle
|
||||
pub struct Circle {
|
||||
// ANCHOR_END: Circle
|
||||
center: Point,
|
||||
radius: i32,
|
||||
}
|
||||
|
||||
// ANCHOR: Circle-impl
|
||||
impl Circle {
|
||||
// ANCHOR_END: Circle-impl
|
||||
pub fn new(center: Point, radius: i32) -> Circle {
|
||||
Circle { center, radius }
|
||||
}
|
||||
|
||||
pub fn circumference(&self) -> f64 {
|
||||
2.0 * std::f64::consts::PI * f64::from(self.radius)
|
||||
}
|
||||
|
||||
pub fn dist(&self, other: &Self) -> f64 {
|
||||
self.center.dist(other.center)
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: Shape
|
||||
pub enum Shape {
|
||||
Polygon(Polygon),
|
||||
Circle(Circle),
|
||||
}
|
||||
// ANCHOR_END: Shape
|
||||
|
||||
impl From<Polygon> for Shape {
|
||||
fn from(poly: Polygon) -> Self {
|
||||
Shape::Polygon(poly)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Circle> for Shape {
|
||||
fn from(circle: Circle) -> Self {
|
||||
Shape::Circle(circle)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
pub fn perimeter(&self) -> f64 {
|
||||
match self {
|
||||
Shape::Polygon(poly) => poly.length(),
|
||||
Shape::Circle(circle) => circle.circumference(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: unit-tests
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn round_two_digits(x: f64) -> f64 {
|
||||
(x * 100.0).round() / 100.0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_point_magnitude() {
|
||||
let p1 = Point::new(12, 13);
|
||||
assert_eq!(round_two_digits(p1.magnitude()), 17.69);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_point_dist() {
|
||||
let p1 = Point::new(10, 10);
|
||||
let p2 = Point::new(14, 13);
|
||||
assert_eq!(round_two_digits(p1.dist(p2)), 5.00);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_point_add() {
|
||||
let p1 = Point::new(16, 16);
|
||||
let p2 = p1 + Point::new(-4, 3);
|
||||
assert_eq!(p2, Point::new(12, 19));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polygon_left_most_point() {
|
||||
let p1 = Point::new(12, 13);
|
||||
let p2 = Point::new(16, 16);
|
||||
|
||||
let mut poly = Polygon::new();
|
||||
poly.add_point(p1);
|
||||
poly.add_point(p2);
|
||||
assert_eq!(poly.left_most_point(), Some(p1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polygon_iter() {
|
||||
let p1 = Point::new(12, 13);
|
||||
let p2 = Point::new(16, 16);
|
||||
|
||||
let mut poly = Polygon::new();
|
||||
poly.add_point(p1);
|
||||
poly.add_point(p2);
|
||||
|
||||
let points = poly.iter().cloned().collect::<Vec<_>>();
|
||||
assert_eq!(points, vec![Point::new(12, 13), Point::new(16, 16)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shape_perimeters() {
|
||||
let mut poly = Polygon::new();
|
||||
poly.add_point(Point::new(12, 13));
|
||||
poly.add_point(Point::new(17, 11));
|
||||
poly.add_point(Point::new(16, 16));
|
||||
let shapes = vec![
|
||||
Shape::from(poly),
|
||||
Shape::from(Circle::new(Point::new(10, 20), 5)),
|
||||
];
|
||||
let perimeters = shapes
|
||||
.iter()
|
||||
.map(Shape::perimeter)
|
||||
.map(round_two_digits)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(perimeters, vec![15.48, 31.42]);
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: unit-tests
|
||||
|
||||
fn main() {}
|
@ -1,9 +0,0 @@
|
||||
# Day 3 Afternoon Exercises
|
||||
|
||||
## Safe FFI Wrapper
|
||||
|
||||
([back to exercise](safe-ffi-wrapper.md))
|
||||
|
||||
```rust
|
||||
{{#include safe-ffi-wrapper.rs:solution}}
|
||||
```
|
@ -1,17 +0,0 @@
|
||||
# Day 3 Morning Exercise
|
||||
|
||||
## Drawing A Simple GUI
|
||||
|
||||
([back to exercise](simple-gui.md))
|
||||
|
||||
```rust
|
||||
{{#include simple-gui.rs:solution}}
|
||||
```
|
||||
|
||||
## Points and Polygons
|
||||
|
||||
([back to exercise](points-polygons.md))
|
||||
|
||||
```rust
|
||||
{{#include points-polygons.rs:solution}}
|
||||
```
|
@ -1,7 +0,0 @@
|
||||
# Solutions
|
||||
|
||||
You will find solutions to the exercises on the following pages.
|
||||
|
||||
Feel free to ask questions about the solutions [on
|
||||
GitHub](https://github.com/google/comprehensive-rust/discussions). Let us know
|
||||
if you have a different or better solution than what is presented here.
|
@ -1,5 +1,3 @@
|
||||
# Generics
|
||||
|
||||
Rust support generics, which lets you abstract algorithms or data structures
|
||||
(such as sorting or a binary tree)
|
||||
over the types used or stored.
|
||||
{{%segment outline}}
|
||||
|
10
src/generics/Cargo.toml
Normal file
10
src/generics/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "generics"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "generics"
|
||||
path = "exercise.rs"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user