From ba2547dba4e30366cac95e2b4e6851cb670ca4ce Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:34:12 -0500 Subject: [PATCH 1/7] Initial translation of Comprehensive Rust Training to our format --- .../020_hello_world.rst | 38 ++++ .../020_hello_world/01_what_is_rust.rst | 46 +++++ .../020_hello_world/02_benefits.rst | 65 ++++++ .../020_hello_world/03_playground.rst | 37 ++++ .../030_types_and_values.rst | 41 ++++ .../030_types_and_values/01_hello_world.rst | 61 ++++++ .../030_types_and_values/02_variables.rst | 40 ++++ .../030_types_and_values/03_values.rst | 52 +++++ .../030_types_and_values/04_arithmetic.rst | 45 ++++ .../030_types_and_values/05_inference.rst | 63 ++++++ .../030_types_and_values/06_exercise.rst | 28 +++ .../040_control_flow_basics.rst | 43 ++++ .../040_control_flow_basics/01_if.rst | 53 +++++ .../040_control_flow_basics/02_match.rst | 85 ++++++++ .../040_control_flow_basics/03_loops.rst | 29 +++ .../04_break_continue.rst | 45 ++++ .../05_blocks_and_scopes.rst | 43 ++++ .../040_control_flow_basics/06_functions.rst | 51 +++++ .../040_control_flow_basics/07_macros.rst | 57 +++++ .../040_control_flow_basics/08_exercise.rst | 39 ++++ .../050_tuples_and_arrays.rst | 40 ++++ .../050_tuples_and_arrays/01_arrays.rst | 49 +++++ .../050_tuples_and_arrays/02_tuples.rst | 39 ++++ .../050_tuples_and_arrays/03_iteration.rst | 36 ++++ .../04_destructuring.rst | 50 +++++ .../050_tuples_and_arrays/05_exercise.rst | 44 ++++ .../060_references.rst | 41 ++++ .../060_references/01_shared.rst | 75 +++++++ .../060_references/02_exclusive.rst | 45 ++++ .../060_references/03_slices.rst | 55 +++++ .../060_references/04_strings.rst | 104 ++++++++++ .../060_references/05_dangling.rst | 51 +++++ .../060_references/06_exercise.rst | 34 +++ .../070_user_defined_types.rst | 42 ++++ .../01_named_structs.rst | 68 ++++++ .../02_tuple_structs.rst | 73 +++++++ .../070_user_defined_types/03_enums.rst | 142 +++++++++++++ .../070_user_defined_types/04_aliases.rst | 39 ++++ .../070_user_defined_types/05_const.rst | 60 ++++++ .../070_user_defined_types/06_static.rst | 58 ++++++ .../070_user_defined_types/07_exercise.rst | 46 +++++ .../080_pattern_matching.rst | 40 ++++ .../080_pattern_matching/01_match.rst | 90 ++++++++ .../02_destructuring_structs.rst | 30 +++ .../03_destructuring_enums.rst | 61 ++++++ .../04_let_control_flow.rst | 167 +++++++++++++++ .../080_pattern_matching/05_exercise.rst | 88 ++++++++ .../090_methods_and_traits.rst | 39 ++++ .../090_methods_and_traits/01_methods.rst | 106 ++++++++++ .../090_methods_and_traits/02_traits.rst | 35 ++++ .../090_methods_and_traits/03_deriving.rst | 41 ++++ .../090_methods_and_traits/04_exercise.rst | 29 +++ .../100_generics.rst | 42 ++++ .../100_generics/01_generic_functions.rst | 62 ++++++ .../100_generics/02_generic_data.rst | 59 ++++++ .../100_generics/03_generic_traits.rst | 61 ++++++ .../100_generics/04_trait_bounds.rst | 61 ++++++ .../100_generics/05_impl_trait.rst | 62 ++++++ .../100_generics/06_dyn_trait.rst | 96 +++++++++ .../100_generics/07_exercise.rst | 35 ++++ .../110_std_types.rst | 43 ++++ .../110_std_types/01_std.rst | 22 ++ .../110_std_types/02_docs.rst | 59 ++++++ .../110_std_types/03_option.rst | 49 +++++ .../110_std_types/04_result.rst | 54 +++++ .../110_std_types/05_string.rst | 76 +++++++ .../110_std_types/06_vec.rst | 63 ++++++ .../110_std_types/07_hashmap.rst | 91 ++++++++ .../110_std_types/08_exercise.rst | 56 +++++ .../120_std_traits.rst | 43 ++++ .../120_std_traits/01_comparisons.rst | 88 ++++++++ .../120_std_traits/02_operators.rst | 66 ++++++ .../120_std_traits/03_from_and_into.rst | 54 +++++ .../120_std_traits/04_casting.rst | 52 +++++ .../120_std_traits/05_read_and_write.rst | 51 +++++ .../120_std_traits/06_default.rst | 64 ++++++ .../120_std_traits/07_closures.rst | 92 +++++++++ .../120_std_traits/08_exercise.rst | 23 +++ .../130_memory_management.rst | 43 ++++ .../130_memory_management/01_review.rst | 90 ++++++++ .../130_memory_management/02_approaches.rst | 66 ++++++ .../130_memory_management/03_ownership.rst | 45 ++++ .../130_memory_management/04_move.rst | 195 ++++++++++++++++++ .../130_memory_management/05_clone.rst | 45 ++++ .../130_memory_management/06_copy_types.rst | 86 ++++++++ .../130_memory_management/07_drop.rst | 73 +++++++ .../130_memory_management/08_exercise.rst | 44 ++++ .../140_smart_pointers.rst | 39 ++++ .../140_smart_pointers/01_box.rst | 106 ++++++++++ .../140_smart_pointers/02_rc.rst | 52 +++++ .../140_smart_pointers/03_trait_objects.rst | 116 +++++++++++ .../140_smart_pointers/04_exercise.rst | 26 +++ .../150_borrowing.rst | 40 ++++ .../150_borrowing/01_shared.rst | 96 +++++++++ .../150_borrowing/02_borrowck.rst | 83 ++++++++ .../150_borrowing/03_examples.rst | 44 ++++ .../150_borrowing/04_interior_mutability.rst | 30 +++ .../150_borrowing/05_exercise.rst | 28 +++ .../160_lifetimes.rst | 39 ++++ .../160_lifetimes/01_lifetime_annotations.rst | 76 +++++++ .../160_lifetimes/02_lifetime_elision.rst | 79 +++++++ .../160_lifetimes/03_struct_lifetimes.rst | 52 +++++ .../160_lifetimes/04_exercise.rst | 120 +++++++++++ .../170_iterators.rst | 41 ++++ .../170_iterators/01_motivation.rst | 73 +++++++ .../170_iterators/02_iterator.rst | 78 +++++++ .../170_iterators/03_helpers.rst | 52 +++++ .../170_iterators/04_collect.rst | 61 ++++++ .../170_iterators/05_intoiterator.rst | 97 +++++++++ .../170_iterators/06_exercise.rst | 24 +++ .../180_modules.rst | 41 ++++ .../180_modules/01_modules.rst | 46 +++++ .../180_modules/02_filesystem.rst | 85 ++++++++ .../180_modules/03_visibility.rst | 62 ++++++ .../180_modules/04_encapsulation.rst | 90 ++++++++ .../180_modules/05_paths.rst | 63 ++++++ .../180_modules/06_exercise.rst | 53 +++++ .../190_testing.rst | 39 ++++ .../190_testing/01_unit_tests.rst | 56 +++++ .../190_testing/02_other.rst | 53 +++++ .../190_testing/03_lints.rst | 39 ++++ .../190_testing/04_exercise.rst | 39 ++++ .../200_error_handling.rst | 43 ++++ .../200_error_handling/01_panics.rst | 63 ++++++ .../200_error_handling/02_result.rst | 96 +++++++++ .../200_error_handling/03_try.rst | 74 +++++++ .../200_error_handling/04_try_conversions.rst | 103 +++++++++ .../200_error_handling/05_error.rst | 54 +++++ .../200_error_handling/06_thiserror.rst | 60 ++++++ .../200_error_handling/07_anyhow.rst | 83 ++++++++ .../200_error_handling/08_exercise.rst | 30 +++ .../210_unsafe_rust.rst | 42 ++++ .../210_unsafe_rust/01_unsafe.rst | 46 +++++ .../210_unsafe_rust/02_dereferencing.rst | 70 +++++++ .../210_unsafe_rust/03_mutable_static.rst | 58 ++++++ .../210_unsafe_rust/04_unions.rst | 41 ++++ .../210_unsafe_rust/05_unsafe_functions.rst | 121 +++++++++++ .../210_unsafe_rust/06_unsafe_traits.rst | 50 +++++ .../210_unsafe_rust/07_exercise.rst | 99 +++++++++ .../standard_course.txt | 20 ++ 140 files changed, 8360 insertions(+) create mode 100644 courses/comprehensive_rust_training/020_hello_world.rst create mode 100644 courses/comprehensive_rust_training/020_hello_world/01_what_is_rust.rst create mode 100644 courses/comprehensive_rust_training/020_hello_world/02_benefits.rst create mode 100644 courses/comprehensive_rust_training/020_hello_world/03_playground.rst create mode 100644 courses/comprehensive_rust_training/030_types_and_values.rst create mode 100644 courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst create mode 100644 courses/comprehensive_rust_training/030_types_and_values/02_variables.rst create mode 100644 courses/comprehensive_rust_training/030_types_and_values/03_values.rst create mode 100644 courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst create mode 100644 courses/comprehensive_rust_training/030_types_and_values/05_inference.rst create mode 100644 courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics/05_blocks_and_scopes.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst create mode 100644 courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst create mode 100644 courses/comprehensive_rust_training/050_tuples_and_arrays.rst create mode 100644 courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst create mode 100644 courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst create mode 100644 courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst create mode 100644 courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst create mode 100644 courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst create mode 100644 courses/comprehensive_rust_training/060_references.rst create mode 100644 courses/comprehensive_rust_training/060_references/01_shared.rst create mode 100644 courses/comprehensive_rust_training/060_references/02_exclusive.rst create mode 100644 courses/comprehensive_rust_training/060_references/03_slices.rst create mode 100644 courses/comprehensive_rust_training/060_references/04_strings.rst create mode 100644 courses/comprehensive_rust_training/060_references/05_dangling.rst create mode 100644 courses/comprehensive_rust_training/060_references/06_exercise.rst create mode 100644 courses/comprehensive_rust_training/070_user_defined_types.rst create mode 100644 courses/comprehensive_rust_training/070_user_defined_types/01_named_structs.rst create mode 100644 courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst create mode 100644 courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst create mode 100644 courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst create mode 100644 courses/comprehensive_rust_training/070_user_defined_types/05_const.rst create mode 100644 courses/comprehensive_rust_training/070_user_defined_types/06_static.rst create mode 100644 courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst create mode 100644 courses/comprehensive_rust_training/080_pattern_matching.rst create mode 100644 courses/comprehensive_rust_training/080_pattern_matching/01_match.rst create mode 100644 courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst create mode 100644 courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst create mode 100644 courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst create mode 100644 courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst create mode 100644 courses/comprehensive_rust_training/090_methods_and_traits.rst create mode 100644 courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst create mode 100644 courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst create mode 100644 courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst create mode 100644 courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst create mode 100644 courses/comprehensive_rust_training/100_generics.rst create mode 100644 courses/comprehensive_rust_training/100_generics/01_generic_functions.rst create mode 100644 courses/comprehensive_rust_training/100_generics/02_generic_data.rst create mode 100644 courses/comprehensive_rust_training/100_generics/03_generic_traits.rst create mode 100644 courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst create mode 100644 courses/comprehensive_rust_training/100_generics/05_impl_trait.rst create mode 100644 courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst create mode 100644 courses/comprehensive_rust_training/100_generics/07_exercise.rst create mode 100644 courses/comprehensive_rust_training/110_std_types.rst create mode 100644 courses/comprehensive_rust_training/110_std_types/01_std.rst create mode 100644 courses/comprehensive_rust_training/110_std_types/02_docs.rst create mode 100644 courses/comprehensive_rust_training/110_std_types/03_option.rst create mode 100644 courses/comprehensive_rust_training/110_std_types/04_result.rst create mode 100644 courses/comprehensive_rust_training/110_std_types/05_string.rst create mode 100644 courses/comprehensive_rust_training/110_std_types/06_vec.rst create mode 100644 courses/comprehensive_rust_training/110_std_types/07_hashmap.rst create mode 100644 courses/comprehensive_rust_training/110_std_types/08_exercise.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits/02_operators.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits/04_casting.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits/06_default.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits/07_closures.rst create mode 100644 courses/comprehensive_rust_training/120_std_traits/08_exercise.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management/01_review.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management/02_approaches.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management/03_ownership.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management/04_move.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management/05_clone.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management/07_drop.rst create mode 100644 courses/comprehensive_rust_training/130_memory_management/08_exercise.rst create mode 100644 courses/comprehensive_rust_training/140_smart_pointers.rst create mode 100644 courses/comprehensive_rust_training/140_smart_pointers/01_box.rst create mode 100644 courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst create mode 100644 courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst create mode 100644 courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst create mode 100644 courses/comprehensive_rust_training/150_borrowing.rst create mode 100644 courses/comprehensive_rust_training/150_borrowing/01_shared.rst create mode 100644 courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst create mode 100644 courses/comprehensive_rust_training/150_borrowing/03_examples.rst create mode 100644 courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst create mode 100644 courses/comprehensive_rust_training/150_borrowing/05_exercise.rst create mode 100644 courses/comprehensive_rust_training/160_lifetimes.rst create mode 100644 courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst create mode 100644 courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst create mode 100644 courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst create mode 100644 courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst create mode 100644 courses/comprehensive_rust_training/170_iterators.rst create mode 100644 courses/comprehensive_rust_training/170_iterators/01_motivation.rst create mode 100644 courses/comprehensive_rust_training/170_iterators/02_iterator.rst create mode 100644 courses/comprehensive_rust_training/170_iterators/03_helpers.rst create mode 100644 courses/comprehensive_rust_training/170_iterators/04_collect.rst create mode 100644 courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst create mode 100644 courses/comprehensive_rust_training/170_iterators/06_exercise.rst create mode 100644 courses/comprehensive_rust_training/180_modules.rst create mode 100644 courses/comprehensive_rust_training/180_modules/01_modules.rst create mode 100644 courses/comprehensive_rust_training/180_modules/02_filesystem.rst create mode 100644 courses/comprehensive_rust_training/180_modules/03_visibility.rst create mode 100644 courses/comprehensive_rust_training/180_modules/04_encapsulation.rst create mode 100644 courses/comprehensive_rust_training/180_modules/05_paths.rst create mode 100644 courses/comprehensive_rust_training/180_modules/06_exercise.rst create mode 100644 courses/comprehensive_rust_training/190_testing.rst create mode 100644 courses/comprehensive_rust_training/190_testing/01_unit_tests.rst create mode 100644 courses/comprehensive_rust_training/190_testing/02_other.rst create mode 100644 courses/comprehensive_rust_training/190_testing/03_lints.rst create mode 100644 courses/comprehensive_rust_training/190_testing/04_exercise.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling/01_panics.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling/02_result.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling/03_try.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling/05_error.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst create mode 100644 courses/comprehensive_rust_training/200_error_handling/08_exercise.rst create mode 100644 courses/comprehensive_rust_training/210_unsafe_rust.rst create mode 100644 courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst create mode 100644 courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst create mode 100644 courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst create mode 100644 courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst create mode 100644 courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst create mode 100644 courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst create mode 100644 courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst create mode 100644 courses/comprehensive_rust_training/standard_course.txt diff --git a/courses/comprehensive_rust_training/020_hello_world.rst b/courses/comprehensive_rust_training/020_hello_world.rst new file mode 100644 index 000000000..e57e4504c --- /dev/null +++ b/courses/comprehensive_rust_training/020_hello_world.rst @@ -0,0 +1,38 @@ +************* +Hello World +************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 020_hello_world/01_what_is_rust.rst +.. include:: 020_hello_world/02_benefits.rst +.. include:: 020_hello_world/03_playground.rst diff --git a/courses/comprehensive_rust_training/020_hello_world/01_what_is_rust.rst b/courses/comprehensive_rust_training/020_hello_world/01_what_is_rust.rst new file mode 100644 index 000000000..152c69241 --- /dev/null +++ b/courses/comprehensive_rust_training/020_hello_world/01_what_is_rust.rst @@ -0,0 +1,46 @@ +=============== +What is Rust? +=============== + +--------------- +What is Rust? +--------------- + +Rust is a new programming language which had its `1.0 release in +2015 `__: + +- Rust is a statically compiled language in a similar role as C++ + + - ``rustc`` uses LLVM as its backend. + +- Rust supports many `platforms and + architectures `__: + + - x86, ARM, WebAssembly, ... + - Linux, Mac, Windows, ... + +- Rust is used for a wide range of devices: + + - firmware and boot loaders, + - smart displays, + - mobile phones, + - desktops, + - servers. + +.. raw:: html + +--------- +Details +--------- + +Rust fits in the same area as C++: + +- High flexibility. +- High level of control. +- Can be scaled down to very constrained devices such as + microcontrollers. +- Has no runtime or garbage collection. +- Focuses on reliability and safety without sacrificing performance. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst new file mode 100644 index 000000000..aa7f15f39 --- /dev/null +++ b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst @@ -0,0 +1,65 @@ +================== +Benefits of Rust +================== + +------------------ +Benefits of Rust +------------------ + +Some unique selling points of Rust: + +- *Compile time memory safety* - whole classes of memory bugs are + prevented at compile time + + - No uninitialized variables. + - No double-frees. + - No use-after-free. + - No ``NULL`` pointers. + - No forgotten locked mutexes. + - No data races between threads. + - No iterator invalidation. + +- *No undefined runtime behavior* - what a Rust statement does is never + left unspecified + + - Array access is bounds checked. + - Integer overflow is defined (panic or wrap-around). + +- *Modern language features* - as expressive and ergonomic as + higher-level languages + + - Enums and pattern matching. + - Generics. + - No overhead FFI. + - Zero-cost abstractions. + - Great compiler errors. + - Built-in dependency manager. + - Built-in support for testing. + - Excellent Language Server Protocol support. + +.. raw:: html + +--------- +Details +--------- + +Do not spend much time here. All of these points will be covered in more +depth later. + +Make sure to ask the class which languages they have experience with. +Depending on the answer you can highlight different features of Rust: + +- Experience with C or C++: Rust eliminates a whole class of *runtime + errors* via the borrow checker. You get performance like in C and + C++, but you don't have the memory unsafety issues. In addition, you + get a modern language with constructs like pattern matching and + built-in dependency management. + +- Experience with Java, Go, Python, JavaScript...: You get the same + memory safety as in those languages, plus a similar high-level + language feeling. In addition you get fast and predictable + performance like C and C++ (no garbage collector) as well as access + to low-level hardware (should you need it). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst new file mode 100644 index 000000000..4edb31cec --- /dev/null +++ b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst @@ -0,0 +1,37 @@ +============ +Playground +============ + +------------ +Playground +------------ + +The `Rust Playground `__ provides an easy +way to run short Rust programs, and is the basis for the examples and +exercises in this course. Try running the "hello-world" program it +starts with. It comes with a few handy features: + +- Under "Tools", use the ``rustfmt`` option to format your code in the + "standard" way. + +- Rust has two main "profiles" for generating code: Debug (extra + runtime checks, less optimization) and Release (fewer runtime checks, + lots of optimization). These are accessible under "Debug" at the top. + +- If you're interested, use "ASM" under "..." to see the generated + assembly code. + +.. raw:: html + +--------- +Details +--------- + +As students head into the break, encourage them to open up the +playground and experiment a little. Encourage them to keep the tab open +and try things out during the rest of the course. This is particularly +helpful for advanced students who want to know more about Rust's +optimizations or generated assembly. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values.rst b/courses/comprehensive_rust_training/030_types_and_values.rst new file mode 100644 index 000000000..be22347cf --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values.rst @@ -0,0 +1,41 @@ +****************** +Types And Values +****************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 030_types_and_values/01_hello_world.rst +.. include:: 030_types_and_values/02_variables.rst +.. include:: 030_types_and_values/03_values.rst +.. include:: 030_types_and_values/04_arithmetic.rst +.. include:: 030_types_and_values/05_inference.rst +.. include:: 030_types_and_values/06_exercise.rst diff --git a/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst b/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst new file mode 100644 index 000000000..881cf0da7 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst @@ -0,0 +1,61 @@ +============== +Hello, World +============== + +-------------- +Hello, World +-------------- + +Let us jump into the simplest possible Rust program, a classic Hello +World program: + +.. code:: rust,editable + + fn main() { + println!("Hello TBD!"); + } + +What you see: + +- Functions are introduced with ``fn``. +- Blocks are delimited by curly braces like in C and C++. +- The ``main`` function is the entry point of the program. +- Rust has hygienic macros, ``println!`` is an example of this. +- Rust strings are UTF-8 encoded and can contain any Unicode character. + +.. raw:: html + +--------- +Details +--------- + +This slide tries to make the students comfortable with Rust code. They +will see a ton of it over the next four days so we start small with +something familiar. + +Key points: + +- Rust is very much like other languages in the C/C++/Java tradition. + It is imperative and it doesn't try to reinvent things unless + absolutely necessary. + +- Rust is modern with full support for things like Unicode. + +- Rust uses macros for situations where you want to have a variable + number of arguments (no function + `overloading <../control-flow-basics/functions.md>`__). + +- Macros being 'hygienic' means they don't accidentally capture + identifiers from the scope they are used in. Rust macros are actually + only `partially + hygienic `__. + +- Rust is multi-paradigm. For example, it has powerful `object-oriented + programming + features `__, and, + while it is not a functional language, it includes a range of + `functional + concepts `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst b/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst new file mode 100644 index 000000000..d61956bc4 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst @@ -0,0 +1,40 @@ +=========== +Variables +=========== + +----------- +Variables +----------- + +Rust provides type safety via static typing. Variable bindings are made +with ``let``: + +.. code:: rust,editable,warnunused + + fn main() { + let x: i32 = 10; + println!("x: {x}"); + // x = 20; + // println!("x: {x}"); + } + +.. raw:: html + +--------- +Details +--------- + +- Uncomment the ``x = 20`` to demonstrate that variables are immutable + by default. Add the ``mut`` keyword to allow changes. + +- Warnings are enabled for this slide, such as for unused variables or + unnecessary ``mut``. These are omitted in most slides to avoid + distracting warnings. Try removing the mutation but leaving the + ``mut`` keyword in place. + +- The ``i32`` here is the type of the variable. This must be known at + compile time, but type inference (covered later) allows the + programmer to omit it in many cases. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/03_values.rst b/courses/comprehensive_rust_training/030_types_and_values/03_values.rst new file mode 100644 index 000000000..696259e64 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/03_values.rst @@ -0,0 +1,52 @@ +======== +Values +======== + +-------- +Values +-------- + +Here are some basic built-in types, and the syntax for literal values of +each type. + ++---------------+-------------------------------+---------------------+ +| | Types | Literals | ++===============+===============================+=====================+ +| Signed | ``i8``, ``i16``, ``i32``, | ``-10``, ``0``, | +| integers | ``i64``, ``i128``, ``isize`` | ``1_000``, | +| | | ``123_i64`` | ++---------------+-------------------------------+---------------------+ +| Unsigned | ``u8``, ``u16``, ``u32``, | ``0``, ``123``, | +| integers | ``u64``, ``u128``, ``usize`` | ``10_u16`` | ++---------------+-------------------------------+---------------------+ +| Floating | ``f32``, ``f64`` | ``3.14``, | +| point numbers | | ``-10.0e20``, | +| | | ``2_f32`` | ++---------------+-------------------------------+---------------------+ +| Unicode | ``char`` | ``'a'``, ``'TBD'``, | +| scalar values | | ``'TBD'`` | ++---------------+-------------------------------+---------------------+ +| Booleans | ``bool`` | ``true``, ``false`` | ++---------------+-------------------------------+---------------------+ + +The types have widths as follows: + +- ``iN``, ``uN``, and ``fN`` are *N* bits wide, +- ``isize`` and ``usize`` are the width of a pointer, +- ``char`` is 32 bits wide, +- ``bool`` is 8 bits wide. + +.. raw:: html + +--------- +Details +--------- + +There are a few syntaxes which are not shown above: + +- All underscores in numbers can be left out, they are for legibility + only. So ``1_000`` can be written as ``1000`` (or ``10_00``), and + ``123_i64`` can be written as ``123i64``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst new file mode 100644 index 000000000..0aaea06f0 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst @@ -0,0 +1,45 @@ +============ +Arithmetic +============ + +------------ +Arithmetic +------------ + +.. code:: rust,editable + + fn interproduct(a: i32, b: i32, c: i32) -> i32 { + return a * b + b * c + c * a; + } + + fn main() { + println!("result: {}", interproduct(120, 100, 248)); + } + +.. raw:: html + +--------- +Details +--------- + +This is the first time we've seen a function other than ``main``, but +the meaning should be clear: it takes three integers, and returns an +integer. Functions will be covered in more detail later. + +Arithmetic is very similar to other languages, with similar precedence. + +What about integer overflow? In C and C++ overflow of *signed* integers +is actually undefined, and might do unknown things at runtime. In Rust, +it's defined. + +Change the ``i32``\ 's to ``i16`` to see an integer overflow, which +panics (checked) in a debug build and wraps in a release build. There +are other options, such as overflowing, saturating, and carrying. These +are accessed with method syntax, e.g., +``(a * b).saturating_add(b * c).saturating_add(c * a)``. + +In fact, the compiler will detect overflow of constant expressions, +which is why the example requires a separate function. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst b/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst new file mode 100644 index 000000000..01d20908c --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst @@ -0,0 +1,63 @@ +================ +Type Inference +================ + +---------------- +Type Inference +---------------- + +Rust will look at how the variable is *used* to determine the type: + +.. raw:: html + + + +.. code:: rust,editable + + fn takes_u32(x: u32) { + println!("u32: {x}"); + } + + fn takes_i8(y: i8) { + println!("i8: {y}"); + } + + fn main() { + let x = 10; + let y = 20; + + takes_u32(x); + takes_i8(y); + // takes_u32(y); + } + +.. raw:: html + +--------- +Details +--------- + +This slide demonstrates how the Rust compiler infers types based on +constraints given by variable declarations and usages. + +It is very important to emphasize that variables declared like this are +not of some sort of dynamic "any type" that can hold any data. The +machine code generated by such declaration is identical to the explicit +declaration of a type. The compiler does the job for us and helps us +write more concise code. + +When nothing constrains the type of an integer literal, Rust defaults to +``i32``. This sometimes appears as ``{integer}`` in error messages. +Similarly, floating-point literals default to ``f64``. + +.. code:: rust,compile_fail + + fn main() { + let x = 3.14; + let y = 20; + assert_eq!(x, y); + // ERROR: no implementation for `{float} == {integer}` + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst b/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst new file mode 100644 index 000000000..de6527d43 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst @@ -0,0 +1,28 @@ +===================== +Exercise: Fibonacci +===================== + +--------------------- +Exercise: Fibonacci +--------------------- + +The Fibonacci sequence begins with ``[0,1]``. For n>1, the n'th +Fibonacci number is calculated recursively as the sum of the n-1'th and +n-2'th Fibonacci numbers. + +Write a function ``fib(n)`` that calculates the n'th Fibonacci number. +When will this function panic? + +.. code:: rust,editable,should_panic + + {{#include exercise.rs:fib}} + if n < 2 { + // The base case. + return todo!("Implement this"); + } else { + // The recursive case. + return todo!("Implement this"); + } + } + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/040_control_flow_basics.rst b/courses/comprehensive_rust_training/040_control_flow_basics.rst new file mode 100644 index 000000000..bdbec3d2b --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics.rst @@ -0,0 +1,43 @@ +********************* +Control Flow Basics +********************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 040_control_flow_basics/01_if.rst +.. include:: 040_control_flow_basics/02_match.rst +.. include:: 040_control_flow_basics/03_loops.rst +.. include:: 040_control_flow_basics/04_break_continue.rst +.. include:: 040_control_flow_basics/05_blocks_and_scopes.rst +.. include:: 040_control_flow_basics/06_functions.rst +.. include:: 040_control_flow_basics/07_macros.rst +.. include:: 040_control_flow_basics/08_exercise.rst diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst b/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst new file mode 100644 index 000000000..3004700a6 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst @@ -0,0 +1,53 @@ +==================== +``if`` expressions +==================== + +-------------------- +``if`` expressions +-------------------- + +You use ```if`` +expressions `__ +exactly like ``if`` statements in other languages: + +.. code:: rust,editable + + fn main() { + let x = 10; + if x == 0 { + println!("zero!"); + } 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: + +.. code:: rust,editable + + fn main() { + let x = 10; + let size = if x < 20 { "small" } else { "large" }; + println!("number size: {}", size); + } + +.. raw:: html + +--------- +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. + +An ``if`` expression should be used in the same way as the other +expressions. For example, when it is used in a ``let`` statement, the +statement must be terminated with a ``;`` as well. Remove the ``;`` +before ``println!`` to see the compiler error. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst new file mode 100644 index 000000000..d6834e96c --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst @@ -0,0 +1,85 @@ +======================= +``match`` Expressions +======================= + +----------------------- +``match`` Expressions +----------------------- + +``match`` can be used to check a value against one or more options: + +.. code:: rust,editable + + fn main() { + let val = 1; + match val { + 1 => println!("one"), + 10 => println!("ten"), + 100 => println!("one hundred"), + _ => { + println!("something else"); + } + } + } + +Like ``if`` expressions, ``match`` can also return a value; + +.. code:: rust,editable + + fn main() { + let flag = true; + let val = match flag { + true => 1, + false => 0, + }; + println!("The value of {flag} is {val}"); + } + +.. raw:: html + +--------- +Details +--------- + +- ``match`` arms are evaluated from top to bottom, and the first one + that matches has its corresponding body executed. + +- There is no fall-through between cases the way that ``switch`` works + in other languages. + +- The body of a ``match`` arm can be a single expression or a block. + Technically this is the same thing, since blocks are also + expressions, but students may not fully understand that symmetry at + this point. + +- ``match`` expressions need to be exhaustive, meaning they either need + to cover all possible values or they need to have a default case such + as ``_``. Exhaustiveness is easiest to demonstrate with enums, but + enums haven't been introduced yet. Instead we demonstrate matching on + a ``bool``, which is the simplest primitive type. + +- This slide introduces ``match`` without talking about pattern + matching, giving students a chance to get familiar with the syntax + without front-loading too much information. We'll be talking about + pattern matching in more detail tomorrow, so try not to go into too + much detail here. + +----------------- +More to Explore +----------------- + +- To further motivate the usage of ``match``, you can compare the + examples to their equivalents written with ``if``. In the second case + matching on a ``bool`` an ``if {} else {}`` block is pretty similar. + But in the first example that checks multiple cases, a ``match`` + expression can be more concise than + ``if {} else if {} else if {} else``. + +- ``match`` also supports match guards, which allow you to add an + arbitrary logical condition that will get evaluated to determine if + the match arm should be taken. However talking about match guards + requires explaining about pattern matching, which we're trying to + avoid on this slide. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst b/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst new file mode 100644 index 000000000..ba3565d16 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst @@ -0,0 +1,29 @@ +======= +Loops +======= + +------- +Loops +------- + +There are three looping keywords in Rust: ``while``, ``loop``, and +``for``: + +----------- +``while`` +----------- + +The ```while`` +keyword `__ +works much like in other languages, executing the loop body as long as +the condition is true. + +.. code:: rust,editable + + fn main() { + let mut x = 200; + while x >= 10 { + x = x / 2; + } + println!("Final x: {x}"); + } diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst b/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst new file mode 100644 index 000000000..d03b4a65e --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst @@ -0,0 +1,45 @@ +============================ +``break`` and ``continue`` +============================ + +---------------------------- +``break`` and ``continue`` +---------------------------- + +If you want to immediately start the next iteration use +```continue`` `__. + +If you want to exit any kind of loop early, use +```break`` `__. +With ``loop``, this can take an optional expression that becomes the +value of the ``loop`` expression. + +.. code:: rust,editable + + fn main() { + let mut i = 0; + loop { + i += 1; + if i > 5 { + break; + } + if i % 2 == 0 { + continue; + } + println!("{}", i); + } + } + +.. raw:: html + +--------- +Details +--------- + +Note that ``loop`` is the only looping construct which can return a +non-trivial value. This is because it's guaranteed to only return at a +``break`` statement (unlike ``while`` and ``for`` loops, which can also +return when the condition fails). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/05_blocks_and_scopes.rst b/courses/comprehensive_rust_training/040_control_flow_basics/05_blocks_and_scopes.rst new file mode 100644 index 000000000..9fbc8024a --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/05_blocks_and_scopes.rst @@ -0,0 +1,43 @@ +=================== +Blocks and Scopes +=================== + +------------------- +Blocks and Scopes +------------------- + +-------- +Blocks +-------- + +A block in Rust contains a sequence of expressions, enclosed by braces +``{}``. Each block has a value and a type, which are those of the last +expression of the block: + +.. code:: 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 ``()``. + +.. raw:: html + +--------- +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``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst b/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst new file mode 100644 index 000000000..d4aee63f4 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst @@ -0,0 +1,51 @@ +=========== +Functions +=========== + +----------- +Functions +----------- + +.. raw:: html + + + +.. code:: 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)); + } + +.. raw:: html + +--------- +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 TBD 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. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst b/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst new file mode 100644 index 000000000..78954b234 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst @@ -0,0 +1,57 @@ +======== +Macros +======== + +-------- +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`` `__. +- ``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. + +.. code:: 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 = 4; + println!("{n}! = {}", factorial(n)); + } + +.. raw:: html + +--------- +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. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst b/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst new file mode 100644 index 000000000..a57a8f61b --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst @@ -0,0 +1,39 @@ +============================ +Exercise: Collatz Sequence +============================ + +---------------------------- +Exercise: Collatz Sequence +---------------------------- + +The `Collatz +Sequence `__ is +defined as follows, for an arbitrary n1 greater than zero: + +- If *ni* is 1, then the sequence terminates at *ni*. +- If *ni* is even, then *ni+1 = ni / 2*. +- If *ni* is odd, then *ni+1 = 3 \* ni + 1*. + +For example, beginning with *n1* = 3: + +- 3 is odd, so *n2* = 3 \* 3 + 1 = 10; +- 10 is even, so *n3* = 10 / 2 = 5; +- 5 is odd, so *n4* = 3 \* 5 + 1 = 16; +- 16 is even, so *n5* = 16 / 2 = 8; +- 8 is even, so *n6* = 8 / 2 = 4; +- 4 is even, so *n7* = 4 / 2 = 2; +- 2 is even, so *n8* = 1; and +- the sequence terminates. + +Write a function to calculate the length of the collatz sequence for a +given initial ``n``. + +.. code:: rust,editable,should_panic + + {{#include exercise.rs:collatz_length}} + todo!("Implement this") + } + + {{#include exercise.rs:tests}} + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays.rst new file mode 100644 index 000000000..b5b9e080e --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays.rst @@ -0,0 +1,40 @@ +******************* +Tuples And Arrays +******************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 050_tuples_and_arrays/01_arrays.rst +.. include:: 050_tuples_and_arrays/02_tuples.rst +.. include:: 050_tuples_and_arrays/03_iteration.rst +.. include:: 050_tuples_and_arrays/04_destructuring.rst +.. include:: 050_tuples_and_arrays/05_exercise.rst diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst new file mode 100644 index 000000000..4230596ef --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst @@ -0,0 +1,49 @@ +======== +Arrays +======== + +-------- +Arrays +-------- + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let mut a: [i8; 10] = [42; 10]; + a[5] = 0; + println!("a: {a:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- A value of the array type ``[T; N]`` holds ``N`` (a compile-time + constant) elements of the same type ``T``. Note that the length of + the array is *part of its type*, which means that ``[u8; 3]`` and + ``[u8; 4]`` are considered two different types. Slices, which have a + size determined at runtime, are covered later. + +- Try accessing an out-of-bounds array element. Array accesses are + checked at runtime. Rust can usually optimize these checks away, and + they can be avoided using unsafe Rust. + +- We can use literals to assign values to arrays. + +- The ``println!`` macro asks for the debug implementation with the + ``?`` format parameter: ``{}`` gives the default output, ``{:?}`` + gives the debug output. Types such as integers and strings implement + the default output, but arrays only implement the debug output. This + means that we must use debug output here. + +- Adding ``#``, eg ``{a:#?}``, invokes a "pretty printing" format, + which can be easier to read. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst new file mode 100644 index 000000000..e998ccc99 --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst @@ -0,0 +1,39 @@ +======== +Tuples +======== + +-------- +Tuples +-------- + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let t: (i8, bool) = (7, true); + println!("t.0: {}", t.0); + println!("t.1: {}", t.1); + } + +.. raw:: html + +--------- +Details +--------- + +- Like arrays, tuples have a fixed length. + +- Tuples group together values of different types into a compound type. + +- Fields of a tuple can be accessed by the period and the index of the + value, e.g. ``t.0``, ``t.1``. + +- The empty tuple ``()`` is referred to as the "unit type" and + signifies absence of a return value, akin to ``void`` in other + languages. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst new file mode 100644 index 000000000..761da0eb5 --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst @@ -0,0 +1,36 @@ +================= +Array Iteration +================= + +----------------- +Array Iteration +----------------- + +The ``for`` statement supports iterating over arrays (but not tuples). + +.. code:: rust,editable + + fn main() { + let primes = [2, 3, 5, 7, 11, 13, 17, 19]; + for prime in primes { + for i in 2..prime { + assert_ne!(prime % i, 0); + } + } + } + +.. raw:: html + +--------- +Details +--------- + +This functionality uses the ``IntoIterator`` trait, but we haven't +covered that yet. + +The ``assert_ne!`` macro is new here. There are also ``assert_eq!`` and +``assert!`` macros. These are always checked, while debug-only variants +like ``debug_assert!`` compile to nothing in release builds. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst new file mode 100644 index 000000000..a670c7525 --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst @@ -0,0 +1,50 @@ +============================ +Patterns and Destructuring +============================ + +---------------------------- +Patterns and Destructuring +---------------------------- + +When working with tuples and other structured values it's common to want +to extract the inner values into local variables. This can be done +manually by directly accessing the inner values: + +.. code:: rust,editable + + fn print_tuple(tuple: (i32, i32)) { + let left = tuple.0; + let right = tuple.1; + println!("left: {left}, right: {right}"); + } + +However, Rust also supports using pattern matching to destructure a +larger value into its constituent parts: + +.. code:: rust,editable + + fn print_tuple(tuple: (i32, i32)) { + let (left, right) = tuple; + println!("left: {left}, right: {right}"); + } + +.. raw:: html + +--------- +Details +--------- + +- The patterns used here are "irrefutable", meaning that the compiler + can statically verify that the value on the right of ``=`` has the + same structure as the pattern. +- A variable name is an irrefutable pattern that always matches any + value, hence why we can also use ``let`` to declare a single + variable. +- Rust also supports using patterns in conditionals, allowing for + equality comparison and destructuring to happen at the same time. + This form of pattern matching will be discussed in more detail later. +- Edit the examples above to show the compiler error when the pattern + doesn't match the value being matched on. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst new file mode 100644 index 000000000..e9f7244ce --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst @@ -0,0 +1,44 @@ +========================= +Exercise: Nested Arrays +========================= + +------------------------- +Exercise: Nested Arrays +------------------------- + +Arrays can contain other arrays: + +.. code:: rust + + let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; + +What is the type of this variable? + +Use an array such as the above to write a function ``transpose`` which +will transpose a matrix (turn rows into columns): + +.. raw:: html + + + +.. code:: bob + + TBDTBD1 2 3TBDTBD TBD1 4 7TBD + "transpose"TBDTBD4 5 6TBDTBD "=="TBD2 5 8TBD + TBDTBD7 8 9TBDTBD TBD3 6 9TBD + +Copy the code below to https://play.rust-lang.org/ and implement the +function. This function only operates on 3x3 matrices. + +.. code:: rust,should_panic + + // TODO: remove this when you're done with your implementation. + #![allow(unused_variables, dead_code)] + + {{#include exercise.rs:transpose}} + unimplemented!() + } + + {{#include exercise.rs:tests}} + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/060_references.rst b/courses/comprehensive_rust_training/060_references.rst new file mode 100644 index 000000000..3c7fba0c4 --- /dev/null +++ b/courses/comprehensive_rust_training/060_references.rst @@ -0,0 +1,41 @@ +************ +References +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 060_references/01_shared.rst +.. include:: 060_references/02_exclusive.rst +.. include:: 060_references/03_slices.rst +.. include:: 060_references/04_strings.rst +.. include:: 060_references/05_dangling.rst +.. include:: 060_references/06_exercise.rst diff --git a/courses/comprehensive_rust_training/060_references/01_shared.rst b/courses/comprehensive_rust_training/060_references/01_shared.rst new file mode 100644 index 000000000..a6274aa2c --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/01_shared.rst @@ -0,0 +1,75 @@ +=================== +Shared References +=================== + +------------------- +Shared References +------------------- + +A reference provides a way to access another value without taking +ownership of the value, and is also called "borrowing". Shared +references are read-only, and the referenced data cannot change. + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let a = 'A'; + let b = 'B'; + let mut r: &char = &a; + println!("r: {}", *r); + r = &b; + println!("r: {}", *r); + } + +A shared reference to a type ``T`` has type ``&T``. A reference value is +made with the ``&`` operator. The ``*`` operator "dereferences" a +reference, yielding its value. + +.. raw:: html + +--------- +Details +--------- + +- References can never be null in Rust, so null checking is not + necessary. + +- A reference is said to "borrow" the value it refers to, and this is a + good model for students not familiar with pointers: code can use the + reference to access the value, but is still "owned" by the original + variable. The course will get into more detail on ownership in day 3. + +- References are implemented as pointers, and a key advantage is that + they can be much smaller than the thing they point to. Students + familiar with C or C++ will recognize references as pointers. Later + parts of the course will cover how Rust prevents the memory-safety + bugs that come from using raw pointers. + +- Rust does not automatically create references for you - the ``&`` is + always required. + +- Rust will auto-dereference in some cases, in particular when invoking + methods (try ``r.is_ascii()``). There is no need for an ``->`` + operator like in C++. + +- In this example, ``r`` is mutable so that it can be reassigned + (``r = &b``). Note that this re-binds ``r``, so that it refers to + something else. This is different from C++, where assignment to a + reference changes the referenced value. + +- A shared reference does not allow modifying the value it refers to, + even if that value was mutable. Try ``*r = 'X'``. + +- Rust is tracking the lifetimes of all references to ensure they live + long enough. Dangling references cannot occur in safe Rust. + ``x_axis`` would return a reference to ``point``, but ``point`` will + be deallocated when the function returns, so this will not compile. + +- We will talk more about borrowing when we get to ownership. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/02_exclusive.rst b/courses/comprehensive_rust_training/060_references/02_exclusive.rst new file mode 100644 index 000000000..b9ec44233 --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/02_exclusive.rst @@ -0,0 +1,45 @@ +====================== +Exclusive References +====================== + +---------------------- +Exclusive References +---------------------- + +Exclusive references, also known as mutable references, allow changing +the value they refer to. They have type ``&mut T``. + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let mut point = (1, 2); + let x_coord = &mut point.0; + *x_coord = 20; + println!("point: {point:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +Key points: + +- "Exclusive" means that only this reference can be used to access the + value. No other references (shared or exclusive) can exist at the + same time, and the referenced value cannot be accessed while the + exclusive reference exists. Try making an ``&point.0`` or changing + ``point.0`` while ``x_coord`` is alive. + +- Be sure to note the difference between ``let mut x_coord: &i32`` and + ``let x_coord: &mut i32``. The first one represents a shared + reference which can be bound to different values, while the second + represents an exclusive reference to a mutable value. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/03_slices.rst b/courses/comprehensive_rust_training/060_references/03_slices.rst new file mode 100644 index 000000000..046fe9e54 --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/03_slices.rst @@ -0,0 +1,55 @@ +======== +Slices +======== + +-------- +Slices +-------- + +A slice gives you a view into a larger collection: + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let a: [i32; 6] = [10, 20, 30, 40, 50, 60]; + println!("a: {a:?}"); + + let s: &[i32] = &a[2..4]; + + println!("s: {s:?}"); + } + +- Slices borrow data from the sliced type. + +.. raw:: html + +--------- +Details +--------- + +- We create a slice by borrowing ``a`` and specifying the starting and + ending indexes in brackets. + +- If the slice starts at index 0, Rust's range syntax allows us to drop + the starting index, meaning that ``&a[0..a.len()]`` and + ``&a[..a.len()]`` are identical. + +- The same is true for the last index, so ``&a[2..a.len()]`` and + ``&a[2..]`` are identical. + +- To easily create a slice of the full array, we can therefore use + ``&a[..]``. + +- ``s`` is a reference to a slice of ``i32``\ s. Notice that the type + of ``s`` (``&[i32]``) no longer mentions the array length. This + allows us to perform computation on slices of different sizes. + +- Slices always borrow from another object. In this example, ``a`` has + to remain 'alive' (in scope) for at least as long as our slice. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/04_strings.rst b/courses/comprehensive_rust_training/060_references/04_strings.rst new file mode 100644 index 000000000..fa2a77c28 --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/04_strings.rst @@ -0,0 +1,104 @@ +.. raw:: html + + + +========= +Strings +========= + +--------- +Strings +--------- + +We can now understand the two string types in Rust: + +- ``&str`` is a slice of UTF-8 encoded bytes, similar to ``&[u8]``. +- ``String`` is an owned buffer of UTF-8 encoded bytes, similar to + ``Vec``. + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let s1: &str = "World"; + println!("s1: {s1}"); + + let mut s2: String = String::from("Hello "); + println!("s2: {s2}"); + s2.push_str(s1); + println!("s2: {s2}"); + + let s3: &str = &s2[s2.len() - s1.len()..]; + println!("s3: {s3}"); + } + +.. raw:: html + +--------- +Details +--------- + +- ``&str`` introduces a string slice, which is an immutable reference + to UTF-8 encoded string data stored in a block of memory. String + literals (``"Hello"``), are stored in the program's binary. + +- Rust's ``String`` type is a wrapper around a vector of bytes. As with + a ``Vec``, it is owned. + +- As with many other types ``String::from()`` creates a string from a + string literal; ``String::new()`` creates a new empty string, to + which string data can be added using the ``push()`` and + ``push_str()`` methods. + +- The ``format!()`` macro is a convenient way to generate an owned + string from dynamic values. It accepts the same format specification + as ``println!()``. + +- You can borrow ``&str`` slices from ``String`` via ``&`` and + optionally range selection. If you select a byte range that is not + aligned to character boundaries, the expression will panic. The + ``chars`` iterator iterates over characters and is preferred over + trying to get character boundaries right. + +- For C++ programmers: think of ``&str`` as ``std::string_view`` from + C++, but the one that always points to a valid string in memory. Rust + ``String`` is a rough equivalent of ``std::string`` from C++ (main + difference: it can only contain UTF-8 encoded bytes and will never + use a small-string optimization). + +- Byte strings literals allow you to create a ``&[u8]`` value directly: + + .. raw:: html + + + + .. code:: rust,editable + + fn main() { + println!("{:?}", b"abc"); + println!("{:?}", &[97, 98, 99]); + } + +- Raw strings allow you to create a ``&str`` value with escapes + disabled: ``r"\n" == "\\n"``. You can embed double-quotes by using an + equal amount of ``#`` on either side of the quotes: + + .. raw:: html + + + + .. code:: rust,editable + + fn main() { + println!(r#"link"#); + println!("link"); + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/05_dangling.rst b/courses/comprehensive_rust_training/060_references/05_dangling.rst new file mode 100644 index 000000000..1e32cc8fa --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/05_dangling.rst @@ -0,0 +1,51 @@ +==================== +Reference Validity +==================== + +-------------------- +Reference Validity +-------------------- + +Rust enforces a number of rules for references that make them always +safe to use. One rule is that references can never be ``null``, making +them safe to use without ``null`` checks. The other rule we'll look at +for now is that references can't *outlive* the data they point to. + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + fn main() { + let x_ref = { + let x = 10; + &x + }; + println!("x: {x_ref}"); + } + +.. raw:: html + +--------- +Details +--------- + +- This slide gets students thinking about references as not simply + being pointers, since Rust has different rules for references than + other languages. + +- We'll look at the rest of Rust's borrowing rules on day 3 when we + talk about Rust's ownership system. + +----------------- +More to Explore +----------------- + +- Rust's equivalent of nullability is the ``Option`` type, which can be + used to make any type "nullable" (not just references/pointers). We + haven't yet introduced enums or pattern matching, though, so try not + to go into too much detail about this here. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/06_exercise.rst b/courses/comprehensive_rust_training/060_references/06_exercise.rst new file mode 100644 index 000000000..927816f8d --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/06_exercise.rst @@ -0,0 +1,34 @@ +==================== +Exercise: Geometry +==================== + +-------------------- +Exercise: Geometry +-------------------- + +We will create a few utility functions for 3-dimensional geometry, +representing a point as ``[f64;3]``. It is up to you to determine the +function signatures. + +.. code:: rust,compile_fail + + // Calculate the magnitude of a vector by summing the squares of its coordinates + // and taking the square root. Use the `sqrt()` method to calculate the square + // root, like `v.sqrt()`. + + {{#include exercise.rs:magnitude}} + fn magnitude(...) -> f64 { + todo!() + } + + // Normalize a vector by calculating its magnitude and dividing all of its + // coordinates by that magnitude. + + {{#include exercise.rs:normalize}} + fn normalize(...) { + todo!() + } + + // Use the following `main` to test your work. + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/070_user_defined_types.rst b/courses/comprehensive_rust_training/070_user_defined_types.rst new file mode 100644 index 000000000..95e6d4573 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types.rst @@ -0,0 +1,42 @@ +******************** +User Defined Types +******************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 070_user_defined_types/01_named_structs.rst +.. include:: 070_user_defined_types/02_tuple_structs.rst +.. include:: 070_user_defined_types/03_enums.rst +.. include:: 070_user_defined_types/04_aliases.rst +.. include:: 070_user_defined_types/05_const.rst +.. include:: 070_user_defined_types/06_static.rst +.. include:: 070_user_defined_types/07_exercise.rst diff --git a/courses/comprehensive_rust_training/070_user_defined_types/01_named_structs.rst b/courses/comprehensive_rust_training/070_user_defined_types/01_named_structs.rst new file mode 100644 index 000000000..9c9ea7464 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/01_named_structs.rst @@ -0,0 +1,68 @@ +=============== +Named Structs +=============== + +--------------- +Named Structs +--------------- + +Like C and C++, Rust has support for custom structs: + +.. code:: rust,editable + + struct Person { + name: String, + age: u8, + } + + fn describe(person: &Person) { + println!("{} is {} years old", person.name, person.age); + } + + fn main() { + let mut peter = Person { name: String::from("Peter"), age: 27 }; + describe(&peter); + + peter.age = 28; + describe(&peter); + + let name = String::from("Avery"); + let age = 39; + let avery = Person { name, age }; + describe(&avery); + + let jackie = Person { name: String::from("Jackie"), ..avery }; + describe(&jackie); + } + +.. raw:: html + +--------- +Details +--------- + +Key Points: + +- Structs work like in C or C++. + + - Like in C++, and unlike in C, no typedef is needed to define a + type. + - Unlike in C++, there is no inheritance between structs. + +- This may be a good time to let people know there are different types + of structs. + + - Zero-sized structs (e.g. ``struct Foo;``) might be used when + implementing a trait on some type but don't have any data that you + want to store in the value itself. + - The next slide will introduce Tuple structs, used when the field + names are not important. + +- If you already have variables with the right names, then you can + create the struct using a shorthand. +- The syntax ``..avery`` allows us to copy the majority of the fields + from the old struct without having to explicitly type it all out. It + must always be the last element. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst b/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst new file mode 100644 index 000000000..dfcb160cf --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst @@ -0,0 +1,73 @@ +.. raw:: html + + + +=============== +Tuple Structs +=============== + +--------------- +Tuple Structs +--------------- + +If the field names are unimportant, you can use a tuple struct: + +.. code:: rust,editable + + struct Point(i32, i32); + + fn main() { + let p = Point(17, 23); + println!("({}, {})", p.0, p.1); + } + +This is often used for single-field wrappers (called newtypes): + +.. code:: rust,editable,compile_fail + + struct PoundsOfForce(f64); + struct Newtons(f64); + + fn compute_thruster_force() -> PoundsOfForce { + todo!("Ask a rocket scientist at NASA") + } + + fn set_thruster_force(force: Newtons) { + // ... + } + + fn main() { + let force = compute_thruster_force(); + set_thruster_force(force); + } + +.. raw:: html + +--------- +Details +--------- + +- Newtypes are a great way to encode additional information about the + value in a primitive type, for example: + + - The number is measured in some units: ``Newtons`` in the example + above. + - The value passed some validation when it was created, so you no + longer have to validate it again at every use: + ``PhoneNumber(String)`` or ``OddNumber(u32)``. + +- Demonstrate how to add a ``f64`` value to a ``Newtons`` type by + accessing the single field in the newtype. + + - Rust generally doesn't like inexplicit things, like automatic + unwrapping or for instance using booleans as integers. + - Operator overloading is discussed on Day 3 (generics). + +- The example is a subtle reference to the `Mars Climate + Orbiter `__ + failure. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst b/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst new file mode 100644 index 000000000..5aecc6ac8 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst @@ -0,0 +1,142 @@ +======= +Enums +======= + +------- +Enums +------- + +The ``enum`` keyword allows the creation of a type which has a few +different variants: + +.. code:: rust,editable + + #[derive(Debug)] + enum Direction { + Left, + Right, + } + + #[derive(Debug)] + enum PlayerMove { + Pass, // Simple variant + Run(Direction), // Tuple variant + Teleport { x: u32, y: u32 }, // Struct variant + } + + fn main() { + let player_move: PlayerMove = PlayerMove::Run(Direction::Left); + println!("On this turn: {player_move:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +Key Points: + +- Enumerations allow you to collect a set of values under one type. +- ``Direction`` is a type with variants. There are two values of + ``Direction``: ``Direction::Left`` and ``Direction::Right``. +- ``PlayerMove`` is a type with three variants. In addition to the + payloads, Rust will store a discriminant so that it knows at runtime + which variant is in a ``PlayerMove`` value. +- 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). + - 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. + +- Rust uses minimal space to store the discriminant. + + - If necessary, it stores an integer of the smallest required size + + - If the allowed variant values do not cover all bit patterns, it + will use invalid bit patterns to encode the discriminant (the + "niche optimization"). For example, ``Option<&u8>`` stores either + a pointer to an integer or ``NULL`` for the ``None`` variant. + + - You can control the discriminant if needed (e.g., for + compatibility with C): + + .. raw:: html + + + + .. code:: rust,editable + + #[repr(u32)] + enum Bar { + A, // 0 + B = 10000, + C, // 10001 + } + + fn main() { + println!("A: {}", Bar::A as u32); + println!("B: {}", Bar::B as u32); + println!("C: {}", Bar::C as u32); + } + + Without ``repr``, the discriminant type takes 2 bytes, because + 10001 fits 2 bytes. + +----------------- +More to Explore +----------------- + +Rust has several optimizations it can employ to make enums take up less +space. + +- Null pointer optimization: For `some + types `__, Rust + guarantees that ``size_of::()`` equals ``size_of::>()``. + + Example code if you want to show how the bitwise representation *may* + look like in practice. It's important to note that the compiler + provides no guarantees regarding this representation, therefore this + is totally unsafe. + + .. raw:: html + + + + .. code:: rust,editable + + use std::mem::transmute; + + macro_rules! dbg_bits { + ($e:expr, $bit_type:ty) => { + println!("- {}: {:#x}", stringify!($e), transmute::<_, $bit_type>($e)); + }; + } + + fn main() { + unsafe { + println!("bool:"); + dbg_bits!(false, u8); + dbg_bits!(true, u8); + + println!("Option:"); + dbg_bits!(None::, u8); + dbg_bits!(Some(false), u8); + dbg_bits!(Some(true), u8); + + println!("Option>:"); + dbg_bits!(Some(Some(false)), u8); + dbg_bits!(Some(Some(true)), u8); + dbg_bits!(Some(None::), u8); + dbg_bits!(None::>, u8); + + println!("Option<&i32>:"); + dbg_bits!(None::<&i32>, usize); + dbg_bits!(Some(&0i32), usize); + } + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst b/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst new file mode 100644 index 000000000..75752ebca --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst @@ -0,0 +1,39 @@ +============== +Type Aliases +============== + +-------------- +Type Aliases +-------------- + +A type alias creates a name for another type. The two types can be used +interchangeably. + +.. code:: rust,editable + + enum CarryableConcreteItem { + Left, + Right, + } + + type Item = CarryableConcreteItem; + + // Aliases are more useful with long, complex types: + use std::cell::RefCell; + use std::sync::{Arc, RwLock}; + type PlayerInventory = RwLock>>>; + +.. raw:: html + +--------- +Details +--------- + +- A `newtype `__ is often a better alternative + since it creates a distinct type. Prefer + ``struct InventoryCount(usize)`` to ``type InventoryCount = usize``. + +- C programmers will recognize this as similar to a ``typedef``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst b/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst new file mode 100644 index 000000000..d206b53b7 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst @@ -0,0 +1,60 @@ +=========== +``const`` +=========== + +----------- +``const`` +----------- + +Constants are evaluated at compile time and their values are inlined +wherever they are used: + +.. raw:: html + + + +.. code:: rust,editable + + const DIGEST_SIZE: usize = 3; + const FILL_VALUE: u8 = calculate_fill_value(); + + const fn calculate_fill_value() -> u8 { + if DIGEST_SIZE < 10 { + 42 + } else { + 13 + } + } + + fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] { + let mut digest = [FILL_VALUE; DIGEST_SIZE]; + for (idx, &b) in text.as_bytes().iter().enumerate() { + digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b); + } + digest + } + + fn main() { + let digest = compute_digest("Hello"); + println!("digest: {digest:?}"); + } + +According to the `Rust RFC +Book `__ +these are inlined upon use. + +Only functions marked ``const`` can be called at compile time to +generate ``const`` values. ``const`` functions can however be called at +runtime. + +.. raw:: html + +--------- +Details +--------- + +- Mention that ``const`` behaves semantically similar to C++'s + ``constexpr`` + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst b/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst new file mode 100644 index 000000000..34f25cd55 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst @@ -0,0 +1,58 @@ +============ +``static`` +============ + +------------ +``static`` +------------ + +Static variables will live during the whole execution of the program, +and therefore will not move: + +.. code:: rust,editable + + static BANNER: &str = "Welcome to RustOS 3.14"; + + fn main() { + println!("{BANNER}"); + } + +As noted in the `Rust RFC +Book `__, +these are not inlined upon use and have an actual associated memory +location. This is useful for unsafe and embedded code, and the variable +lives through the entirety of the program execution. When a +globally-scoped value does not have a reason to need object identity, +``const`` is generally preferred. + +.. raw:: html + +--------- +Details +--------- + +- ``static`` is similar to mutable global variables in C++. +- ``static`` provides object identity: an address in memory and state + as required by types with interior mutability such as ``Mutex``. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +Because ``static`` variables are accessible from any thread, they must +be ``Sync``. Interior mutability is possible through a +```Mutex`` `__, +atomic or similar. + +It is common to use ``OnceLock`` in a static as a way to support +initialization on first use. ``OnceCell`` is not ``Sync`` and thus +cannot be used in this context. + +Thread-local data can be created with the macro ``std::thread_local``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst b/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst new file mode 100644 index 000000000..d89c8d97e --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst @@ -0,0 +1,46 @@ +=========================== +Exercise: Elevator Events +=========================== + +--------------------------- +Exercise: Elevator Events +--------------------------- + +We will create a data structure to represent an event in an elevator +control system. It is up to you to define the types and functions to +construct various events. Use ``#[derive(Debug)]`` to allow the types to +be formatted with ``{:?}``. + +This exercise only requires creating and populating data structures so +that ``main`` runs without errors. The next part of the course will +cover getting data out of these structures. + +.. code:: rust,editable,should_panic + + {{#include exercise.rs:event}} + // TODO: add required variants + } + + {{#include exercise.rs:direction}} + + {{#include exercise.rs:car_arrived}} + todo!() + } + + {{#include exercise.rs:car_door_opened}} + todo!() + } + + {{#include exercise.rs:car_door_closed}} + todo!() + } + + {{#include exercise.rs:lobby_call_button_pressed}} + todo!() + } + + {{#include exercise.rs:car_floor_button_pressed}} + todo!() + } + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/080_pattern_matching.rst b/courses/comprehensive_rust_training/080_pattern_matching.rst new file mode 100644 index 000000000..e8b85a6ff --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching.rst @@ -0,0 +1,40 @@ +****************** +Pattern Matching +****************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 080_pattern_matching/01_match.rst +.. include:: 080_pattern_matching/02_destructuring_structs.rst +.. include:: 080_pattern_matching/03_destructuring_enums.rst +.. include:: 080_pattern_matching/04_let_control_flow.rst +.. include:: 080_pattern_matching/05_exercise.rst diff --git a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst new file mode 100644 index 000000000..a15013aeb --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst @@ -0,0 +1,90 @@ +================= +Matching Values +================= + +----------------- +Matching Values +----------------- + +The ``match`` keyword lets you match a value against one or more +*patterns*. The patterns can be simple values, similarly to ``switch`` +in C and C++, but they can also be used to express more complex +conditions: + +.. code:: rust,editable + + #[rustfmt::skip] + fn main() { + let input = 'x'; + match input { + 'q' => println!("Quitting"), + 'a' | 's' | 'w' | 'd' => println!("Moving around"), + '0'..='9' => println!("Number input"), + key if key.is_lowercase() => println!("Lowercase: {key}"), + _ => println!("Something else"), + } + } + +A variable in the pattern (``key`` in this example) will create a +binding that can be used within the match arm. We will learn more about +this on the next slide. + +A match guard causes the arm to match only if the condition is true. If +the condition is false the match will continue checking later cases. + +.. raw:: html + +--------- +Details +--------- + +Key Points: + +- You might point out how some specific characters are being used when + in a pattern + + - ``|`` as an ``or`` + - ``..`` can expand as much as it needs to be + - ``1..=5`` represents an inclusive range + - ``_`` is a wild card + +- Match guards as a separate syntax feature are important and necessary + when we wish to concisely express more complex ideas than patterns + alone would allow. +- They are not the same as separate ``if`` expression inside of the + match arm. An ``if`` expression inside of the branch block (after + ``=>``) happens after the match arm is selected. Failing the ``if`` + condition inside of that block won't result in other arms of the + original ``match`` expression being considered. +- The condition defined in the guard applies to every expression in a + pattern with an ``|``. + +================= +More To Explore +================= + +----------------- +More To Explore +----------------- + +- Another piece of pattern syntax you can show students is the ``@`` + syntax which binds a part of a pattern to a variable. For example: + + .. code:: rust + + let opt = Some(123); + match opt { + outer @ Some(inner) => { + println!("outer: {outer:?}, inner: {inner}"); + } + None => {} + } + + In this example ``inner`` has the value 123 which it pulled from the + ``Option`` via destructuring, ``outer`` captures the entire + ``Some(inner)`` expression, so it contains the full + ``Option::Some(123)``. This is rarely used but can be useful in more + complex patterns. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst b/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst new file mode 100644 index 000000000..f6f3f7e2c --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst @@ -0,0 +1,30 @@ +========= +Structs +========= + +--------- +Structs +--------- + +Like tuples, Struct can also be destructured by matching: + +.. code:: rust,editable + + {{#include ../../third_party/rust-by-example/destructuring-structs.rs}} + +.. raw:: html + +--------- +Details +--------- + +- Change the literal values in ``foo`` to match with the other + patterns. +- Add a new field to ``Foo`` and make changes to the pattern as needed. +- The distinction between a capture and a constant expression can be + hard to spot. Try changing the ``2`` in the second arm to a variable, + and see that it subtly doesn't work. Change it to a ``const`` and see + it working again. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst b/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst new file mode 100644 index 000000000..cc974fec6 --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst @@ -0,0 +1,61 @@ +======= +Enums +======= + +------- +Enums +------- + +Like tuples, enums can also be destructured by matching: + +Patterns can also be used to bind variables to parts of your values. +This is how you inspect the structure of your types. Let us start with a +simple ``enum`` type: + +.. code:: rust,editable + + enum Result { + Ok(i32), + Err(String), + } + + fn divide_in_two(n: i32) -> Result { + if n % 2 == 0 { + Result::Ok(n / 2) + } else { + Result::Err(format!("cannot divide {n} into two equal parts")) + } + } + + fn main() { + let n = 100; + match divide_in_two(n) { + Result::Ok(half) => println!("{n} divided in two is {half}"), + Result::Err(msg) => println!("sorry, an error happened: {msg}"), + } + } + +Here we have used the arms to *destructure* the ``Result`` value. In the +first arm, ``half`` is bound to the value inside the ``Ok`` variant. In +the second arm, ``msg`` is bound to the error message. + +.. raw:: html + +--------- +Details +--------- + +- The ``if``/``else`` expression is returning an enum that is later + unpacked with a ``match``. +- You can try adding a third variant to the enum definition and + displaying the errors when running the code. Point out the places + where your code is now inexhaustive and how the compiler tries to + give you hints. +- The values in the enum variants can only be accessed after being + pattern matched. +- Demonstrate what happens when the search is inexhaustive. Note the + advantage the Rust compiler provides by confirming when all cases are + handled. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst b/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst new file mode 100644 index 000000000..94df9e26e --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst @@ -0,0 +1,167 @@ +================== +Let Control Flow +================== + +------------------ +Let Control Flow +------------------ + +Rust has a few control flow constructs which differ from other +languages. They are used for pattern matching: + +- ``if let`` expressions +- ``let else`` expressions +- ``while let`` expressions + +======================== +``if let`` expressions +======================== + +------------------------ +``if let`` expressions +------------------------ + +The ```if let`` +expression `__ +lets you execute different code depending on whether a value matches a +pattern: + +.. code:: rust,editable + + use std::time::Duration; + + fn sleep_for(secs: f32) { + if let Ok(duration) = Duration::try_from_secs_f32(secs) { + std::thread::sleep(duration); + println!("slept for {duration:?}"); + } + } + + fn main() { + sleep_for(-10.0); + sleep_for(0.8); + } + +========================== +``let else`` expressions +========================== + +-------------------------- +``let else`` expressions +-------------------------- + +For the common case of matching a pattern and returning from the +function, use +```let else`` `__. +The "else" case must diverge (``return``, ``break``, or panic - anything +but falling off the end of the block). + +.. code:: rust,editable + + fn hex_or_die_trying(maybe_string: Option) -> Result { + // TODO: The structure of this code is difficult to follow -- rewrite it with let-else! + if let Some(s) = maybe_string { + if let Some(first_byte_char) = s.chars().next() { + if let Some(digit) = first_byte_char.to_digit(16) { + Ok(digit) + } else { + return Err(String::from("not a hex digit")); + } + } else { + return Err(String::from("got empty string")); + } + } else { + return Err(String::from("got None")); + } + } + + fn main() { + println!("result: {:?}", hex_or_die_trying(Some(String::from("foo")))); + } + +Like with ``if let``, there is a +```while let`` `__ +variant which repeatedly tests a value against a pattern: + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let mut name = String::from("Comprehensive Rust TBD"); + while let Some(c) = name.pop() { + println!("character: {c}"); + } + // (There are more efficient ways to reverse a string!) + } + +Here +```String::pop`` `__ +returns ``Some(c)`` until the string is empty, after which it will +return ``None``. The ``while let`` lets us keep iterating through all +items. + +.. raw:: html + +--------- +Details +--------- + +-------- +if-let +-------- + +- 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. + +---------- +let-else +---------- + +``if-let``\ s can pile up, as shown. The ``let-else`` construct supports +flattening this nested code. Rewrite the awkward version for students, +so they can see the transformation. + +The rewritten version is: + +.. code:: rust + + fn hex_or_die_trying(maybe_string: Option) -> Result { + let Some(s) = maybe_string else { + return Err(String::from("got None")); + }; + + let Some(first_byte_char) = s.chars().next() else { + return Err(String::from("got empty string")); + }; + + let Some(digit) = first_byte_char.to_digit(16) else { + return Err(String::from("not a hex digit")); + }; + + return Ok(digit); + } + +=========== +while-let +=========== + +----------- +while-let +----------- + +- 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 + ``name.pop()``. The ``while let`` provides syntactic sugar for the + above scenario. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst new file mode 100644 index 000000000..c27dc8729 --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst @@ -0,0 +1,88 @@ +================================= +Exercise: Expression Evaluation +================================= + +--------------------------------- +Exercise: Expression Evaluation +--------------------------------- + +Let's write a simple recursive evaluator for arithmetic expressions. + +An example of a small arithmetic expression could be ``10 + 20``, which +evaluates to ``30``. We can represent the expression as a tree: + +.. raw:: html + + + +.. code:: bob + + .-------. + .------ | + | ------. + | '-------' | + v v + .--------. .--------. + | 10 | | 20 | + '--------' '--------' + +A bigger and more complex expression would be +``(10 * 9) + ((3 - 4) * 5)``, which evaluate to ``85``. We represent +this as a much bigger tree: + +.. raw:: html + + + +.. code:: bob + + .-----. + .---------------- | + | ----------------. + | '-----' | + v v + .-----. .-----. + .---- | * | ----. .---- | * | ----. + | '-----' | | '-----' | + v v v v + .------. .-----. .-----. .-----. + | 10 | | 9 | .---- | "-"| ----. | 5 | + '------' '-----' | '-----' | '-----' + v v + .-----. .-----. + | 3 | | 4 | + '-----' '-----' + +In code, we will represent the tree with two types: + +.. code:: rust + + {{#include exercise.rs:Operation}} + + {{#include exercise.rs:Expression}} + +The ``Box`` type here is a smart pointer, and will be covered in detail +later in the course. An expression can be "boxed" with ``Box::new`` as +seen in the tests. To evaluate a boxed expression, use the deref +operator (``*``) to "unbox" it: ``eval(*boxed_expr)``. + +Copy and paste the code into the Rust playground, and begin implementing +``eval``. The final product should pass the tests. It may be helpful to +use ``todo!()`` and get the tests to pass one-by-one. You can also skip +a test temporarily with ``#[ignore]``: + +.. code:: none + + #[test] + #[ignore] + fn test_value() { .. } + +.. code:: rust + + {{#include exercise.rs:Operation}} + + {{#include exercise.rs:Expression}} + + {{#include exercise.rs:eval}} + todo!() + } + + {{#include exercise.rs:tests}} diff --git a/courses/comprehensive_rust_training/090_methods_and_traits.rst b/courses/comprehensive_rust_training/090_methods_and_traits.rst new file mode 100644 index 000000000..f2e751b80 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits.rst @@ -0,0 +1,39 @@ +******************** +Methods And Traits +******************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 090_methods_and_traits/01_methods.rst +.. include:: 090_methods_and_traits/02_traits.rst +.. include:: 090_methods_and_traits/03_deriving.rst +.. include:: 090_methods_and_traits/04_exercise.rst diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst b/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst new file mode 100644 index 000000000..263946375 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst @@ -0,0 +1,106 @@ +========= +Methods +========= + +--------- +Methods +--------- + +Rust allows you to associate functions with your new types. You do this +with an ``impl`` block: + +.. code:: rust,editable + + #[derive(Debug)] + struct CarRace { + name: String, + laps: Vec, + } + + impl CarRace { + // No receiver, a static method + fn new(name: &str) -> Self { + Self { name: String::from(name), laps: Vec::new() } + } + + // Exclusive borrowed read-write access to self + fn add_lap(&mut self, lap: i32) { + self.laps.push(lap); + } + + // Shared and read-only borrowed access to self + fn print_laps(&self) { + println!("Recorded {} laps for {}:", self.laps.len(), self.name); + for (idx, lap) in self.laps.iter().enumerate() { + println!("Lap {idx}: {lap} sec"); + } + } + + // Exclusive ownership of self (covered later) + fn finish(self) { + let total: i32 = self.laps.iter().sum(); + println!("Race {} is finished, total lap time: {}", self.name, total); + } + } + + fn main() { + let mut race = CarRace::new("Monaco Grand Prix"); + race.add_lap(70); + race.add_lap(68); + race.print_laps(); + race.add_lap(71); + race.print_laps(); + race.finish(); + // race.add_lap(42); + } + +The ``self`` arguments specify the "receiver" - the object the method +acts on. There are several common receivers for a method: + +- ``&self``: borrows the object from the caller using a shared and + immutable reference. The object can be used again afterwards. +- ``&mut self``: borrows the object from the caller using a unique and + mutable reference. The object can be used again afterwards. +- ``self``: takes ownership of the object and moves it away from the + caller. The method becomes the owner of the object. The object will + be dropped (deallocated) when the method returns, unless its + ownership is explicitly transmitted. Complete ownership does not + automatically mean mutability. +- ``mut self``: same as above, but the method can mutate the object. +- No receiver: this becomes a static method on the struct. Typically + used to create constructors which are called ``new`` by convention. + +.. raw:: html + +--------- +Details +--------- + +Key Points: + +- It can be helpful to introduce methods by comparing them to + functions. + + - Methods are called on an instance of a type (such as a struct or + enum), the first parameter represents the instance as ``self``. + - Developers may choose to use methods to take advantage of method + receiver syntax and to help keep them more organized. By using + methods we can keep all the implementation code in one predictable + place. + +- Point out the use of the keyword ``self``, a method receiver. + + - Show that it is an abbreviated term for ``self: Self`` and perhaps + show how the struct name could also be used. + - Explain that ``Self`` is a type alias for the type the ``impl`` + block is in and can be used elsewhere in the block. + - Note how ``self`` is used like other structs and dot notation can + be used to refer to individual fields. + - This might be a good time to demonstrate how the ``&self`` differs + from ``self`` by trying to run ``finish`` twice. + - Beyond variants on ``self``, there are also `special wrapper + types `__ + allowed to be receiver types, such as ``Box``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst new file mode 100644 index 000000000..576f78323 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst @@ -0,0 +1,35 @@ +======== +Traits +======== + +-------- +Traits +-------- + +Rust lets you abstract over types with traits. They're similar to +interfaces: + +.. code:: rust,editable + + trait Pet { + /// Return a sentence from this pet. + fn talk(&self) -> String; + + /// Print a string to the terminal greeting this pet. + fn greet(&self); + } + +.. raw:: html + +--------- +Details +--------- + +- A trait defines a number of methods that types must have in order to + implement the trait. + +- In the "Generics" segment, next, we will see how to build + functionality that is generic over all types implementing a trait. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst b/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst new file mode 100644 index 000000000..71637bfe2 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst @@ -0,0 +1,41 @@ +========== +Deriving +========== + +---------- +Deriving +---------- + +Supported traits can be automatically implemented for your custom types, +as follows: + +.. code:: rust,editable + + #[derive(Debug, Clone, Default)] + struct Player { + name: String, + strength: u8, + hit_points: u8, + } + + fn main() { + let p1 = Player::default(); // Default trait adds `default` constructor. + let mut p2 = p1.clone(); // Clone trait adds `clone` method. + p2.name = String::from("EldurScrollz"); + // Debug trait adds support for printing with `{:?}`. + println!("{p1:?} vs. {p2:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +Derivation is implemented with macros, and many crates provide useful +derive macros to add useful functionality. For example, ``serde`` can +derive serialization support for a struct using +``#[derive(Serialize)]``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst b/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst new file mode 100644 index 000000000..9470d4fe0 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst @@ -0,0 +1,29 @@ +======================== +Exercise: Logger Trait +======================== + +------------------------ +Exercise: Logger Trait +------------------------ + +Let's design a simple logging utility, using a trait ``Logger`` with a +``log`` method. Code which might log its progress can then take an +``&impl Logger``. In testing, this might put messages in the test +logfile, while in a production build it would send messages to a log +server. + +However, the ``StderrLogger`` given below logs all messages, regardless +of verbosity. Your task is to write a ``VerbosityFilter`` type that will +ignore messages above a maximum verbosity. + +This is a common pattern: a struct wrapping a trait implementation and +implementing that same trait, adding behavior in the process. What other +kinds of wrappers might be useful in a logging utility? + +.. code:: rust,compile_fail + + {{#include exercise.rs:setup}} + + // TODO: Define and implement `VerbosityFilter`. + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/100_generics.rst b/courses/comprehensive_rust_training/100_generics.rst new file mode 100644 index 000000000..d1351be38 --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics.rst @@ -0,0 +1,42 @@ +********** +Generics +********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 100_generics/01_generic_functions.rst +.. include:: 100_generics/02_generic_data.rst +.. include:: 100_generics/03_generic_traits.rst +.. include:: 100_generics/04_trait_bounds.rst +.. include:: 100_generics/05_impl_trait.rst +.. include:: 100_generics/06_dyn_trait.rst +.. include:: 100_generics/07_exercise.rst diff --git a/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst new file mode 100644 index 000000000..c997d1650 --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst @@ -0,0 +1,62 @@ +=================== +Generic Functions +=================== + +------------------- +Generic Functions +------------------- + +Rust supports generics, which lets you abstract algorithms or data +structures (such as sorting or a binary tree) over the types used or +stored. + +.. code:: rust,editable + + /// Pick `even` or `odd` depending on the value of `n`. + fn pick(n: i32, even: T, odd: T) -> T { + if n % 2 == 0 { + even + } else { + odd + } + } + + fn main() { + println!("picked a number: {:?}", pick(97, 222, 333)); + println!("picked a string: {:?}", pick(28, "dog", "cat")); + } + +.. raw:: html + +--------- +Details +--------- + +- Rust infers a type for T based on the types of the arguments and + return value. + +- In this example we only use the primitive types ``i32`` and ``&str`` + for ``T``, but we can use any type here, including user-defined + types: + + .. code:: rust,ignore + + struct Foo { + val: u8, + } + + pick(123, Foo { val: 7 }, Foo { val: 456 }); + +- This is similar to C++ templates, but Rust partially compiles the + generic function immediately, so that function must be valid for all + types matching the constraints. For example, try modifying ``pick`` + to return ``even + odd`` if ``n == 0``. Even if only the ``pick`` + instantiation with integers is used, Rust still considers it invalid. + C++ would let you do this. + +- Generic code is turned into non-generic code based on the call sites. + This is a zero-cost abstraction: you get exactly the same result as + if you had hand-coded the data structures without the abstraction. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst new file mode 100644 index 000000000..e4f10203c --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst @@ -0,0 +1,59 @@ +==================== +Generic Data Types +==================== + +-------------------- +Generic Data Types +-------------------- + +You can use generics to abstract over the concrete field type: + +.. code:: rust,editable + + #[derive(Debug)] + struct Point { + x: T, + y: T, + } + + impl Point { + fn coords(&self) -> (&T, &T) { + (&self.x, &self.y) + } + + fn set_x(&mut self, x: T) { + self.x = x; + } + } + + fn main() { + let integer = Point { x: 5, y: 10 }; + let float = Point { x: 1.0, y: 4.0 }; + println!("{integer:?} and {float:?}"); + println!("coords: {:?}", integer.coords()); + } + +.. raw:: html + +--------- +Details +--------- + +- *Q:* Why ``T`` is specified twice in ``impl Point {}``? Isn't + that redundant? + + - This is because it is a generic implementation section for generic + type. They are independently generic. + - It means these methods are defined for any ``T``. + - It is possible to write ``impl Point { .. }``. + + - ``Point`` is still generic and you can use ``Point``, but + methods in this block will only be available for + ``Point``. + +- Try declaring a new variable ``let p = Point { x: 5, y: 10.0 };``. + Update the code to allow points that have elements of different + types, by using two type variables, e.g., ``T`` and ``U``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst new file mode 100644 index 000000000..539188bb2 --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst @@ -0,0 +1,61 @@ +================ +Generic Traits +================ + +---------------- +Generic Traits +---------------- + +Traits can also be generic, just like types and functions. A trait's +parameters get concrete types when it is used. + +.. code:: rust,editable + + #[derive(Debug)] + struct Foo(String); + + impl From for Foo { + fn from(from: u32) -> Foo { + Foo(format!("Converted from integer: {from}")) + } + } + + impl From for Foo { + fn from(from: bool) -> Foo { + Foo(format!("Converted from bool: {from}")) + } + } + + fn main() { + let from_int = Foo::from(123); + let from_bool = Foo::from(true); + println!("{from_int:?}, {from_bool:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- The ``From`` trait will be covered later in the course, but its + `definition in the ``std`` + docs `__ is + simple. + +- Implementations of the trait do not need to cover all possible type + parameters. Here, ``Foo::from("hello")`` would not compile because + there is no ``From<&str>`` implementation for ``Foo``. + +- Generic traits take types as "input", while associated types are a + kind of "output" type. A trait can have multiple implementations for + different input types. + +- In fact, Rust requires that at most one implementation of a trait + match for any type T. Unlike some other languages, Rust has no + heuristic for choosing the "most specific" match. There is work on + adding this support, called + `specialization `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst new file mode 100644 index 000000000..142d3c81b --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst @@ -0,0 +1,61 @@ +============== +Trait Bounds +============== + +-------------- +Trait Bounds +-------------- + +When working with generics, you often want to require the types to +implement some trait, so that you can call this trait's methods. + +You can do this with ``T: Trait``: + +.. code:: rust,editable + + fn duplicate(a: T) -> (T, T) { + (a.clone(), a.clone()) + } + + struct NotCloneable; + + fn main() { + let foo = String::from("foo"); + let pair = duplicate(foo); + println!("{pair:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- Try making a ``NonCloneable`` and passing it to ``duplicate``. + +- When multiple traits are necessary, use ``+`` to join them. + +- Show a ``where`` clause, students will encounter it when reading + code. + + .. code:: rust,ignore + + fn duplicate(a: T) -> (T, T) + where + T: Clone, + { + (a.clone(), a.clone()) + } + + - It declutters the function signature if you have many parameters. + - It has additional features making it more powerful. + + - If someone asks, the extra feature is that the type on the left + of ":" can be arbitrary, like ``Option``. + +- Note that Rust does not (yet) support specialization. For example, + given the original ``duplicate``, it is invalid to add a specialized + ``duplicate(a: u32)``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst new file mode 100644 index 000000000..d93da92dd --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst @@ -0,0 +1,62 @@ +================ +``impl Trait`` +================ + +---------------- +``impl Trait`` +---------------- + +Similar to trait bounds, an ``impl Trait`` syntax can be used in +function arguments and return values: + +.. code:: rust,editable + + // Syntactic sugar for: + // fn add_42_millions>(x: T) -> i32 { + fn add_42_millions(x: impl Into) -> i32 { + x.into() + 42_000_000 + } + + fn pair_of(x: u32) -> impl std::fmt::Debug { + (x + 1, x - 1) + } + + fn main() { + let many = add_42_millions(42_i8); + println!("{many}"); + let many_more = add_42_millions(10_000_000); + println!("{many_more}"); + let debuggable = pair_of(27); + println!("debuggable: {debuggable:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +``impl Trait`` allows you to work with types which you cannot name. The +meaning of ``impl Trait`` is a bit different in the different positions. + +- For a parameter, ``impl Trait`` is like an anonymous generic + parameter with a trait bound. + +- For a return type, it means that the return type is some concrete + type that implements the trait, without naming the type. This can be + useful when you don't want to expose the concrete type in a public + API. + + Inference is hard in return position. A function returning + ``impl Foo`` picks the concrete type it returns, without writing it + out in the source. A function returning a generic type like + ``collect() -> B`` can return any type satisfying ``B``, and the + caller may need to choose one, such as with + ``let x: Vec<_> = foo.collect()`` or with the turbofish, + ``foo.collect::>()``. + +What is the type of ``debuggable``? Try ``let debuggable: () = ..`` to +see what the error message shows. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst new file mode 100644 index 000000000..9da5f9305 --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst @@ -0,0 +1,96 @@ +=============== +``dyn Trait`` +=============== + +--------------- +``dyn Trait`` +--------------- + +In addition to using traits for static dispatch via generics, Rust also +supports using them for type-erased, dynamic dispatch via trait objects: + +.. code:: rust,editable + + struct Dog { + name: String, + age: i8, + } + struct Cat { + lives: i8, + } + + trait Pet { + fn talk(&self) -> String; + } + + impl Pet for Dog { + fn talk(&self) -> String { + format!("Woof, my name is {}!", self.name) + } + } + + impl Pet for Cat { + fn talk(&self) -> String { + String::from("Miau!") + } + } + + // Uses generics and static dispatch. + fn generic(pet: &impl Pet) { + println!("Hello, who are you? {}", pet.talk()); + } + + // Uses type-erasure and dynamic dispatch. + fn dynamic(pet: &dyn Pet) { + println!("Hello, who are you? {}", pet.talk()); + } + + fn main() { + let cat = Cat { lives: 9 }; + let dog = Dog { name: String::from("Fido"), age: 5 }; + + generic(&cat); + generic(&dog); + + dynamic(&cat); + dynamic(&dog); + } + +.. raw:: html + +--------- +Details +--------- + +- Generics, including ``impl Trait``, use monomorphization to create a + specialized instance of the function for each different type that the + generic is instantiated with. This means that calling a trait method + from within a generic function still uses static dispatch, as the + compiler has full type information and can resolve which type's trait + implementation to use. + +- When using ``dyn Trait``, it instead uses dynamic dispatch through a + `virtual method + table `__ + (vtable). This means that there's a single version of ``fn dynamic`` + that is used regardless of what type of ``Pet`` is passed in. + +- When using ``dyn Trait``, the trait object needs to be behind some + kind of indirection. In this case it's a reference, though smart + pointer types like ``Box`` can also be used (this will be + demonstrated on day 3). + +- At runtime, a ``&dyn Pet`` is represented as a "fat pointer", i.e. a + pair of two pointers: One pointer points to the concrete object that + implements ``Pet``, and the other points to the vtable for the trait + implementation for that type. When calling the ``talk`` method on + ``&dyn Pet`` the compiler looks up the function pointer for ``talk`` + in the vtable and then invokes the function, passing the pointer to + the ``Dog`` or ``Cat`` into that function. The compiler doesn't need + to know the concrete type of the ``Pet`` in order to do this. + +- A ``dyn Trait`` is considered to be "type-erased", because we no + longer have compile-time knowledge of what the concrete type is. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/07_exercise.rst b/courses/comprehensive_rust_training/100_generics/07_exercise.rst new file mode 100644 index 000000000..5dfcef6dd --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -0,0 +1,35 @@ +=========================== +Exercise: Generic ``min`` +=========================== + +--------------------------- +Exercise: Generic ``min`` +--------------------------- + +In this short exercise, you will implement a generic ``min`` function +that determines the minimum of two values, using the +```Ord`` `__ +trait. + +.. code:: rust,compile_fail + + use std::cmp::Ordering; + + // TODO: implement the `min` function used in `main`. + + {{#include exercise.rs:main}} + +.. raw:: html + +--------- +Details +--------- + +- Show students the + ```Ord`` `__ + trait and + ```Ordering`` `__ + enum. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types.rst b/courses/comprehensive_rust_training/110_std_types.rst new file mode 100644 index 000000000..76f4fddbe --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types.rst @@ -0,0 +1,43 @@ +*********** +Std Types +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 110_std_types/01_std.rst +.. include:: 110_std_types/02_docs.rst +.. include:: 110_std_types/03_option.rst +.. include:: 110_std_types/04_result.rst +.. include:: 110_std_types/05_string.rst +.. include:: 110_std_types/06_vec.rst +.. include:: 110_std_types/07_hashmap.rst +.. include:: 110_std_types/08_exercise.rst diff --git a/courses/comprehensive_rust_training/110_std_types/01_std.rst b/courses/comprehensive_rust_training/110_std_types/01_std.rst new file mode 100644 index 000000000..2ca301da9 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/01_std.rst @@ -0,0 +1,22 @@ +================== +Standard Library +================== + +------------------ +Standard Library +------------------ + +Rust comes with a standard library which helps establish a set of common +types used by Rust libraries and programs. This way, two libraries can +work together smoothly because they both use the same ``String`` type. + +In fact, Rust contains several layers of the Standard Library: ``core``, +``alloc`` and ``std``. + +- ``core`` includes the most basic types and functions that don't + depend on ``libc``, allocator or even the presence of an operating + system. +- ``alloc`` includes types which require a global heap allocator, such + as ``Vec``, ``Box`` and ``Arc``. +- Embedded Rust applications often only use ``core``, and sometimes + ``alloc``. diff --git a/courses/comprehensive_rust_training/110_std_types/02_docs.rst b/courses/comprehensive_rust_training/110_std_types/02_docs.rst new file mode 100644 index 000000000..ea971532c --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/02_docs.rst @@ -0,0 +1,59 @@ +=============== +Documentation +=============== + +--------------- +Documentation +--------------- + +Rust comes with extensive documentation. For example: + +- All of the details about + `loops `__. +- Primitive types like + ```u8`` `__. +- Standard library types like + ```Option`` `__ + or + ```BinaryHeap`` `__. + +Use ``rustup doc --std`` or https://std.rs to view the documentation. + +In fact, you can document your own code: + +.. code:: rust,editable + + /// Determine whether the first argument is divisible by the second argument. + /// + /// If the second argument is zero, the result is false. + fn is_divisible_by(lhs: u32, rhs: u32) -> bool { + if rhs == 0 { + return false; + } + lhs % rhs == 0 + } + +The contents are treated as Markdown. All published Rust library crates +are automatically documented at ```docs.rs`` `__ using +the `rustdoc `__ +tool. It is idiomatic to document all public items in an API using this +pattern. + +To document an item from inside the item (such as inside a module), use +``//!`` or ``/*! .. */``, called "inner doc comments": + +.. code:: rust,editable + + //! This module contains functionality relating to divisibility of integers. + +.. raw:: html + +--------- +Details +--------- + +- Show students the generated docs for the ``rand`` crate at + https://docs.rs/rand. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/03_option.rst b/courses/comprehensive_rust_training/110_std_types/03_option.rst new file mode 100644 index 000000000..d0382644f --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/03_option.rst @@ -0,0 +1,49 @@ +======== +Option +======== + +-------- +Option +-------- + +We have already seen some use of ``Option``. It stores either a value +of type ``T`` or nothing. For example, +```String::find`` `__ +returns an ``Option``. + +.. code:: rust,editable,should_panic + + fn main() { + let name = "LTBDwe TBDTBD LTBDopard Gepardi"; + let mut position: Option = name.find('TBD'); + println!("find returned {position:?}"); + assert_eq!(position.unwrap(), 14); + position = name.find('Z'); + println!("find returned {position:?}"); + assert_eq!(position.expect("Character not found"), 0); + } + +.. raw:: html + +--------- +Details +--------- + +- ``Option`` is widely used, not just in the standard library. +- ``unwrap`` will return the value in an ``Option``, or panic. + ``expect`` is similar but takes an error message. + + - You can panic on None, but you can't "accidentally" forget to + check for None. + - It's common to ``unwrap``/``expect`` all over the place when + hacking something together, but production code typically handles + ``None`` in a nicer fashion. + +- The "niche optimization" means that ``Option`` often has the same + size in memory as ``T``, if there is some representation that is not + a valid value of T. For example, a reference cannot be NULL, so + ``Option<&T>`` automatically uses NULL to represent the ``None`` + variant, and thus can be stored in the same memory as ``&T``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/04_result.rst b/courses/comprehensive_rust_training/110_std_types/04_result.rst new file mode 100644 index 000000000..ab5840e70 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/04_result.rst @@ -0,0 +1,54 @@ +======== +Result +======== + +-------- +Result +-------- + +``Result`` is similar to ``Option``, but indicates the success or +failure of an operation, each with a different enum variant. It is +generic: ``Result`` where ``T`` is used in the ``Ok`` variant and +``E`` appears in the ``Err`` variant. + +.. code:: rust,editable + + use std::fs::File; + use std::io::Read; + + fn main() { + let file: Result = File::open("diary.txt"); + match file { + Ok(mut file) => { + let mut contents = String::new(); + if let Ok(bytes) = file.read_to_string(&mut contents) { + println!("Dear diary: {contents} ({bytes} bytes)"); + } else { + println!("Could not read file content"); + } + } + Err(err) => { + println!("The diary could not be opened: {err}"); + } + } + } + +.. raw:: html + +--------- +Details +--------- + +- As with ``Option``, the successful value sits inside of ``Result``, + forcing the developer to explicitly extract it. This encourages error + checking. In the case where an error should never happen, + ``unwrap()`` or ``expect()`` can be called, and this is a signal of + the developer intent too. +- ``Result`` documentation is a recommended read. Not during the + course, but it is worth mentioning. It contains a lot of convenience + methods and functions that help functional-style programming. +- ``Result`` is the standard type to implement error handling as we + will see on Day 4. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/05_string.rst b/courses/comprehensive_rust_training/110_std_types/05_string.rst new file mode 100644 index 000000000..28b661019 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -0,0 +1,76 @@ +======== +String +======== + +-------- +String +-------- + +```String`` `__ +is a growable UTF-8 encoded string: + +.. code:: rust,editable + + fn main() { + let mut s1 = String::new(); + s1.push_str("Hello"); + println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity()); + + let mut s2 = String::with_capacity(s1.len() + 1); + s2.push_str(&s1); + s2.push('!'); + println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity()); + + let s3 = String::from("TBDTBD"); + println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count()); + } + +``String`` implements +```Deref`` `__, +which means that you can call all ``str`` methods on a ``String``. + +.. raw:: html + +--------- +Details +--------- + +- ``String::new`` returns a new empty string, use + ``String::with_capacity`` when you know how much data you want to + push to the string. +- ``String::len`` returns the size of the ``String`` in bytes (which + can be different from its length in characters). +- ``String::chars`` returns an iterator over the actual characters. + Note that a ``char`` can be different from what a human will consider + a "character" due to `grapheme + clusters `__. +- When people refer to strings they could either be talking about + ``&str`` or ``String``. +- When a type implements ``Deref``, the compiler will let + you transparently call methods from ``T``. + + - We haven't discussed the ``Deref`` trait yet, so at this point + this mostly explains the structure of the sidebar in the + documentation. + - ``String`` implements ``Deref`` which transparently + gives it access to ``str``\ 's methods. + - Write and compare ``let s3 = s1.deref();`` and ``let s3 = &*s1;``. + +- ``String`` is implemented as a wrapper around a vector of bytes, many + of the operations you see supported on vectors are also supported on + ``String``, but with some extra guarantees. +- Compare the different ways to index a ``String``: + + - To a character by using ``s3.chars().nth(i).unwrap()`` where ``i`` + is in-bound, out-of-bounds. + - To a substring by using ``s3[0..4]``, where that slice is on + character boundaries or not. + +- Many types can be converted to a string with the + ```to_string`` `__ + method. This trait is automatically implemented for all types that + implement ``Display``, so anything that can be formatted can also be + converted to a string. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/06_vec.rst b/courses/comprehensive_rust_training/110_std_types/06_vec.rst new file mode 100644 index 000000000..337d3bfe5 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -0,0 +1,63 @@ +========= +``Vec`` +========= + +--------- +``Vec`` +--------- + +```Vec`` `__ is the +standard resizable heap-allocated buffer: + +.. code:: rust,editable + + fn main() { + let mut v1 = Vec::new(); + v1.push(42); + println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity()); + + let mut v2 = Vec::with_capacity(v1.len() + 1); + v2.extend(v1.iter()); + v2.push(9999); + println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity()); + + // Canonical macro to initialize a vector with elements. + let mut v3 = vec![0, 0, 1, 2, 3, 4]; + + // Retain only the even elements. + v3.retain(|x| x % 2 == 0); + println!("{v3:?}"); + + // Remove consecutive duplicates. + v3.dedup(); + println!("{v3:?}"); + } + +``Vec`` implements +```Deref`` `__, +which means that you can call slice methods on a ``Vec``. + +.. raw:: html + +--------- +Details +--------- + +- ``Vec`` is a type of collection, along with ``String`` and + ``HashMap``. The data it contains is stored on the heap. This means + the amount of data doesn't need to be known at compile time. It can + grow or shrink at runtime. +- Notice how ``Vec`` is a generic type too, but you don't have to + specify ``T`` explicitly. As always with Rust type inference, the + ``T`` was established during the first ``push`` call. +- ``vec![...]`` is a canonical macro to use instead of ``Vec::new()`` + and it supports adding initial elements to the vector. +- To index the vector you use ``[`` ``]``, but they will panic if out + of bounds. Alternatively, using ``get`` will return an ``Option``. + The ``pop`` function will remove the last element. +- Slices are covered on day 3. For now, students only need to know that + a value of type ``Vec`` gives access to all of the documented slice + methods, too. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst new file mode 100644 index 000000000..e4d1f23bb --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -0,0 +1,91 @@ +============= +``HashMap`` +============= + +------------- +``HashMap`` +------------- + +Standard hash map with protection against HashDoS attacks: + +.. code:: rust,editable + + use std::collections::HashMap; + + fn main() { + let mut page_counts = HashMap::new(); + page_counts.insert("Adventures of Huckleberry Finn", 207); + page_counts.insert("Grimms' Fairy Tales", 751); + page_counts.insert("Pride and Prejudice", 303); + + if !page_counts.contains_key("Les MisTBDrables") { + println!( + "We know about {} books, but not Les MisTBDrables.", + page_counts.len() + ); + } + + for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] { + match page_counts.get(book) { + Some(count) => println!("{book}: {count} pages"), + None => println!("{book} is unknown."), + } + } + + // Use the .entry() method to insert a value if nothing is found. + for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] { + let page_count: &mut i32 = page_counts.entry(book).or_insert(0); + *page_count += 1; + } + + println!("{page_counts:#?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- ``HashMap`` is not defined in the prelude and needs to be brought + into scope. + +- Try the following lines of code. The first line will see if a book is + in the hashmap and if not return an alternative value. The second + line will insert the alternative value in the hashmap if the book is + not found. + + .. code:: rust,ignore + + let pc1 = page_counts + .get("Harry Potter and the Sorcerer's Stone") + .unwrap_or(&336); + let pc2 = page_counts + .entry("The Hunger Games") + .or_insert(374); + +- Unlike ``vec!``, there is unfortunately no standard ``hashmap!`` + macro. + + - Although, since Rust 1.56, HashMap implements + ```From<[(K, V); N]>`` `__, + which allows us to easily initialize a hash map from a literal + array: + + .. code:: rust,ignore + + let page_counts = HashMap::from([ + ("Harry Potter and the Sorcerer's Stone".to_string(), 336), + ("The Hunger Games".to_string(), 374), + ]); + +- Alternatively HashMap can be built from any ``Iterator`` which yields + key-value tuples. + +- This type has several "method-specific" return types, such as + ``std::collections::hash_map::Keys``. These types often appear in + searches of the Rust docs. Show students the docs for this type, and + the helpful link back to the ``keys`` method. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/08_exercise.rst b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst new file mode 100644 index 000000000..9d6f69982 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst @@ -0,0 +1,56 @@ +=================== +Exercise: Counter +=================== + +------------------- +Exercise: Counter +------------------- + +In this exercise you will take a very simple data structure and make it +generic. It uses a +```std::collections::HashMap`` `__ +to keep track of which values have been seen and how many times each one +has appeared. + +The initial version of ``Counter`` is hard coded to only work for +``u32`` values. Make the struct and its methods generic over the type of +value being tracked, that way ``Counter`` can track any type of value. + +If you finish early, try using the +```entry`` `__ +method to halve the number of hash lookups required to implement the +``count`` method. + +.. code:: rust,compile_fail,editable + + use std::collections::HashMap; + + /// Counter counts the number of times each value of type T has been seen. + struct Counter { + values: HashMap, + } + + impl Counter { + /// Create a new Counter. + fn new() -> Self { + Counter { + values: HashMap::new(), + } + } + + /// Count an occurrence of the given value. + fn count(&mut self, value: u32) { + if self.values.contains_key(&value) { + *self.values.get_mut(&value).unwrap() += 1; + } else { + self.values.insert(value, 1); + } + } + + /// Return the number of times the given value has been seen. + fn times_seen(&self, value: u32) -> u64 { + self.values.get(&value).copied().unwrap_or_default() + } + } + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/120_std_traits.rst b/courses/comprehensive_rust_training/120_std_traits.rst new file mode 100644 index 000000000..aae2116c8 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits.rst @@ -0,0 +1,43 @@ +************ +Std Traits +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 120_std_traits/01_comparisons.rst +.. include:: 120_std_traits/02_operators.rst +.. include:: 120_std_traits/03_from_and_into.rst +.. include:: 120_std_traits/04_casting.rst +.. include:: 120_std_traits/05_read_and_write.rst +.. include:: 120_std_traits/06_default.rst +.. include:: 120_std_traits/07_closures.rst +.. include:: 120_std_traits/08_exercise.rst diff --git a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst new file mode 100644 index 000000000..0c1167a3a --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst @@ -0,0 +1,88 @@ +============= +Comparisons +============= + +------------- +Comparisons +------------- + +These traits support comparisons between values. All traits can be +derived for types containing fields that implement these traits. + +-------------------------- +``PartialEq`` and ``Eq`` +-------------------------- + +``PartialEq`` is a partial equivalence relation, with required method +``eq`` and provided method ``ne``. The ``==`` and ``!=`` operators will +call these methods. + +.. code:: rust,editable + + struct Key { + id: u32, + metadata: Option, + } + impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } + } + +``Eq`` is a full equivalence relation (reflexive, symmetric, and +transitive) and implies ``PartialEq``. Functions that require full +equivalence will use ``Eq`` as a trait bound. + +---------------------------- +``PartialOrd`` and ``Ord`` +---------------------------- + +``PartialOrd`` defines a partial ordering, with a ``partial_cmp`` +method. It is used to implement the ``<``, ``<=``, ``>=``, and ``>`` +operators. + +.. code:: rust,editable + + use std::cmp::Ordering; + #[derive(Eq, PartialEq)] + struct Citation { + author: String, + year: u32, + } + impl PartialOrd for Citation { + fn partial_cmp(&self, other: &Self) -> Option { + match self.author.partial_cmp(&other.author) { + Some(Ordering::Equal) => self.year.partial_cmp(&other.year), + author_ord => author_ord, + } + } + } + +``Ord`` is a total ordering, with ``cmp`` returning ``Ordering``. + +.. raw:: html + +--------- +Details +--------- + +``PartialEq`` can be implemented between different types, but ``Eq`` +cannot, because it is reflexive: + +.. code:: rust,editable + + struct Key { + id: u32, + metadata: Option, + } + impl PartialEq for Key { + fn eq(&self, other: &u32) -> bool { + self.id == *other + } + } + +In practice, it's common to derive these traits, but uncommon to +implement them. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/02_operators.rst b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst new file mode 100644 index 000000000..f7193fbbb --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst @@ -0,0 +1,66 @@ +=========== +Operators +=========== + +----------- +Operators +----------- + +Operator overloading is implemented via traits in +```std::ops`` `__: + +.. code:: rust,editable + + #[derive(Debug, Copy, Clone)] + struct Point { + x: i32, + y: i32, + } + + impl std::ops::Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { x: self.x + other.x, y: self.y + other.y } + } + } + + fn main() { + let p1 = Point { x: 10, y: 20 }; + let p2 = Point { x: 100, y: 200 }; + println!("{p1:?} + {p2:?} = {:?}", p1 + p2); + } + +.. raw:: html + +--------- +Details +--------- + +Discussion points: + +- You could implement ``Add`` for ``&Point``. In which situations is + that useful? + + - Answer: ``Add:add`` consumes ``self``. If type ``T`` for which you + are overloading the operator is not ``Copy``, you should consider + overloading the operator for ``&T`` as well. This avoids + unnecessary cloning on the call site. + +- Why is ``Output`` an associated type? Could it be made a type + parameter of the method? + + - Short answer: Function type parameters are controlled by the + caller, but associated types (like ``Output``) are controlled by + the implementer of a trait. + +- You could implement ``Add`` for two different types, e.g. + ``impl Add<(i32, i32)> for Point`` would add a tuple to a ``Point``. + +The ``Not`` trait (``!`` operator) is notable because it does not +"boolify" like the same operator in C-family languages; instead, for +integer types it negates each bit of the number, which arithmetically is +equivalent to subtracting it from -1: ``!5 == -6``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst b/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst new file mode 100644 index 000000000..937587ba3 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst @@ -0,0 +1,54 @@ +======================= +``From`` and ``Into`` +======================= + +----------------------- +``From`` and ``Into`` +----------------------- + +Types implement +```From`` `__ and +```Into`` `__ to +facilitate type conversions. Unlike ``as``, these traits correspond to +lossless, infallible conversions. + +.. code:: rust,editable + + fn main() { + let s = String::from("hello"); + let addr = std::net::Ipv4Addr::from([127, 0, 0, 1]); + let one = i16::from(true); + let bigger = i32::from(123_i16); + println!("{s}, {addr}, {one}, {bigger}"); + } + +```Into`` `__ is +automatically implemented when +```From`` `__ is +implemented: + +.. code:: rust,editable + + fn main() { + let s: String = "hello".into(); + let addr: std::net::Ipv4Addr = [127, 0, 0, 1].into(); + let one: i16 = true.into(); + let bigger: i32 = 123_i16.into(); + println!("{s}, {addr}, {one}, {bigger}"); + } + +.. raw:: html + +--------- +Details +--------- + +- That's why it is common to only implement ``From``, as your type will + get ``Into`` implementation too. +- When declaring a function argument input type like "anything that can + be converted into a ``String``", the rule is opposite, you should use + ``Into``. Your function will accept types that implement ``From`` and + those that *only* implement ``Into``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/04_casting.rst b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst new file mode 100644 index 000000000..04d2480c0 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst @@ -0,0 +1,52 @@ +========= +Casting +========= + +--------- +Casting +--------- + +Rust has no *implicit* type conversions, but does support explicit casts +with ``as``. These generally follow C semantics where those are defined. + +.. code:: rust,editable + + fn main() { + let value: i64 = 1000; + println!("as u16: {}", value as u16); + println!("as i16: {}", value as i16); + println!("as u8: {}", value as u8); + } + +The results of ``as`` are *always* defined in Rust and consistent across +platforms. This might not match your intuition for changing sign or +casting to a smaller type TBD check the docs, and comment for clarity. + +Casting with ``as`` is a relatively sharp tool that is easy to use +incorrectly, and can be a source of subtle bugs as future maintenance +work changes the types that are used or the ranges of values in types. +Casts are best used only when the intent is to indicate unconditional +truncation (e.g. selecting the bottom 32 bits of a ``u64`` with +``as u32``, regardless of what was in the high bits). + +For infallible casts (e.g. ``u32`` to ``u64``), prefer using ``From`` or +``Into`` over ``as`` to confirm that the cast is in fact infallible. For +fallible casts, ``TryFrom`` and ``TryInto`` are available when you want +to handle casts that fit differently from those that don't. + +.. raw:: html + +--------- +Details +--------- + +Consider taking a break after this slide. + +``as`` is similar to a C++ static cast. Use of ``as`` in cases where +data might be lost is generally discouraged, or at least deserves an +explanatory comment. + +This is common in casting integers to ``usize`` for use as an index. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst b/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst new file mode 100644 index 000000000..a849cc5ab --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst @@ -0,0 +1,51 @@ +======================== +``Read`` and ``Write`` +======================== + +------------------------ +``Read`` and ``Write`` +------------------------ + +Using ```Read`` `__ +and +```BufRead`` `__, +you can abstract over ``u8`` sources: + +.. code:: rust,editable + + use std::io::{BufRead, BufReader, Read, Result}; + + fn count_lines(reader: R) -> usize { + let buf_reader = BufReader::new(reader); + buf_reader.lines().count() + } + + fn main() -> Result<()> { + let slice: &[u8] = b"foo\nbar\nbaz\n"; + println!("lines in slice: {}", count_lines(slice)); + + let file = std::fs::File::open(std::env::current_exe()?)?; + println!("lines in file: {}", count_lines(file)); + Ok(()) + } + +Similarly, +```Write`` `__ lets +you abstract over ``u8`` sinks: + +.. code:: rust,editable + + use std::io::{Result, Write}; + + fn log(writer: &mut W, msg: &str) -> Result<()> { + writer.write_all(msg.as_bytes())?; + writer.write_all("\n".as_bytes()) + } + + fn main() -> Result<()> { + let mut buffer = Vec::new(); + log(&mut buffer, "Hello")?; + log(&mut buffer, "World")?; + println!("Logged: {buffer:?}"); + Ok(()) + } diff --git a/courses/comprehensive_rust_training/120_std_traits/06_default.rst b/courses/comprehensive_rust_training/120_std_traits/06_default.rst new file mode 100644 index 000000000..2c7e68907 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -0,0 +1,64 @@ +======================= +The ``Default`` Trait +======================= + +----------------------- +The ``Default`` Trait +----------------------- + +```Default`` `__ +trait produces a default value for a type. + +.. code:: rust,editable + + #[derive(Debug, Default)] + struct Derived { + x: u32, + y: String, + z: Implemented, + } + + #[derive(Debug)] + struct Implemented(String); + + impl Default for Implemented { + fn default() -> Self { + Self("John Smith".into()) + } + } + + fn main() { + let default_struct = Derived::default(); + println!("{default_struct:#?}"); + + let almost_default_struct = + Derived { y: "Y is set!".into(), ..Derived::default() }; + println!("{almost_default_struct:#?}"); + + let nothing: Option = None; + println!("{:#?}", nothing.unwrap_or_default()); + } + +.. raw:: html + +--------- +Details +--------- + +- It can be implemented directly or it can be derived via + ``#[derive(Default)]``. +- A derived implementation will produce a value where all fields are + set to their default values. + + - This means all types in the struct must implement ``Default`` too. + +- Standard Rust types often implement ``Default`` with reasonable + values (e.g. ``0``, ``""``, etc). +- The partial struct initialization works nicely with default. +- The Rust standard library is aware that types can implement + ``Default`` and provides convenience methods that use it. +- The ``..`` syntax is called `struct update + syntax `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/07_closures.rst b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst new file mode 100644 index 000000000..136765953 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst @@ -0,0 +1,92 @@ +========== +Closures +========== + +---------- +Closures +---------- + +Closures or lambda expressions have types which cannot be named. +However, they implement special +```Fn`` `__, +```FnMut`` `__, and +```FnOnce`` `__ +traits: + +.. code:: rust,editable + + fn apply_and_log(func: impl FnOnce(i32) -> i32, func_name: &str, input: i32) { + println!("Calling {func_name}({input}): {}", func(input)) + } + + fn main() { + let n = 3; + let add_3 = |x| x + n; + apply_and_log(&add_3, "add_3", 10); + apply_and_log(&add_3, "add_3", 20); + + let mut v = Vec::new(); + let mut accumulate = |x: i32| { + v.push(x); + v.iter().sum::() + }; + apply_and_log(&mut accumulate, "accumulate", 4); + apply_and_log(&mut accumulate, "accumulate", 5); + + let multiply_sum = |x| x * v.into_iter().sum::(); + apply_and_log(multiply_sum, "multiply_sum", 3); + } + +.. raw:: html + +--------- +Details +--------- + +An ``Fn`` (e.g. ``add_3``) neither consumes nor mutates captured values. +It can be called needing only a shared reference to the closure, which +means the closure can be executed repeatedly and even concurrently. + +An ``FnMut`` (e.g. ``accumulate``) might mutate captured values. The +closure object is accessed via exclusive reference, so it can be called +repeatedly but not concurrently. + +If you have an ``FnOnce`` (e.g. ``multiply_sum``), you may only call it +once. Doing so consumes the closure and any values captured by move. + +``FnMut`` is a subtype of ``FnOnce``. ``Fn`` is a subtype of ``FnMut`` +and ``FnOnce``. I.e. you can use an ``FnMut`` wherever an ``FnOnce`` is +called for, and you can use an ``Fn`` wherever an ``FnMut`` or +``FnOnce`` is called for. + +When you define a function that takes a closure, you should take +``FnOnce`` if you can (i.e. you call it once), or ``FnMut`` else, and +last ``Fn``. This allows the most flexibility for the caller. + +In contrast, when you have a closure, the most flexible you can have is +``Fn`` (which can be passed to a consumer of any of the 3 closure +traits), then ``FnMut``, and lastly ``FnOnce``. + +The compiler also infers ``Copy`` (e.g. for ``add_3``) and ``Clone`` +(e.g. ``multiply_sum``), depending on what the closure captures. +Function pointers (references to ``fn`` items) implement ``Copy`` and +``Fn``. + +By default, closures will capture each variable from an outer scope by +the least demanding form of access they can (by shared reference if +possible, then exclusive reference, then by move). The ``move`` keyword +forces capture by value. + +.. code:: rust,editable + + fn make_greeter(prefix: String) -> impl Fn(&str) { + return move |name| println!("{} {}", prefix, name); + } + + fn main() { + let hi = make_greeter("Hi".to_string()); + hi("Greg"); + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst new file mode 100644 index 000000000..ede44e997 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst @@ -0,0 +1,23 @@ +================= +Exercise: ROT13 +================= + +----------------- +Exercise: ROT13 +----------------- + +In this example, you will implement the classic `"ROT13" +cipher `__. Copy this code to the +playground, and implement the missing bits. Only rotate ASCII alphabetic +characters, to ensure the result is still valid UTF-8. + +.. code:: rust,compile_fail + + {{#include exercise.rs:head }} + + // Implement the `Read` trait for `RotDecoder`. + + {{#include exercise.rs:main }} + +What happens if you chain two ``RotDecoder`` instances together, each +rotating by 13 characters? diff --git a/courses/comprehensive_rust_training/130_memory_management.rst b/courses/comprehensive_rust_training/130_memory_management.rst new file mode 100644 index 000000000..020134370 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management.rst @@ -0,0 +1,43 @@ +******************* +Memory Management +******************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 130_memory_management/01_review.rst +.. include:: 130_memory_management/02_approaches.rst +.. include:: 130_memory_management/03_ownership.rst +.. include:: 130_memory_management/04_move.rst +.. include:: 130_memory_management/05_clone.rst +.. include:: 130_memory_management/06_copy_types.rst +.. include:: 130_memory_management/07_drop.rst +.. include:: 130_memory_management/08_exercise.rst diff --git a/courses/comprehensive_rust_training/130_memory_management/01_review.rst b/courses/comprehensive_rust_training/130_memory_management/01_review.rst new file mode 100644 index 000000000..dfd0c6e52 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/01_review.rst @@ -0,0 +1,90 @@ +========================== +Review of Program Memory +========================== + +-------------------------- +Review of Program Memory +-------------------------- + +Programs allocate memory in two ways: + +- Stack: Continuous area of memory for local variables. + + - Values have fixed sizes known at compile time. + - Extremely fast: just move a stack pointer. + - Easy to manage: follows function calls. + - Great memory locality. + +- Heap: Storage of values outside of function calls. + + - Values have dynamic sizes determined at runtime. + - Slightly slower than the stack: some book-keeping needed. + - No guarantee of memory locality. + +--------- +Example +--------- + +Creating a ``String`` puts fixed-sized metadata on the stack and +dynamically sized data, the actual string, on the heap: + +.. code:: rust,editable + + fn main() { + let s1 = String::from("Hello"); + } + +.. code:: bob + + Stack + .- - - - - - - - - - - - - -. Heap + : : .- - - - - - - - - - - - - - - -. + : s1 : : : + : +-----------+-------+ : : : + : | capacity | 5 | : : +----+----+----+----+----+ : + : | ptr | o-+---+-----+-->| H | e | l | l | o | : + : | len | 5 | : : +----+----+----+----+----+ : + : +-----------+-------+ : : : + : : : : + `- - - - - - - - - - - - - -' `- - - - - - - - - - - - - - - -' + +.. raw:: html + +--------- +Details +--------- + +- Mention that a ``String`` is backed by a ``Vec``, so it has a + capacity and length and can grow if mutable via reallocation on the + heap. + +- If students ask about it, you can mention that the underlying memory + is heap allocated using the `System + Allocator `__ + and custom allocators can be implemented using the `Allocator + API `__ + +----------------- +More to Explore +----------------- + +We can inspect the memory layout with ``unsafe`` Rust. However, you +should point out that this is rightfully unsafe! + +.. code:: rust,editable + + fn main() { + let mut s1 = String::from("Hello"); + s1.push(' '); + s1.push_str("world"); + // DON'T DO THIS AT HOME! For educational purposes only. + // String provides no guarantees about its layout, so this could lead to + // undefined behavior. + unsafe { + let (capacity, ptr, len): (usize, usize, usize) = std::mem::transmute(s1); + println!("capacity = {capacity}, ptr = {ptr:#x}, len = {len}"); + } + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst new file mode 100644 index 000000000..4719b56cc --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst @@ -0,0 +1,66 @@ +================================= +Approaches to Memory Management +================================= + +--------------------------------- +Approaches to Memory Management +--------------------------------- + +Traditionally, languages have fallen into two broad categories: + +- Full control via manual memory management: C, C++, Pascal, ... + + - Programmer decides when to allocate or free heap memory. + - Programmer must determine whether a pointer still points to valid + memory. + - Studies show, programmers make mistakes. + +- Full safety via automatic memory management at runtime: Java, Python, + Go, Haskell, ... + + - A runtime system ensures that memory is not freed until it can no + longer be referenced. + - Typically implemented with reference counting or garbage + collection. + +Rust offers a new mix: + + Full control *and* safety via compile time enforcement of correct + memory management. + +It does this with an explicit ownership concept. + +.. raw:: html + +--------- +Details +--------- + +This slide is intended to help students coming from other languages to +put Rust in context. + +- C must manage heap manually with ``malloc`` and ``free``. Common + errors include forgetting to call ``free``, calling it multiple times + for the same pointer, or dereferencing a pointer after the memory it + points to has been freed. + +- C++ has tools like smart pointers (``unique_ptr``, ``shared_ptr``) + that take advantage of language guarantees about calling destructors + to ensure memory is freed when a function returns. It is still quite + easy to mis-use these tools and create similar bugs to C. + +- Java, Go, and Python rely on the garbage collector to identify memory + that is no longer reachable and discard it. This guarantees that any + pointer can be dereferenced, eliminating use-after-free and other + classes of bugs. But, GC has a runtime cost and is difficult to tune + properly. + +Rust's ownership and borrowing model can, in many cases, get the +performance of C, with alloc and free operations precisely where they +are required TBD zero cost. It also provides tools similar to C++'s smart +pointers. When required, other options such as reference counting are +available, and there are even crates available to support runtime +garbage collection (not covered in this class). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst new file mode 100644 index 000000000..a196d4dae --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst @@ -0,0 +1,45 @@ +=========== +Ownership +=========== + +----------- +Ownership +----------- + +All variable bindings have a *scope* where they are valid and it is an +error to use a variable outside its scope: + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + struct Point(i32, i32); + + fn main() { + { + let p = Point(3, 4); + println!("x: {}", p.0); + } + println!("y: {}", p.1); + } + +We say that the variable *owns* the value. Every Rust value has +precisely one owner at all times. + +At the end of the scope, the variable is *dropped* and the data is +freed. A destructor can run here to free up resources. + +.. raw:: html + +--------- +Details +--------- + +Students familiar with garbage-collection implementations will know that +a garbage collector starts with a set of "roots" to find all reachable +memory. Rust's "single owner" principle is a similar idea. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/04_move.rst b/courses/comprehensive_rust_training/130_memory_management/04_move.rst new file mode 100644 index 000000000..23e7e1082 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/04_move.rst @@ -0,0 +1,195 @@ +================ +Move Semantics +================ + +---------------- +Move Semantics +---------------- + +An assignment will transfer *ownership* between variables: + +.. code:: rust,editable + + fn main() { + let s1: String = String::from("Hello!"); + let s2: String = s1; + println!("s2: {s2}"); + // println!("s1: {s1}"); + } + +- The assignment of ``s1`` to ``s2`` transfers ownership. +- When ``s1`` goes out of scope, nothing happens: it does not own + anything. +- When ``s2`` goes out of scope, the string data is freed. + +Before move to ``s2``: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -. + : : : : + : s1 : : : + : +-----------+-------+ : : +----+----+----+----+----+----+ : + : | ptr | o---+---+-----+-->| H | e | l | l | o | ! | : + : | len | 6 | : : +----+----+----+----+----+----+ : + : | capacity | 6 | : : : + : +-----------+-------+ : : : + : : `- - - - - - - - - - - - - - - - - - -' + : : + `- - - - - - - - - - - - - -' + +After move to ``s2``: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -. + : : : : + : s1 "(inaccessible)" : : : + : +-----------+-------+ : : +----+----+----+----+----+----+ : + : | ptr | o---+---+--+--+-->| H | e | l | l | o | ! | : + : | len | 6 | : | : +----+----+----+----+----+----+ : + : | capacity | 6 | : | : : + : +-----------+-------+ : | : : + : : | `- - - - - - - - - - - - - - - - - - -' + : s2 : | + : +-----------+-------+ : | + : | ptr | o---+---+--' + : | len | 6 | : + : | capacity | 6 | : + : +-----------+-------+ : + : : + `- - - - - - - - - - - - - -' + +When you pass a value to a function, the value is assigned to the +function parameter. This transfers ownership: + +.. code:: rust,editable + + fn say_hello(name: String) { + println!("Hello {name}") + } + + fn main() { + let name = String::from("Alice"); + say_hello(name); + // say_hello(name); + } + +.. raw:: html + +--------- +Details +--------- + +- Mention that this is the opposite of the defaults in C++, which + copies by value unless you use ``std::move`` (and the move + constructor is defined!). + +- It is only the ownership that moves. Whether any machine code is + generated to manipulate the data itself is a matter of optimization, + and such copies are aggressively optimized away. + +- Simple values (such as integers) can be marked ``Copy`` (see later + slides). + +- In Rust, clones are explicit (by using ``clone``). + +In the ``say_hello`` example: + +- With the first call to ``say_hello``, ``main`` gives up ownership of + ``name``. Afterwards, ``name`` cannot be used anymore within + ``main``. +- The heap memory allocated for ``name`` will be freed at the end of + the ``say_hello`` function. +- ``main`` can retain ownership if it passes ``name`` as a reference + (``&name``) and if ``say_hello`` accepts a reference as a parameter. +- Alternatively, ``main`` can pass a clone of ``name`` in the first + call (``name.clone()``). +- Rust makes it harder than C++ to inadvertently create copies by + making move semantics the default, and by forcing programmers to make + clones explicit. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +-------------------------------- +Defensive Copies in Modern C++ +-------------------------------- + +Modern C++ solves this differently: + +.. code:: cpp + + std::string s1 = "Cpp"; + std::string s2 = s1; // Duplicate the data in s1. + +- The heap data from ``s1`` is duplicated and ``s2`` gets its own + independent copy. +- When ``s1`` and ``s2`` go out of scope, they each free their own + memory. + +Before copy-assignment: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - -. + : : : : + : s1 : : : + : +-----------+-------+ : : +----+----+----+ : + : | ptr | o---+---+--+--+-->| C | p | p | : + : | len | 3 | : : +----+----+----+ : + : | capacity | 3 | : : : + : +-----------+-------+ : : : + : : `- - - - - - - - - - - -' + `- - - - - - - - - - - - - -' + +After copy-assignment: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - -. + : : : : + : s1 : : : + : +-----------+-------+ : : +----+----+----+ : + : | ptr | o---+---+--+--+-->| C | p | p | : + : | len | 3 | : : +----+----+----+ : + : | capacity | 3 | : : : + : +-----------+-------+ : : : + : : : : + : s2 : : : + : +-----------+-------+ : : +----+----+----+ : + : | ptr | o---+---+-----+-->| C | p | p | : + : | len | 3 | : : +----+----+----+ : + : | capacity | 3 | : : : + : +-----------+-------+ : : : + : : `- - - - - - - - - - - -' + `- - - - - - - - - - - - - -' + +Key points: + +- C++ has made a slightly different choice than Rust. Because ``=`` + copies data, the string data has to be cloned. Otherwise we would get + a double-free when either string goes out of scope. + +- C++ also has + ```std::move`` `__, + which is used to indicate when a value may be moved from. If the + example had been ``s2 = std::move(s1)``, no heap allocation would + take place. After the move, ``s1`` would be in a valid but + unspecified state. Unlike Rust, the programmer is allowed to keep + using ``s1``. + +- Unlike Rust, ``=`` in C++ can run arbitrary code as determined by the + type which is being copied or moved. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/05_clone.rst b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst new file mode 100644 index 000000000..a9a029b88 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst @@ -0,0 +1,45 @@ +======= +Clone +======= + +------- +Clone +------- + +Sometimes you *want* to make a copy of a value. The ``Clone`` trait +accomplishes this. + +.. code:: rust,editable + + fn say_hello(name: String) { + println!("Hello {name}") + } + + fn main() { + let name = String::from("Alice"); + say_hello(name.clone()); + say_hello(name); + } + +.. raw:: html + +--------- +Details +--------- + +- The idea of ``Clone`` is to make it easy to spot where heap + allocations are occurring. Look for ``.clone()`` and a few others + like ``vec!`` or ``Box::new``. + +- It's common to "clone your way out" of problems with the borrow + checker, and return later to try to optimize those clones away. + +- ``clone`` generally performs a deep copy of the value, meaning that + if you e.g. clone an array, all of the elements of the array are + cloned as well. + +- The behavior for ``clone`` is user-defined, so it can perform custom + cloning logic if needed. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst new file mode 100644 index 000000000..0e2c21589 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst @@ -0,0 +1,86 @@ +============ +Copy Types +============ + +------------ +Copy Types +------------ + +While move semantics are the default, certain types are copied by +default: + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let x = 42; + let y = x; + println!("x: {x}"); // would not be accessible if not Copy + println!("y: {y}"); + } + +These types implement the ``Copy`` trait. + +You can opt-in your own types to use copy semantics: + +.. raw:: html + + + +.. code:: rust,editable + + #[derive(Copy, Clone, Debug)] + struct Point(i32, i32); + + fn main() { + let p1 = Point(3, 4); + let p2 = p1; + println!("p1: {p1:?}"); + println!("p2: {p2:?}"); + } + +- After the assignment, both ``p1`` and ``p2`` own their own data. +- We can also use ``p1.clone()`` to explicitly copy the data. + +.. raw:: html + +--------- +Details +--------- + +Copying and cloning are not the same thing: + +- Copying refers to bitwise copies of memory regions and does not work + on arbitrary objects. +- Copying does not allow for custom logic (unlike copy constructors in + C++). +- Cloning is a more general operation and also allows for custom + behavior by implementing the ``Clone`` trait. +- Copying does not work on types that implement the ``Drop`` trait. + +In the above example, try the following: + +- Add a ``String`` field to ``struct Point``. It will not compile + because ``String`` is not a ``Copy`` type. +- Remove ``Copy`` from the ``derive`` attribute. The compiler error is + now in the ``println!`` for ``p1``. +- Show that it works if you clone ``p1`` instead. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +- Shared references are ``Copy``/``Clone``, mutable references are not. + This is because Rust requires that mutable references be exclusive, + so while it's valid to make a copy of a shared reference, creating a + copy of a mutable reference would violate Rust's borrowing rules. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst new file mode 100644 index 000000000..866c5ec6a --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst @@ -0,0 +1,73 @@ +==================== +The ``Drop`` Trait +==================== + +-------------------- +The ``Drop`` Trait +-------------------- + +Values which implement +```Drop`` `__ can +specify code to run when they go out of scope: + +.. code:: rust,editable + + struct Droppable { + name: &'static str, + } + + impl Drop for Droppable { + fn drop(&mut self) { + println!("Dropping {}", self.name); + } + } + + fn main() { + let a = Droppable { name: "a" }; + { + let b = Droppable { name: "b" }; + { + let c = Droppable { name: "c" }; + let d = Droppable { name: "d" }; + println!("Exiting block B"); + } + println!("Exiting block A"); + } + drop(a); + println!("Exiting main"); + } + +.. raw:: html + +--------- +Details +--------- + +- Note that ``std::mem::drop`` is not the same as + ``std::ops::Drop::drop``. +- Values are automatically dropped when they go out of scope. +- When a value is dropped, if it implements ``std::ops::Drop`` then its + ``Drop::drop`` implementation will be called. +- All its fields will then be dropped too, whether or not it implements + ``Drop``. +- ``std::mem::drop`` is just an empty function that takes any value. + The significance is that it takes ownership of the value, so at the + end of its scope it gets dropped. This makes it a convenient way to + explicitly drop values earlier than they would otherwise go out of + scope. + + - This can be useful for objects that do some work on ``drop``: + releasing locks, closing files, etc. + +Discussion points: + +- Why doesn't ``Drop::drop`` take ``self``? + + - Short-answer: If it did, ``std::mem::drop`` would be called at the + end of the block, resulting in another call to ``Drop::drop``, and + a stack overflow! + +- Try replacing ``drop(a)`` with ``a.drop()``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst new file mode 100644 index 000000000..869bfbf6e --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst @@ -0,0 +1,44 @@ +======================== +Exercise: Builder Type +======================== + +------------------------ +Exercise: Builder Type +------------------------ + +In this example, we will implement a complex data type that owns all of +its data. We will use the "builder pattern" to support building a new +value piece-by-piece, using convenience functions. + +Fill in the missing pieces. + +.. code:: rust,should_panic,editable + + {{#include exercise.rs:Package}} + {{#include exercise.rs:as_dependency}} + todo!("1") + } + } + + {{#include exercise.rs:PackageBuilder}} + {{#include exercise.rs:new}} + todo!("2") + } + + {{#include exercise.rs:version}} + + {{#include exercise.rs:authors}} + todo!("3") + } + + {{#include exercise.rs:dependency}} + todo!("4") + } + + {{#include exercise.rs:language}} + todo!("5") + } + + {{#include exercise.rs:build}} + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/140_smart_pointers.rst b/courses/comprehensive_rust_training/140_smart_pointers.rst new file mode 100644 index 000000000..ef73dfa51 --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -0,0 +1,39 @@ +**************** +Smart Pointers +**************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 140_smart_pointers/01_box.rst +.. include:: 140_smart_pointers/02_rc.rst +.. include:: 140_smart_pointers/03_trait_objects.rst +.. include:: 140_smart_pointers/04_exercise.rst diff --git a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst new file mode 100644 index 000000000..43b594729 --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -0,0 +1,106 @@ +============ +``Box`` +============ + +------------ +``Box`` +------------ + +```Box`` `__ is an +owned pointer to data on the heap: + +.. code:: rust,editable + + fn main() { + let five = Box::new(5); + println!("five: {}", *five); + } + +.. code:: bob + + Stack Heap + .- - - - - - -. .- - - - - - -. + : : : : + : five : : : + : +-----+ : : +-----+ : + : | o---|---+-----+-->| 5 | : + : +-----+ : : +-----+ : + : : : : + : : : : + `- - - - - - -' `- - - - - - -' + +``Box`` implements ``Deref``, which means that you can +`call methods from ``T`` directly on a +``Box`` `__. + +Recursive data types or data types with dynamic sizes cannot be stored +inline without a pointer indirection. ``Box`` accomplishes that +indirection: + +.. code:: rust,editable + + #[derive(Debug)] + enum List { + /// A non-empty list: first element and the rest of the list. + Element(T, Box>), + /// An empty list. + Nil, + } + + fn main() { + let list: List = + List::Element(1, Box::new(List::Element(2, Box::new(List::Nil)))); + println!("{list:?}"); + } + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - - . .- - - - - - - - - - - - - - - - - - - - - - - - -. + : : : : + : list : : : + : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : + : | Element | 1 | o--+----+-----+--->| Element | 2 | o--+--->| Nil | // | // | : + : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : + : : : : + : : : : + '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' + +.. raw:: html + +--------- +Details +--------- + +- ``Box`` is like ``std::unique_ptr`` in C++, except that it's + guaranteed to be not null. + +- A ``Box`` can be useful when you: + + - have a type whose size can't be known at compile time, but the + Rust compiler wants to know an exact size. + - want to transfer ownership of a large amount of data. To avoid + copying large amounts of data on the stack, instead store the data + on the heap in a ``Box`` so only the pointer is moved. + +- If ``Box`` was not used and we attempted to embed a ``List`` directly + into the ``List``, the compiler would not be able to compute a fixed + size for the struct in memory (the ``List`` would be of infinite + size). + +- ``Box`` solves this problem as it has the same size as a regular + pointer and just points at the next element of the ``List`` in the + heap. + +- Remove the ``Box`` in the List definition and show the compiler + error. We get the message "recursive without indirection", because + for data recursion, we have to use indirection, a ``Box`` or + reference of some kind, instead of storing the value directly. + +- Though ``Box`` looks like ``std::unique_ptr`` in C++, it cannot be + empty/null. This makes ``Box`` one of the types that allow the + compiler to optimize storage of some enums (the "niche + optimization"). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst new file mode 100644 index 000000000..a1a2f460c --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -0,0 +1,52 @@ +======== +``Rc`` +======== + +-------- +``Rc`` +-------- + +```Rc`` `__ is a +reference-counted shared pointer. Use this when you need to refer to the +same data from multiple places: + +.. code:: rust,editable + + use std::rc::Rc; + + fn main() { + let a = Rc::new(10); + let b = Rc::clone(&a); + + println!("a: {a}"); + println!("b: {b}"); + } + +- See ```Arc`` <../concurrency/shared-state/arc.md>`__ and + ```Mutex`` `__ + if you are in a multi-threaded context. +- You can *downgrade* a shared pointer into a + ```Weak`` `__ + pointer to create cycles that will get dropped. + +.. raw:: html + +--------- +Details +--------- + +- ``Rc``\ 's count ensures that its contained value is valid for as + long as there are references. +- ``Rc`` in Rust is like ``std::shared_ptr`` in C++. +- ``Rc::clone`` is cheap: it creates a pointer to the same allocation + and increases the reference count. Does not make a deep clone and can + generally be ignored when looking for performance issues in code. +- ``make_mut`` actually clones the inner value if necessary + ("clone-on-write") and returns a mutable reference. +- Use ``Rc::strong_count`` to check the reference count. +- ``Rc::downgrade`` gives you a *weakly reference-counted* object to + create cycles that will be dropped properly (likely in combination + with ``RefCell``). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst b/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst new file mode 100644 index 000000000..10eca0308 --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst @@ -0,0 +1,116 @@ +===================== +Owned Trait Objects +===================== + +--------------------- +Owned Trait Objects +--------------------- + +We previously saw how trait objects can be used with references, e.g +``&dyn Pet``. However, we can also use trait objects with smart pointers +like ``Box`` to create an owned trait object: ``Box``. + +.. code:: rust,editable + + struct Dog { + name: String, + age: i8, + } + struct Cat { + lives: i8, + } + + trait Pet { + fn talk(&self) -> String; + } + + impl Pet for Dog { + fn talk(&self) -> String { + format!("Woof, my name is {}!", self.name) + } + } + + impl Pet for Cat { + fn talk(&self) -> String { + String::from("Miau!") + } + } + + fn main() { + let pets: Vec> = vec![ + Box::new(Cat { lives: 9 }), + Box::new(Dog { name: String::from("Fido"), age: 5 }), + ]; + for pet in pets { + println!("Hello, who are you? {}", pet.talk()); + } + } + +Memory layout after allocating ``pets``: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - -. + : : : : + : "pets: Vec>" : : "data: Cat" +----+----+----+----+ : + : +-----------+-------+ : : +-------+-------+ | F | i | d | o | : + : | ptr | o---+-------+--. : | lives | 9 | +----+----+----+----+ : + : | len | 2 | : | : +-------+-------+ ^ : + : | capacity | 2 | : | : ^ | : + : +-----------+-------+ : | : | '-------. : + : : | : | data:"Dog"| : + : : | : | +-------+--|-------+ : + `- - - - - - - - - - - - - - - -' | : +---|-+-----+ | name | o, 4, 4 | : + `--+-->| o o | o o-|----->| age | 5 | : + : +-|---+-|---+ +-------+----------+ : + : | | : + `- - -| - - |- - - - - - - - - - - - - - - - -' + | | + | | "Program text" + .- - -| - - |- - - - - - - - - - - - - - - - -. + : | | vtable : + : | | +----------------------+ : + : | `----->| "::talk" | : + : | +----------------------+ : + : | vtable : + : | +----------------------+ : + : '----------->| "::talk" | : + : +----------------------+ : + : : + '- - - - - - - - - - - - - - - - - - - - - - -' + +.. raw:: html + +--------- +Details +--------- + +- Types that implement a given trait may be of different sizes. This + makes it impossible to have things like ``Vec`` in the + example above. + +- ``dyn Pet`` is a way to tell the compiler about a dynamically sized + type that implements ``Pet``. + +- In the example, ``pets`` is allocated on the stack and the vector + data is on the heap. The two vector elements are *fat pointers*: + + - A fat pointer is a double-width pointer. It has two components: a + pointer to the actual object and a pointer to the `virtual method + table `__ + (vtable) for the ``Pet`` implementation of that particular object. + - The data for the ``Dog`` named Fido is the ``name`` and ``age`` + fields. The ``Cat`` has a ``lives`` field. + +- Compare these outputs in the above example: + + .. code:: rust,ignore + + println!("{} {}", std::mem::size_of::(), std::mem::size_of::()); + println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>()); + println!("{}", std::mem::size_of::<&dyn Pet>()); + println!("{}", std::mem::size_of::>()); + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst new file mode 100644 index 000000000..e9bcba20f --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst @@ -0,0 +1,26 @@ +======================= +Exercise: Binary Tree +======================= + +----------------------- +Exercise: Binary Tree +----------------------- + +A binary tree is a tree-type data structure where every node has two +children (left and right). We will create a tree where each node stores +a value. For a given node N, all nodes in a N's left subtree contain +smaller values, and all nodes in N's right subtree will contain larger +values. + +Implement the following types, so that the given tests pass. + +Extra Credit: implement an iterator over a binary tree that returns the +values in order. + +.. code:: rust,editable,ignore + + {{#include exercise.rs:types}} + + // Implement `new`, `insert`, `len`, and `has` for `Subtree`. + + {{#include exercise.rs:tests}} diff --git a/courses/comprehensive_rust_training/150_borrowing.rst b/courses/comprehensive_rust_training/150_borrowing.rst new file mode 100644 index 000000000..afa79700e --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing.rst @@ -0,0 +1,40 @@ +*********** +Borrowing +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 150_borrowing/01_shared.rst +.. include:: 150_borrowing/02_borrowck.rst +.. include:: 150_borrowing/03_examples.rst +.. include:: 150_borrowing/04_interior_mutability.rst +.. include:: 150_borrowing/05_exercise.rst diff --git a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst new file mode 100644 index 000000000..b66dcef5d --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst @@ -0,0 +1,96 @@ +=================== +Borrowing a Value +=================== + +------------------- +Borrowing a Value +------------------- + +As we saw before, instead of transferring ownership when calling a +function, you can let a function *borrow* the value: + +.. raw:: html + + + +.. code:: rust,editable + + #[derive(Debug)] + struct Point(i32, i32); + + fn add(p1: &Point, p2: &Point) -> Point { + Point(p1.0 + p2.0, p1.1 + p2.1) + } + + fn main() { + let p1 = Point(3, 4); + let p2 = Point(10, 20); + let p3 = add(&p1, &p2); + println!("{p1:?} + {p2:?} = {p3:?}"); + } + +- The ``add`` function *borrows* two points and returns a new point. +- The caller retains ownership of the inputs. + +.. raw:: html + +--------- +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 +================= + +----------------- +More to Explore +----------------- + +Notes on stack returns and inlining: + +- Demonstrate that the return from ``add`` is cheap because the + compiler can eliminate the copy operation, by inlining the call to + add into main. Change the above code to print stack addresses and run + it on the + `Playground `__ + or look at the assembly in `Godbolt `__. + In the "DEBUG" optimization level, the addresses should change, while + they stay the same when changing to the "RELEASE" setting: + + .. raw:: html + + + + .. code:: rust,editable + + #[derive(Debug)] + struct Point(i32, i32); + + fn add(p1: &Point, p2: &Point) -> Point { + let p = Point(p1.0 + p2.0, p1.1 + p2.1); + println!("&p.0: {:p}", &p.0); + p + } + + pub fn main() { + let p1 = Point(3, 4); + let p2 = Point(10, 20); + let p3 = add(&p1, &p2); + println!("&p3.0: {:p}", &p3.0); + println!("{p1:?} + {p2:?} = {p3:?}"); + } + +- The Rust compiler can do automatic inlining, that can be disabled on + a function level with ``#[inline(never)]``. + +- Once disabled, the printed address will change on all optimization + levels. Looking at Godbolt or Playground, one can see that in this + case, the return of the value depends on the ABI, e.g. on amd64 the + two i32 that is making up the point will be returned in 2 registers + (eax and edx). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst new file mode 100644 index 000000000..d17e15bf7 --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst @@ -0,0 +1,83 @@ +================= +Borrow Checking +================= + +----------------- +Borrow Checking +----------------- + +Rust's *borrow checker* puts constraints on the ways you can borrow +values. We've already seen that a reference cannot *outlive* the value +it borrows: + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + fn main() { + let x_ref = { + let x = 10; + &x + }; + println!("x: {x_ref}"); + } + +There's also a second main rule that the borrow checker enforces: The +*aliasing* rule. 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. + +.. raw:: html + + + +.. code:: 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}"); + } + +.. raw:: html + +--------- +Details +--------- + +- The "outlives" rule was demonstrated previously when we first looked + at references. We review it here to show students that the borrow + checking is following a few different rules to validate borrowing. +- 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 code. 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." + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst new file mode 100644 index 000000000..406bf994d --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst @@ -0,0 +1,44 @@ +=============== +Borrow Errors +=============== + +--------------- +Borrow Errors +--------------- + +As a concrete example of how these borrowing rules prevent memory +errors, consider the case of modifying a collection while there are +references to its elements: + +.. code:: rust,editable,compile_fail + + fn main() { + let mut vec = vec![1, 2, 3, 4, 5]; + let elem = &vec[2]; + vec.push(6); + println!("{elem}"); + } + +Similarly, consider the case of iterator invalidation: + +.. code:: rust,editable,compile_fail + + fn main() { + let mut vec = vec![1, 2, 3, 4, 5]; + for elem in &vec { + vec.push(elem * 2); + } + } + +.. raw:: html + +--------- +Details +--------- + +- In both of these cases, modifying the collection by pushing new + elements into it can potentially invalidate existing references to + the collection's elements if the collection has to reallocate. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst new file mode 100644 index 000000000..16c1e2002 --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst @@ -0,0 +1,30 @@ +===================== +Interior Mutability +===================== + +--------------------- +Interior Mutability +--------------------- + +In some situations, it's necessary to modify data behind a shared +(read-only) reference. For example, a shared data structure might have +an internal cache, and wish to update that cache from read-only methods. + +The "interior mutability" pattern allows exclusive (mutable) access +behind a shared reference. The standard library provides several ways to +do this, all while still ensuring safety, typically by performing a +runtime check. + +.. raw:: html + +--------- +Details +--------- + +The main thing to take away from this slide is that Rust provides *safe* +ways to modify data behind a shared reference. There are a variety of +ways to ensure that safety, and the next sub-slides present a few of +them. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst new file mode 100644 index 000000000..bae5268bd --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst @@ -0,0 +1,28 @@ +============================= +Exercise: Health Statistics +============================= + +----------------------------- +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 method: + +.. code:: 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}} diff --git a/courses/comprehensive_rust_training/160_lifetimes.rst b/courses/comprehensive_rust_training/160_lifetimes.rst new file mode 100644 index 000000000..5318fc755 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes.rst @@ -0,0 +1,39 @@ +*********** +Lifetimes +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 160_lifetimes/01_lifetime_annotations.rst +.. include:: 160_lifetimes/02_lifetime_elision.rst +.. include:: 160_lifetimes/03_struct_lifetimes.rst +.. include:: 160_lifetimes/04_exercise.rst diff --git a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst new file mode 100644 index 000000000..3bb19ba62 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst @@ -0,0 +1,76 @@ +====================== +Lifetime Annotations +====================== + +---------------------- +Lifetime Annotations +---------------------- + +A reference has a *lifetime*, which must not "outlive" the value it +refers to. This is verified by the borrow checker. + +The lifetime can be implicit - this is what we have seen so far. +Lifetimes can also be explicit: ``&'a Point``, ``&'document str``. +Lifetimes start with ``'`` and ``'a`` is a typical default name. Read +``&'a Point`` as "a borrowed ``Point`` which is valid for at least the +lifetime ``a``". + +Lifetimes are always inferred by the compiler: you cannot assign a +lifetime yourself. Explicit lifetime annotations create constraints +where there is ambiguity; the compiler verifies that there is a valid +solution. + +Lifetimes become more complicated when considering passing values to and +returning values from functions. + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + #[derive(Debug)] + struct Point(i32, i32); + + fn left_most(p1: &Point, p2: &Point) -> &Point { + if p1.0 < p2.0 { + p1 + } else { + p2 + } + } + + fn main() { + let p1: Point = Point(10, 10); + let p2: Point = Point(20, 20); + let p3 = left_most(&p1, &p2); // What is the lifetime of p3? + println!("p3: {p3:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +In this example, the compiler does not know what lifetime to infer for +``p3``. Looking inside the function body shows that it can only safely +assume that ``p3``\ 's lifetime is the shorter of ``p1`` and ``p2``. But +just like types, Rust requires explicit annotations of lifetimes on +function arguments and return values. + +Add ``'a`` appropriately to ``left_most``: + +.. code:: rust,ignore + + fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { + +This says, "given p1 and p2 which both outlive ``'a``, the return value +lives for at least ``'a``. + +In common cases, lifetimes can be elided, as described on the next +slide. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst new file mode 100644 index 000000000..1f36966c9 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst @@ -0,0 +1,79 @@ +============================= +Lifetimes in Function Calls +============================= + +----------------------------- +Lifetimes in Function Calls +----------------------------- + +Lifetimes for function arguments and return values must be fully +specified, but Rust allows lifetimes to be elided in most cases with `a +few simple +rules `__. This +is not inference TBD it is just a syntactic shorthand. + +- Each argument which does not have a lifetime annotation is given one. +- If there is only one argument lifetime, it is given to all + un-annotated return values. +- If there are multiple argument lifetimes, but the first one is for + ``self``, that lifetime is given to all un-annotated return values. + +.. code:: rust,editable + + #[derive(Debug)] + struct Point(i32, i32); + + fn cab_distance(p1: &Point, p2: &Point) -> i32 { + (p1.0 - p2.0).abs() + (p1.1 - p2.1).abs() + } + + fn nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> { + let mut nearest = None; + for p in points { + if let Some((_, nearest_dist)) = nearest { + let dist = cab_distance(p, query); + if dist < nearest_dist { + nearest = Some((p, dist)); + } + } else { + nearest = Some((p, cab_distance(p, query))); + }; + } + nearest.map(|(p, _)| p) + } + + fn main() { + let points = &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1)]; + println!("{:?}", nearest(points, &Point(0, 2))); + } + +.. raw:: html + +--------- +Details +--------- + +In this example, ``cab_distance`` is trivially elided. + +The ``nearest`` function provides another example of a function with +multiple references in its arguments that requires explicit annotation. + +Try adjusting the signature to "lie" about the lifetimes returned: + +.. code:: rust,ignore + + fn nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> { + +This won't compile, demonstrating that the annotations are checked for +validity by the compiler. Note that this is not the case for raw +pointers (unsafe), and this is a common source of errors with unsafe +Rust. + +Students may ask when to use lifetimes. Rust borrows *always* have +lifetimes. Most of the time, elision and type inference mean these don't +need to be written out. In more complicated cases, lifetime annotations +can help resolve ambiguity. Often, especially when prototyping, it's +easier to just work with owned data by cloning values where necessary. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst new file mode 100644 index 000000000..6b8a6bfd4 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst @@ -0,0 +1,52 @@ +============================== +Lifetimes in Data Structures +============================== + +------------------------------ +Lifetimes in Data Structures +------------------------------ + +If a data type stores borrowed data, it must be annotated with a +lifetime: + +.. code:: rust,editable + + #[derive(Debug)] + struct Highlight<'doc>(&'doc str); + + fn erase(text: String) { + println!("Bye {text}!"); + } + + fn main() { + let text = String::from("The quick brown fox jumps over the lazy dog."); + let fox = Highlight(&text[4..19]); + let dog = Highlight(&text[35..43]); + // erase(text); + println!("{fox:?}"); + println!("{dog:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- In the above example, the annotation on ``Highlight`` enforces that + the data underlying the contained ``&str`` lives at least as long as + any instance of ``Highlight`` that uses that data. +- If ``text`` is consumed before the end of the lifetime of ``fox`` (or + ``dog``), the borrow checker throws an error. +- Types with borrowed data force users to hold on to the original data. + This can be useful for creating lightweight views, but it generally + makes them somewhat harder to use. +- When possible, make data structures own their data directly. +- Some structs with multiple references inside can have more than one + lifetime annotation. This can be necessary if there is a need to + describe lifetime relationships between the references themselves, in + addition to the lifetime of the struct itself. Those are very + advanced use cases. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst new file mode 100644 index 000000000..57341ecf0 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst @@ -0,0 +1,120 @@ +============================ +Exercise: Protobuf Parsing +============================ + +---------------------------- +Exercise: Protobuf Parsing +---------------------------- + +In this exercise, you will build a parser for the `protobuf binary +encoding `__. Don't +worry, it's simpler than it seems! This illustrates a common parsing +pattern, passing slices of data. The underlying data itself is never +copied. + +Fully parsing a protobuf message requires knowing the types of the +fields, indexed by their field numbers. That is typically provided in a +``proto`` file. In this exercise, we'll encode that information into +``match`` statements in functions that get called for each field. + +We'll use the following proto: + +.. code:: proto + + message PhoneNumber { + optional string number = 1; + optional string type = 2; + } + + message Person { + optional string name = 1; + optional int32 id = 2; + repeated PhoneNumber phones = 3; + } + +---------- +Messages +---------- + +A proto message is encoded as a series of fields, one after the next. +Each is implemented as a "tag" followed by the value. The tag contains a +field number (e.g., ``2`` for the ``id`` field of a ``Person`` message) +and a wire type defining how the payload should be determined from the +byte stream. These are combined into a single integer, as decoded in +``unpack_tag`` below. + +-------- +Varint +-------- + +Integers, including the tag, are represented with a variable-length +encoding called VARINT. Luckily, ``parse_varint`` is defined for you +below. + +------------ +Wire Types +------------ + +Proto defines several wire types, only two of which are used in this +exercise. + +The ``Varint`` wire type contains a single varint, and is used to encode +proto values of type ``int32`` such as ``Person.id``. + +The ``Len`` wire type contains a length expressed as a varint, followed +by a payload of that number of bytes. This is used to encode proto +values of type ``string`` such as ``Person.name``. It is also used to +encode proto values containing sub-messages such as ``Person.phones``, +where the payload contains an encoding of the sub-message. + +---------- +Exercise +---------- + +The given code also defines callbacks to handle ``Person`` and +``PhoneNumber`` fields, and to parse a message into a series of calls to +those callbacks. + +What remains for you is to implement the ``parse_field`` function and +the ``ProtoMessage`` trait for ``Person`` and ``PhoneNumber``. + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + {{#include exercise.rs:preliminaries }} + + + {{#include exercise.rs:parse_field }} + _ => todo!("Based on the wire type, build a Field, consuming as many bytes as necessary.") + }; + todo!("Return the field, and any un-consumed bytes.") + } + + {{#include exercise.rs:parse_message }} + + {{#include exercise.rs:message_phone_number_type}} + + {{#include exercise.rs:message_person_type}} + + // TODO: Implement ProtoMessage for Person and PhoneNumber. + + {{#include exercise.rs:main }} + +.. raw:: html + +--------- +Details +--------- + +- In this exercise there are various cases where protobuf parsing might + fail, e.g. if you try to parse an ``i32`` when there are fewer than 4 + bytes left in the data buffer. In normal Rust code we'd handle this + with the ``Result`` enum, but for simplicity in this exercise we + panic if any errors are encountered. On day 4 we'll cover error + handling in Rust in more detail. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators.rst b/courses/comprehensive_rust_training/170_iterators.rst new file mode 100644 index 000000000..a5f89d40f --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators.rst @@ -0,0 +1,41 @@ +*********** +Iterators +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 170_iterators/01_motivation.rst +.. include:: 170_iterators/02_iterator.rst +.. include:: 170_iterators/03_helpers.rst +.. include:: 170_iterators/04_collect.rst +.. include:: 170_iterators/05_intoiterator.rst +.. include:: 170_iterators/06_exercise.rst diff --git a/courses/comprehensive_rust_training/170_iterators/01_motivation.rst b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst new file mode 100644 index 000000000..2d4938485 --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst @@ -0,0 +1,73 @@ +====================== +Motivating Iterators +====================== + +---------------------- +Motivating Iterators +---------------------- + +If you want to iterate over the contents of an array, you'll need to +define: + +- Some state to keep track of where you are in the iteration process, + e.g. an index. +- A condition to determine when iteration is done. +- Logic for updating the state of iteration each loop. +- Logic for fetching each element using that iteration state. + +In a C-style for loop you declare these things directly: + +.. code:: c,editable + + for (int i = 0; i < array_len; i += 1) { + int elem = array[i]; + } + +In Rust we bundle this state and logic together into an object known as +an "iterator". + +.. raw:: html + +--------- +Details +--------- + +- This slide provides context for what Rust iterators do under the + hood. We use the (hopefully) familiar construct of a C-style ``for`` + loop to show how iteration requires some state and some logic, that + way on the next slide we can show how an iterator bundles these + together. + +- Rust doesn't have a C-style ``for`` loop, but we can express the same + thing with ``while``: + + .. code:: rust,editable + + let array = [2, 4, 6, 8]; + let mut i = 0; + while i < array.len() { + let elem = array[i]; + i += 1; + } + +----------------- +More to Explore +----------------- + +There's another way to express array iteration using ``for`` in C and +C++: You can use a pointer to the front and a pointer to the end of the +array and then compare those pointers to determine when the loop should +end. + +.. code:: c,editable + + for (int *ptr = array; ptr < array + len; ptr += 1) { + int elem = *ptr; + } + +If students ask, you can point out that this is how Rust's slice and +array iterators work under the hood (though implemented as a Rust +iterator). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst new file mode 100644 index 000000000..97847f1bb --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst @@ -0,0 +1,78 @@ +==================== +``Iterator`` Trait +==================== + +-------------------- +``Iterator`` Trait +-------------------- + +The +```Iterator`` `__ +trait defines how an object can be used to produce a sequence of values. +For example, if we wanted to create an iterator that can produce the +elements of a slice it might look something like this: + +.. code:: rust,editable + + struct SliceIter<'s> { + slice: &'s [i32], + i: usize, + } + + impl<'s> Iterator for SliceIter<'s> { + type Item = &'s i32; + + fn next(&mut self) -> Option { + if self.i == self.slice.len() { + None + } else { + let next = &self.slice[self.i]; + self.i += 1; + Some(next) + } + } + } + + fn main() { + let slice = &[2, 4, 6, 8]; + let iter = SliceIter { slice, i: 0 }; + for elem in iter { + println!("elem: {elem}"); + } + } + +.. raw:: html + +--------- +Details +--------- + +- The ``SliceIter`` example implements the same logic as the C-style + ``for`` loop demonstrated on the last slide. + +- Point out to the students that iterators are lazy: Creating the + iterator just initializes the struct but does not otherwise do any + work. No work happens until the ``next`` method is called. + +- Iterators don't need to be finite! It's entirely valid to have an + iterator that will produce values forever. For example, a half open + range like ``0..`` will keep going until integer overflow occurs. + +----------------- +More to Explore +----------------- + +- The "real" version of ``SliceIter`` is the + ```slice::Iter`` `__ + type in the standard library, however the real version uses pointers + under the hood instead of an index in order to eliminate bounds + checks. + +- The ``SliceIter`` example is a good example of a struct that contains + a reference and therefore uses lifetime annotations. + +- You can also demonstrate adding a generic parameter to ``SliceIter`` + to allow it to work with any kind of slice (not just ``&[i32]``). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst new file mode 100644 index 000000000..1246fd39d --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst @@ -0,0 +1,52 @@ +============================= +``Iterator`` Helper Methods +============================= + +----------------------------- +``Iterator`` Helper Methods +----------------------------- + +In addition to the ``next`` method that defines how an iterator behaves, +the ``Iterator`` trait provides 70+ helper methods that can be used to +build customized iterators. + +.. code:: rust,editable + + let result: i32 = (1..=10) // Create a range from 1 to 10 + .filter(|&x| x % 2 == 0) // Keep only even numbers + .map(|x| x * x) // Square each number + .sum(); // Sum up all the squared numbers + + println!("The sum of squares of even numbers from 1 to 10 is: {}", result); + +.. raw:: html + +--------- +Details +--------- + +- The ``Iterator`` trait implements many common functional programming + operations over collections (e.g. ``map``, ``filter``, ``reduce``, + etc). This is the trait where you can find all the documentation + about them. + +- Many of these helper methods take the original iterator and produce a + new iterator with different behavior. These are know as "iterator + adapter methods". + +- Some methods, like ``sum`` and ``count``, consume the iterator and + pull all of the elements out of it. + +- These methods are designed to be chained together so that it's easy + to build a custom iterator that does exactly what you need. + +----------------- +More to Explore +----------------- + +- Rust's iterators are extremely efficient and highly optimizable. Even + complex iterators made by combining many adapter methods will still + result in code as efficient as equivalent imperative implementations. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/04_collect.rst b/courses/comprehensive_rust_training/170_iterators/04_collect.rst new file mode 100644 index 000000000..8295c9423 --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/04_collect.rst @@ -0,0 +1,61 @@ +============= +``collect`` +============= + +------------- +``collect`` +------------- + +The +```collect`` `__ +method lets you build a collection from an +```Iterator`` `__. + +.. code:: rust,editable + + fn main() { + let primes = vec![2, 3, 5, 7]; + let prime_squares = primes.into_iter().map(|p| p * p).collect::>(); + println!("prime_squares: {prime_squares:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- Any iterator can be collected in to a ``Vec``, ``VecDeque``, or + ``HashSet``. Iterators that produce key-value pairs (i.e. a + two-element tuple) can also be collected into ``HashMap`` and + ``BTreeMap``. + +Show the students the definition for ``collect`` in the standard library +docs. There are two ways to specify the generic type ``B`` for this +method: + +- With the "turbofish": ``some_iterator.collect::()``, + as shown. The ``_`` shorthand used here lets Rust infer the type of + the ``Vec`` elements. +- With type inference: + ``let prime_squares: Vec<_> = some_iterator.collect()``. Rewrite the + example to use this form. + +----------------- +More to Explore +----------------- + +- If students are curious about how this works, you can bring up the + ```FromIterator`` `__ + trait, which defines how each type of collection gets built from an + iterator. +- In addition to the basic implementations of ``FromIterator`` for + ``Vec``, ``HashMap``, etc., there are also more specialized + implementations which let you do cool things like convert an + ``Iterator>`` into a ``Result, E>``. +- The reason type annotations are often needed with ``collect`` is + because it's generic over its return type. This makes it harder for + the compiler to infer the correct type in a lot of cases. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst new file mode 100644 index 000000000..4c94c3953 --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst @@ -0,0 +1,97 @@ +================== +``IntoIterator`` +================== + +------------------ +``IntoIterator`` +------------------ + +The ``Iterator`` trait tells you how to *iterate* once you have created +an iterator. The related trait +```IntoIterator`` `__ +defines how to create an iterator for a type. It is used automatically +by the ``for`` loop. + +.. code:: rust,editable + + struct Grid { + x_coords: Vec, + y_coords: Vec, + } + + impl IntoIterator for Grid { + type Item = (u32, u32); + type IntoIter = GridIter; + fn into_iter(self) -> GridIter { + GridIter { grid: self, i: 0, j: 0 } + } + } + + struct GridIter { + grid: Grid, + i: usize, + j: usize, + } + + impl Iterator for GridIter { + type Item = (u32, u32); + + fn next(&mut self) -> Option<(u32, u32)> { + if self.i >= self.grid.x_coords.len() { + self.i = 0; + self.j += 1; + if self.j >= self.grid.y_coords.len() { + return None; + } + } + let res = Some((self.grid.x_coords[self.i], self.grid.y_coords[self.j])); + self.i += 1; + res + } + } + + fn main() { + let grid = Grid { x_coords: vec![3, 5, 7, 9], y_coords: vec![10, 20, 30, 40] }; + for (x, y) in grid { + println!("point = {x}, {y}"); + } + } + +.. raw:: html + +--------- +Details +--------- + +- ``IntoIterator`` is the trait that makes for loops work. It is + implemented by collection types such as ``Vec`` and references to + them such as ``&Vec`` and ``&[T]``. Ranges also implement it. This + is why you can iterate over a vector with + ``for i in some_vec { .. }`` but ``some_vec.next()`` doesn't exist. + +Click through to the docs for ``IntoIterator``. Every implementation of +``IntoIterator`` must declare two types: + +- ``Item``: the type to 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`` + +The example iterates over all combinations of x and y coordinates. + +Try iterating over the grid twice in ``main``. Why does this fail? Note +that ``IntoIterator::into_iter`` takes ownership of ``self``. + +Fix this issue by implementing ``IntoIterator`` for ``&Grid`` and +storing a reference to the ``Grid`` in ``GridIter``. + +The same problem can occur for standard library types: +``for e in some_vector`` will take ownership of ``some_vector`` and +iterate over owned elements from that vector. Use +``for e in &some_vector`` instead, to iterate over references to +elements of ``some_vector``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst new file mode 100644 index 000000000..32cc53b35 --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst @@ -0,0 +1,24 @@ +==================================== +Exercise: Iterator Method Chaining +==================================== + +------------------------------------ +Exercise: Iterator Method Chaining +------------------------------------ + +In this exercise, you will need to find and use some of the provided +methods in the +```Iterator`` `__ +trait to implement a complex calculation. + +Copy the following code to https://play.rust-lang.org/ and make the +tests pass. Use an iterator expression and ``collect`` the result to +construct the return value. + +.. code:: rust + + {{#include exercise.rs:offset_differences}} + unimplemented!() + } + + {{#include exercise.rs:unit-tests}} diff --git a/courses/comprehensive_rust_training/180_modules.rst b/courses/comprehensive_rust_training/180_modules.rst new file mode 100644 index 000000000..f76f8e3b1 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules.rst @@ -0,0 +1,41 @@ +********* +Modules +********* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 180_modules/01_modules.rst +.. include:: 180_modules/02_filesystem.rst +.. include:: 180_modules/03_visibility.rst +.. include:: 180_modules/04_encapsulation.rst +.. include:: 180_modules/05_paths.rst +.. include:: 180_modules/06_exercise.rst diff --git a/courses/comprehensive_rust_training/180_modules/01_modules.rst b/courses/comprehensive_rust_training/180_modules/01_modules.rst new file mode 100644 index 000000000..d56709e5b --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/01_modules.rst @@ -0,0 +1,46 @@ +========= +Modules +========= + +--------- +Modules +--------- + +We have seen how ``impl`` blocks let us namespace functions to a type. + +Similarly, ``mod`` lets us namespace types and functions: + +.. code:: rust,editable + + mod foo { + pub fn do_something() { + println!("In the foo module"); + } + } + + mod bar { + pub fn do_something() { + println!("In the bar module"); + } + } + + fn main() { + foo::do_something(); + bar::do_something(); + } + +.. raw:: html + +--------- +Details +--------- + +- Packages provide functionality and include a ``Cargo.toml`` file that + describes how to build a bundle of 1+ crates. +- Crates are a tree of modules, where a binary crate creates an + executable and a library crate compiles to a library. +- Modules define organization, scope, and are the focus of this + section. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst new file mode 100644 index 000000000..0246025f7 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst @@ -0,0 +1,85 @@ +====================== +Filesystem Hierarchy +====================== + +---------------------- +Filesystem Hierarchy +---------------------- + +Omitting the module content will tell Rust to look for it in another +file: + +.. code:: rust,editable,compile_fail + + mod garden; + +This tells Rust that the ``garden`` module content is found at +``src/garden.rs``. Similarly, a ``garden::vegetables`` module can be +found at ``src/garden/vegetables.rs``. + +The ``crate`` root is in: + +- ``src/lib.rs`` (for a library crate) +- ``src/main.rs`` (for a binary crate) + +Modules defined in files can be documented, too, using "inner doc +comments". These document the item that contains them TBD in this case, a +module. + +.. code:: rust,editable,compile_fail + + //! This module implements the garden, including a highly performant germination + //! implementation. + + // Re-export types from this module. + pub use garden::Garden; + pub use seeds::SeedPacket; + + /// Sow the given seed packets. + pub fn sow(seeds: Vec) { + todo!() + } + + /// Harvest the produce in the garden that is ready. + pub fn harvest(garden: &mut Garden) { + todo!() + } + +.. raw:: html + +--------- +Details +--------- + +- Before Rust 2018, modules needed to be located at ``module/mod.rs`` + instead of ``module.rs``, and this is still a working alternative for + editions after 2018. + +- The main reason to introduce ``filename.rs`` as alternative to + ``filename/mod.rs`` was because many files named ``mod.rs`` can be + hard to distinguish in IDEs. + +- Deeper nesting can use folders, even if the main module is a file: + + .. code:: ignore + + src/ + TBDTBDTBD main.rs + TBDTBDTBD top_module.rs + TBDTBDTBD top_module/ + TBDTBDTBD sub_module.rs + +- The place rust will look for modules can be changed with a compiler + directive: + + .. code:: rust,ignore + + #[path = "some/path.rs"] + mod some_module; + + This is useful, for example, if you would like to place tests for a + module in a file named ``some_module_test.rs``, similar to the + convention in Go. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/03_visibility.rst b/courses/comprehensive_rust_training/180_modules/03_visibility.rst new file mode 100644 index 000000000..46d431312 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/03_visibility.rst @@ -0,0 +1,62 @@ +============ +Visibility +============ + +------------ +Visibility +------------ + +Modules are a privacy boundary: + +- Module items are private by default (hides implementation details). +- Parent and sibling items are always visible. +- In other words, if an item is visible in module ``foo``, it's visible + in all the descendants of ``foo``. + +.. code:: rust,editable + + mod outer { + fn private() { + println!("outer::private"); + } + + pub fn public() { + println!("outer::public"); + } + + mod inner { + fn private() { + println!("outer::inner::private"); + } + + pub fn public() { + println!("outer::inner::public"); + super::private(); + } + } + } + + fn main() { + outer::public(); + } + +.. raw:: html + +--------- +Details +--------- + +- Use the ``pub`` keyword to make modules public. + +Additionally, there are advanced ``pub(...)`` specifiers to restrict the +scope of public visibility. + +- See the `Rust + Reference `__. +- Configuring ``pub(crate)`` visibility is a common pattern. +- Less commonly, you can give visibility to a specific path. +- In any case, visibility must be granted to an ancestor module (and + all of its descendants). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst new file mode 100644 index 000000000..cb9b6f448 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst @@ -0,0 +1,90 @@ +============================== +Visibility and Encapsulation +============================== + +------------------------------ +Visibility and Encapsulation +------------------------------ + +Like with items in a module, struct fields are also private by default. +Private fields are likewise visible within the rest of the module +(including child modules). This allows us to encapsulate implementation +details of struct, controlling what data and functionality is visible +externally. + +.. code:: rust,editable + + use outer::Foo; + + mod outer { + pub struct Foo { + pub val: i32, + is_big: bool, + } + + impl Foo { + pub fn new(val: i32) -> Self { + Self { val, is_big: val > 100 } + } + } + + pub mod inner { + use super::Foo; + + pub fn print_foo(foo: &Foo) { + println!("Is {} big? {}", foo.val, foo.is_big); + } + } + } + + fn main() { + let foo = Foo::new(42); + println!("foo.val = {}", foo.val); + // let foo = Foo { val: 42, is_big: true }; + + outer::inner::print_foo(&foo); + // println!("Is {} big? {}", foo.val, foo.is_big); + } + +.. raw:: html + +--------- +Details +--------- + +- This slide demonstrates how privacy in structs is module-based. + Students coming from object oriented languages may be used to types + being the encapsulation boundary, so this demonstrates how Rust + behaves differently while showing how we can still achieve + encapsulation. + +- Note how the ``is_big`` field is fully controlled by ``Foo``, + allowing ``Foo`` to control how it's initialized and enforce any + invariants it needs to (e.g. that ``is_big`` is only ``true`` if + ``val > 100``). + +- Point out how helper functions can be defined in the same module + (including child modules) in order to get access to the type's + private fields/methods. + +- The first commented out line demonstrates that you cannot initialize + a struct with private fields. The second one demonstrates that you + also can't directly access private fields. + +- Enums do not support privacy: Variants and data within those variants + is always public. + +----------------- +More to Explore +----------------- + +- If students want more information about privacy (or lack thereof) in + enums, you can bring up ``#[doc_hidden]`` and ``#[non_exhaustive]`` + and show how they're used to limit what can be done with an enum. + +- Module privacy still applies when there are ``impl`` blocks in other + modules `(example in the + playground) `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/05_paths.rst b/courses/comprehensive_rust_training/180_modules/05_paths.rst new file mode 100644 index 000000000..a0f41faab --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/05_paths.rst @@ -0,0 +1,63 @@ +================== +use, super, self +================== + +------------------ +use, super, self +------------------ + +A module can bring symbols from another module into scope with ``use``. +You will typically see something like this at the top of each module: + +.. code:: rust,editable + + use std::collections::HashSet; + use std::process::abort; + +------- +Paths +------- + +Paths are resolved as follows: + +1. As a relative path: + + - ``foo`` or ``self::foo`` refers to ``foo`` in the current module, + - ``super::foo`` refers to ``foo`` in the parent module. + +2. As an absolute path: + + - ``crate::foo`` refers to ``foo`` in the root of the current crate, + - ``bar::foo`` refers to ``foo`` in the ``bar`` crate. + +.. raw:: html + +--------- +Details +--------- + +- It is common to "re-export" symbols at a shorter path. For example, + the top-level ``lib.rs`` in a crate might have + + .. code:: rust,ignore + + mod storage; + + pub use storage::disk::DiskStorage; + pub use storage::network::NetworkStorage; + + making ``DiskStorage`` and ``NetworkStorage`` available to other + crates with a convenient, short path. + +- For the most part, only items that appear in a module need to be + ``use``\ 'd. However, a trait must be in scope to call any methods on + that trait, even if a type implementing that trait is already in + scope. For example, to use the ``read_to_string`` method on a type + implementing the ``Read`` trait, you need to ``use std::io::Read``. + +- The ``use`` statement can have a wildcard: ``use std::io::*``. This + is discouraged because it is not clear which items are imported, and + those might change over time. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/06_exercise.rst b/courses/comprehensive_rust_training/180_modules/06_exercise.rst new file mode 100644 index 000000000..d4b094ad5 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/06_exercise.rst @@ -0,0 +1,53 @@ +===================================== +Exercise: Modules for a GUI Library +===================================== + +------------------------------------- +Exercise: Modules for a GUI Library +------------------------------------- + +In this exercise, you will reorganize a small GUI Library +implementation. This library defines a ``Widget`` trait and a few +implementations of that trait, as well as a ``main`` function. + +It is typical to put each type or set of closely-related types into its +own module, so each widget type should get its own module. + +------------- +Cargo Setup +------------- + +The Rust playground only supports one file, so you will need to make a +Cargo project on your local filesystem: + +.. code:: shell + + cargo init gui-modules + cd gui-modules + cargo run + +Edit the resulting ``src/main.rs`` to add ``mod`` statements, and add +additional files in the ``src`` directory. + +-------- +Source +-------- + +Here's the single-module implementation of the GUI library: + +.. code:: rust + + {{#include exercise.rs:single-module}} + +.. raw:: html + +--------- +Details +--------- + +Encourage students to divide the code in a way that feels natural for +them, and get accustomed to the required ``mod``, ``use``, and ``pub`` +declarations. Afterward, discuss what organizations are most idiomatic. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/190_testing.rst b/courses/comprehensive_rust_training/190_testing.rst new file mode 100644 index 000000000..3b2d61dbe --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing.rst @@ -0,0 +1,39 @@ +********* +Testing +********* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 190_testing/01_unit_tests.rst +.. include:: 190_testing/02_other.rst +.. include:: 190_testing/03_lints.rst +.. include:: 190_testing/04_exercise.rst diff --git a/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst new file mode 100644 index 000000000..a69211bfd --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst @@ -0,0 +1,56 @@ +============ +Unit Tests +============ + +------------ +Unit Tests +------------ + +Rust and Cargo come with a simple unit test framework. Tests are marked +with ``#[test]``. Unit tests are often put in a nested ``tests`` module, +using ``#[cfg(test)]`` to conditionally compile them only when building +tests. + +.. code:: rust,editable,ignore + + fn first_word(text: &str) -> &str { + match text.find(' ') { + Some(idx) => &text[..idx], + None => &text, + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_empty() { + assert_eq!(first_word(""), ""); + } + + #[test] + fn test_single_word() { + assert_eq!(first_word("Hello"), "Hello"); + } + + #[test] + fn test_multiple_words() { + assert_eq!(first_word("Hello World"), "Hello"); + } + } + +- This lets you unit test private helpers. +- The ``#[cfg(test)]`` attribute is only active when you run + ``cargo test``. + +.. raw:: html + +--------- +Details +--------- + +Run the tests in the playground in order to show their results. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/190_testing/02_other.rst b/courses/comprehensive_rust_training/190_testing/02_other.rst new file mode 100644 index 000000000..fbf56faa3 --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing/02_other.rst @@ -0,0 +1,53 @@ +====================== +Other Types of Tests +====================== + +---------------------- +Other Types of Tests +---------------------- + +------------------- +Integration Tests +------------------- + +If you want to test your library as a client, use an integration test. + +Create a ``.rs`` file under ``tests/``: + +.. code:: rust,ignore + + // tests/my_library.rs + use my_library::init; + + #[test] + fn test_init() { + assert!(init().is_ok()); + } + +These tests only have access to the public API of your crate. + +--------------------- +Documentation Tests +--------------------- + +Rust has built-in support for documentation tests: + +.. code:: rust + + /// Shortens a string to the given length. + /// + /// ``` + /// # use playground::shorten_string; + /// assert_eq!(shorten_string("Hello World", 5), "Hello"); + /// assert_eq!(shorten_string("Hello World", 20), "Hello World"); + /// ``` + pub fn shorten_string(s: &str, length: usize) -> &str { + &s[..std::cmp::min(length, s.len())] + } + +- Code blocks in ``///`` comments are automatically seen as Rust code. +- The code will be compiled and executed as part of ``cargo test``. +- Adding ``#`` in the code will hide it from the docs, but will still + compile/run it. +- Test the above code on the `Rust + Playground `__. diff --git a/courses/comprehensive_rust_training/190_testing/03_lints.rst b/courses/comprehensive_rust_training/190_testing/03_lints.rst new file mode 100644 index 000000000..510dca137 --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing/03_lints.rst @@ -0,0 +1,39 @@ +=========================== +Compiler Lints and Clippy +=========================== + +--------------------------- +Compiler Lints and Clippy +--------------------------- + +The Rust compiler produces fantastic error messages, as well as helpful +built-in lints. `Clippy `__ provides +even more lints, organized into groups that can be enabled per-project. + +.. code:: rust,editable,should_panic,warnunused + + #[deny(clippy::cast_possible_truncation)] + fn main() { + let mut x = 3; + while (x < 70000) { + x *= 2; + } + println!("X probably fits in a u16, right? {}", x as u16); + } + +.. raw:: html + +--------- +Details +--------- + +There are compiler lints visible here, but not clippy lints. Run +``clippy`` on the playground site to show clippy warnings. Clippy has +extensive documentation of its lints, and adds new lints (including +default-deny lints) all the time. + +Note that errors or warnings with ``help: ...`` can be fixed with +``cargo fix`` or via your editor. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/190_testing/04_exercise.rst b/courses/comprehensive_rust_training/190_testing/04_exercise.rst new file mode 100644 index 000000000..633f84215 --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing/04_exercise.rst @@ -0,0 +1,39 @@ +========================== +Exercise: Luhn Algorithm +========================== + +-------------------------- +Exercise: Luhn Algorithm +-------------------------- + +The `Luhn algorithm `__ is +used to validate credit card numbers. The algorithm takes a string as +input and does the following to validate the credit card number: + +- Ignore all spaces. Reject numbers with fewer than two digits. + +- Moving from **right to left**, double every second digit: for the + number ``1234``, we double ``3`` and ``1``. For the number ``98765``, + we double ``6`` and ``8``. + +- After doubling a digit, sum the digits if the result is greater than + 9. So doubling ``7`` becomes ``14`` which becomes ``1 + 4 = 5``. + +- Sum all the undoubled and doubled digits. + +- The credit card number is valid if the sum ends with ``0``. + +The provided code provides a buggy implementation of the luhn algorithm, +along with two basic unit tests that confirm that most of the algorithm +is implemented correctly. + +Copy the code below to https://play.rust-lang.org/ and write additional +tests to uncover bugs in the provided implementation, fixing any bugs +you find. + +.. code:: rust + + {{#include exercise.rs:luhn}} + + {{#include exercise.rs:unit-tests}} + } diff --git a/courses/comprehensive_rust_training/200_error_handling.rst b/courses/comprehensive_rust_training/200_error_handling.rst new file mode 100644 index 000000000..7b378a558 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling.rst @@ -0,0 +1,43 @@ +**************** +Error Handling +**************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 200_error_handling/01_panics.rst +.. include:: 200_error_handling/02_result.rst +.. include:: 200_error_handling/03_try.rst +.. include:: 200_error_handling/04_try_conversions.rst +.. include:: 200_error_handling/05_error.rst +.. include:: 200_error_handling/06_thiserror.rst +.. include:: 200_error_handling/07_anyhow.rst +.. include:: 200_error_handling/08_exercise.rst diff --git a/courses/comprehensive_rust_training/200_error_handling/01_panics.rst b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst new file mode 100644 index 000000000..79499cab7 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst @@ -0,0 +1,63 @@ +======== +Panics +======== + +-------- +Panics +-------- + +Rust handles fatal errors with a "panic". + +Rust will trigger a panic if a fatal error happens at runtime: + +.. code:: rust,editable,should_panic + + fn main() { + let v = vec![10, 20, 30]; + println!("v[100]: {}", v[100]); + } + +- 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. + +.. raw:: html + +--------- +Details +--------- + +By default, a panic will cause the stack to unwind. The unwinding can be +caught: + +.. code:: 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``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/02_result.rst b/courses/comprehensive_rust_training/200_error_handling/02_result.rst new file mode 100644 index 000000000..008941a9d --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -0,0 +1,96 @@ +============ +``Result`` +============ + +------------ +``Result`` +------------ + +Our primary mechanism for error handling in Rust is the +```Result`` `__ +enum, which we briefly saw when discussing standard library types. + +.. code:: rust,editable + + use std::fs::File; + use std::io::Read; + + fn main() { + let file: Result = File::open("diary.txt"); + match file { + Ok(mut file) => { + let mut contents = String::new(); + if let Ok(bytes) = file.read_to_string(&mut contents) { + println!("Dear diary: {contents} ({bytes} bytes)"); + } else { + println!("Could not read file content"); + } + } + Err(err) => { + println!("The diary could not be opened: {err}"); + } + } + } + +.. raw:: html + +--------- +Details +--------- + +- ``Result`` has two variants: ``Ok`` which contains the success value, + and ``Err`` which contains an error value of some kind. + +- Whether or not a function can produce an error is encoded in the + function's type signature by having the function return a ``Result`` + value. + +- Like with ``Option``, there is no way to forget to handle an error: + You cannot access either the success value or the error value without + first pattern matching on the ``Result`` to check which variant you + have. Methods like ``unwrap`` make it easier to write quick-and-dirty + code that doesn't do robust error handling, but means that you can + always see in your source code where proper error handling is being + skipped. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +It may be helpful to compare error handling in Rust to error handling +conventions that students may be familiar with from other programming +languages. + +------------ +Exceptions +------------ + +- Many languages use exceptions, e.g. C++, Java, Python. + +- In most languages with exceptions, whether or not a function can + throw an exception is not visible as part of its type signature. This + generally means that you can't tell when calling a function if it may + throw an exception or not. + +- Exceptions generally unwind the call stack, propagating upward until + a ``try`` block is reached. An error originating deep in the call + stack may impact an unrelated function further up. + +--------------- +Error Numbers +--------------- + +- Some languages have functions return an error number (or some other + error value) separately from the successful return value of the + function. Examples include C and Go. + +- Depending on the language it may be possible to forget to check the + error value, in which case you may be accessing an uninitialized or + otherwise invalid success value. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/03_try.rst b/courses/comprehensive_rust_training/200_error_handling/03_try.rst new file mode 100644 index 000000000..2074a0289 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/03_try.rst @@ -0,0 +1,74 @@ +============== +Try Operator +============== + +-------------- +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 + +.. code:: rust,ignore + + match some_expression { + Ok(value) => value, + Err(err) => return Err(err), + } + +into the much simpler + +.. code:: rust,ignore + + some_expression? + +We can use this to simplify our error handling code: + +.. code:: rust,editable + + use std::io::Read; + use std::{fs, io}; + + fn read_username(path: &str) -> Result { + let username_file_result = fs::File::open(path); + let mut username_file = match username_file_result { + Ok(file) => file, + Err(err) => return Err(err), + }; + + let mut username = String::new(); + match username_file.read_to_string(&mut username) { + Ok(_) => Ok(username), + Err(err) => Err(err), + } + } + + fn main() { + //fs::write("config.dat", "alice").unwrap(); + let username = read_username("config.dat"); + println!("username or error: {username:?}"); + } + +.. raw:: html + +--------- +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. +- 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. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst b/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst new file mode 100644 index 000000000..aa772da79 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst @@ -0,0 +1,103 @@ +================= +Try Conversions +================= + +----------------- +Try Conversions +----------------- + +The effective expansion of ``?`` is a little more complicated than +previously indicated: + +.. code:: rust,ignore + + expression? + +works the same as + +.. code:: 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 +--------- + +.. code:: rust,editable + + use std::error::Error; + use std::io::Read; + use std::{fmt, fs, io}; + + #[derive(Debug)] + enum ReadUsernameError { + IoError(io::Error), + EmptyUsername(String), + } + + impl Error for ReadUsernameError {} + + impl fmt::Display for ReadUsernameError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::IoError(e) => write!(f, "I/O error: {e}"), + Self::EmptyUsername(path) => write!(f, "Found no username in {path}"), + } + } + } + + impl From for ReadUsernameError { + fn from(err: io::Error) -> Self { + Self::IoError(err) + } + } + + fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + fs::File::open(path)?.read_to_string(&mut username)?; + if username.is_empty() { + return Err(ReadUsernameError::EmptyUsername(String::from(path))); + } + Ok(username) + } + + fn main() { + //std::fs::write("config.dat", "").unwrap(); + let username = read_username("config.dat"); + println!("username or error: {username:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +The ``?`` operator must return a value compatible with the return type +of the function. For ``Result``, it means that the error types have to +be compatible. A function that returns ``Result`` can +only use ``?`` on a value of type ``Result`` if +``ErrorOuter`` and ``ErrorInner`` are the same type or if ``ErrorOuter`` +implements ``From``. + +A common alternative to a ``From`` implementation is +``Result::map_err``, especially when the conversion only happens in one +place. + +There is no compatibility requirement for ``Option``. A function +returning ``Option`` can use the ``?`` operator on ``Option`` for +arbitrary ``T`` and ``U`` types. + +A function that returns ``Result`` cannot use ``?`` on ``Option`` and +vice versa. However, ``Option::ok_or`` converts ``Option`` to ``Result`` +whereas ``Result::ok`` turns ``Result`` into ``Option``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/05_error.rst b/courses/comprehensive_rust_training/200_error_handling/05_error.rst new file mode 100644 index 000000000..465936d86 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/05_error.rst @@ -0,0 +1,54 @@ +===================== +Dynamic Error Types +===================== + +--------------------- +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. + +.. code:: rust,editable + + use std::error::Error; + use std::fs; + use std::io::Read; + + fn read_count(path: &str) -> Result> { + 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}"), + } + } + +.. raw:: html + +--------- +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`` 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. + +Make sure to implement the ``std::error::Error`` trait when defining a +custom error type so it can be boxed. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst new file mode 100644 index 000000000..f4aff8be6 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -0,0 +1,60 @@ +=============== +``thiserror`` +=============== + +--------------- +``thiserror`` +--------------- + +The ```thiserror`` `__ crate provides macros +to help avoid boilerplate when defining error types. It provides derive +macros that assist in implementing ``From``, ``Display``, and the +``Error`` trait. + +.. code:: rust,editable,compile_fail + + use std::io::Read; + use std::{fs, io}; + use thiserror::Error; + + #[derive(Debug, Error)] + enum ReadUsernameError { + #[error("I/O error: {0}")] + IoError(#[from] io::Error), + #[error("Found no username in {0}")] + EmptyUsername(String), + } + + fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + 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:?}"), + } + } + +.. raw:: html + +--------- +Details +--------- + +- The ``Error`` derive macro is provided by ``thiserror``, and has lots + of useful attributes to help define error types in a compact way. +- The message from ``#[error]`` is used to derive the ``Display`` + trait. +- Note that the (``thiserror::``)\ ``Error`` derive macro, while it has + the effect of implementing the (``std::error::``)\ ``Error`` trait, + is not the same this; traits and macros do not share a namespace. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst new file mode 100644 index 000000000..e95b801a7 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -0,0 +1,83 @@ +============ +``anyhow`` +============ + +------------ +``anyhow`` +------------ + +The ```anyhow`` `__ crate provides a rich error +type with support for carrying additional contextual information, which +can be used to provide a semantic trace of what the program was doing +leading up to the error. + +This can be combined with the convenience macros from +```thiserror`` `__ to avoid writing out +trait impls explicitly for custom error types. + +.. code:: rust,editable,compile_fail + + use anyhow::{bail, Context, Result}; + use std::fs; + use std::io::Read; + use thiserror::Error; + + #[derive(Clone, Debug, Eq, Error, PartialEq)] + #[error("Found no username in {0}")] + struct EmptyUsernameError(String); + + fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + fs::File::open(path) + .with_context(|| format!("Failed to open {path}"))? + .read_to_string(&mut username) + .context("Failed to read")?; + if username.is_empty() { + bail!(EmptyUsernameError(path.to_string())); + } + Ok(username) + } + + fn main() { + //fs::write("config.dat", "").unwrap(); + match read_username("config.dat") { + Ok(username) => println!("Username: {username}"), + Err(err) => println!("Error: {err:?}"), + } + } + +.. raw:: html + +--------- +Details +--------- + +- ``anyhow::Error`` is essentially a wrapper around ``Box``. + As such it's again generally not a good choice for the public API of + a library, but is widely used in applications. +- ``anyhow::Result`` is a type alias for + ``Result``. +- Functionality provided by ``anyhow::Error`` may be familiar to Go + developers, as it provides similar behavior to the Go ``error`` type + and ``Result`` is much like a Go ``(T, error)`` + (with the convention that only one element of the pair is + meaningful). +- ``anyhow::Context`` is a trait implemented for the standard + ``Result`` and ``Option`` types. ``use anyhow::Context`` is necessary + to enable ``.context()`` and ``.with_context()`` on those types. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +- ``anyhow::Error`` has support for downcasting, much like + ``std::any::Any``; the specific error type stored inside can be + extracted for examination if desired with + ```Error::downcast`` `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst new file mode 100644 index 000000000..b65584ce6 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst @@ -0,0 +1,30 @@ +================================= +Exercise: Rewriting with Result +================================= + +--------------------------------- +Exercise: Rewriting with Result +--------------------------------- + +In this exercise we're revisiting the expression evaluator exercise that +we did in day 2. Our initial solution ignores a possible error case: +Dividing by zero! Rewrite ``eval`` to instead use idiomatic error +handling to handle this error case and return an error when it occurs. +We provide a simple ``DivideByZeroError`` type to use as the error type +for ``eval``. + +.. code:: rust,editable + + {{#include exercise.rs:types}} + + {{#include exercise.rs:eval}} + + {{#include exercise.rs:tests}} + +- The starting code here isn't exactly the same as the previous + exercise's solution: We've added in an explicit panic to show + students where the error case is. Point this out if students get + confused. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust.rst b/courses/comprehensive_rust_training/210_unsafe_rust.rst new file mode 100644 index 000000000..d39fd99bf --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust.rst @@ -0,0 +1,42 @@ +************* +Unsafe Rust +************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. container:: PRELUDE END + +.. include:: 210_unsafe_rust/01_unsafe.rst +.. include:: 210_unsafe_rust/02_dereferencing.rst +.. include:: 210_unsafe_rust/03_mutable_static.rst +.. include:: 210_unsafe_rust/04_unions.rst +.. include:: 210_unsafe_rust/05_unsafe_functions.rst +.. include:: 210_unsafe_rust/06_unsafe_traits.rst +.. include:: 210_unsafe_rust/07_exercise.rst diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst new file mode 100644 index 000000000..d3c398c19 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst @@ -0,0 +1,46 @@ +============= +Unsafe Rust +============= + +------------- +Unsafe Rust +------------- + +The Rust language has two parts: + +- **Safe Rust:** memory safe, no undefined behavior possible. +- **Unsafe Rust:** can trigger undefined behavior if preconditions are + violated. + +We saw mostly safe Rust in this course, but it's important to know what +Unsafe Rust is. + +Unsafe code is usually small and isolated, and its correctness should be +carefully documented. It is usually wrapped in a safe abstraction layer. + +Unsafe Rust gives you access to five new capabilities: + +- Dereference raw pointers. +- Access or modify mutable static variables. +- Access ``union`` fields. +- Call ``unsafe`` functions, including ``extern`` functions. +- Implement ``unsafe`` traits. + +We will briefly cover unsafe capabilities next. For full details, please +see `Chapter 19.1 in the Rust +Book `__ and +the `Rustonomicon `__. + +.. raw:: html + +--------- +Details +--------- + +Unsafe Rust does not mean the code is incorrect. It means that +developers have turned off some compiler safety features and have to +write correct code by themselves. It means the compiler no longer +enforces Rust's memory-safety rules. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst new file mode 100644 index 000000000..c290932b1 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst @@ -0,0 +1,70 @@ +============================ +Dereferencing Raw Pointers +============================ + +---------------------------- +Dereferencing Raw Pointers +---------------------------- + +Creating pointers is safe, but dereferencing them requires ``unsafe``: + +.. code:: rust,editable + + fn main() { + let mut s = String::from("careful!"); + + let r1 = &raw mut s; + let r2 = r1 as *const String; + + // SAFETY: r1 and r2 were obtained from references and so are guaranteed to + // be non-null and properly aligned, the objects underlying the references + // from which they were obtained are live throughout the whole unsafe + // block, and they are not accessed either through the references or + // concurrently through any other pointers. + unsafe { + println!("r1 is: {}", *r1); + *r1 = String::from("uhoh"); + println!("r2 is: {}", *r2); + } + + // NOT SAFE. DO NOT DO THIS. + /* + let r3: &String = unsafe { &*r1 }; + drop(s); + println!("r3 is: {}", *r3); + */ + } + +.. raw:: html + +--------- +Details +--------- + +It is good practice (and required by the Android Rust style guide) to +write a comment for each ``unsafe`` block explaining how the code inside +it satisfies the safety requirements of the unsafe operations it is +doing. + +In the case of pointer dereferences, this means that the pointers must +be `valid `__, +i.e.: + +- The pointer must be non-null. +- The pointer must be *dereferenceable* (within the bounds of a single + allocated object). +- The object must not have been deallocated. +- There must not be concurrent accesses to the same location. +- If the pointer was obtained by casting a reference, the underlying + object must be live and no reference may be used to access the + memory. + +In most cases the pointer must also be properly aligned. + +The "NOT SAFE" section gives an example of a common kind of UB bug: +``*r1`` has the ``'static`` lifetime, so ``r3`` has type +``&'static String``, and thus outlives ``s``. Creating a reference from +a pointer requires *great care*. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst b/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst new file mode 100644 index 000000000..cdbc9027d --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst @@ -0,0 +1,58 @@ +========================== +Mutable Static Variables +========================== + +-------------------------- +Mutable Static Variables +-------------------------- + +It is safe to read an immutable static variable: + +.. code:: rust,editable + + static HELLO_WORLD: &str = "Hello, world!"; + + fn main() { + println!("HELLO_WORLD: {HELLO_WORLD}"); + } + +However, since data races can occur, it is unsafe to read and write +mutable static variables: + +.. code:: rust,editable + + static mut COUNTER: u32 = 0; + + fn add_to_counter(inc: u32) { + // SAFETY: There are no other threads which could be accessing `COUNTER`. + unsafe { + COUNTER += inc; + } + } + + fn main() { + add_to_counter(42); + + // SAFETY: There are no other threads which could be accessing `COUNTER`. + unsafe { + println!("COUNTER: {COUNTER}"); + } + } + +.. raw:: html + +--------- +Details +--------- + +- The program here is safe because it is single-threaded. However, the + Rust compiler is conservative and will assume the worst. Try removing + the ``unsafe`` and see how the compiler explains that it is undefined + behavior to mutate a static from multiple threads. + +- Using a mutable static is generally a bad idea, but there are some + cases where it might make sense in low-level ``no_std`` code, such as + implementing a heap allocator or working with some C APIs. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst new file mode 100644 index 000000000..8d0f2c5c9 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst @@ -0,0 +1,41 @@ +======== +Unions +======== + +-------- +Unions +-------- + +Unions are like enums, but you need to track the active field yourself: + +.. code:: rust,editable + + #[repr(C)] + union MyUnion { + i: u8, + b: bool, + } + + fn main() { + let u = MyUnion { i: 42 }; + println!("int: {}", unsafe { u.i }); + println!("bool: {}", unsafe { u.b }); // Undefined behavior! + } + +.. raw:: html + +--------- +Details +--------- + +Unions are very rarely needed in Rust as you can usually use an enum. +They are occasionally needed for interacting with C library APIs. + +If you just want to reinterpret bytes as a different type, you probably +want +```std::mem::transmute`` `__ +or a safe wrapper such as the +```zerocopy`` `__ crate. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst b/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst new file mode 100644 index 000000000..e1acf65b3 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst @@ -0,0 +1,121 @@ +================== +Unsafe Functions +================== + +------------------ +Unsafe Functions +------------------ + +-------------------------- +Calling Unsafe Functions +-------------------------- + +A function or method can be marked ``unsafe`` if it has extra +preconditions you must uphold to avoid undefined behaviour: + +.. code:: rust,editable + + extern "C" { + fn abs(input: i32) -> i32; + } + + fn main() { + let emojis = "TBDTBDTBD"; + + // SAFETY: The indices are in the correct order, within the bounds of the + // string slice, and lie on UTF-8 sequence boundaries. + unsafe { + println!("emoji: {}", emojis.get_unchecked(0..4)); + println!("emoji: {}", emojis.get_unchecked(4..7)); + println!("emoji: {}", emojis.get_unchecked(7..11)); + } + + println!("char count: {}", count_chars(unsafe { emojis.get_unchecked(0..7) })); + + // SAFETY: `abs` doesn't deal with pointers and doesn't have any safety + // requirements. + unsafe { + println!("Absolute value of -3 according to C: {}", abs(-3)); + } + + // Not upholding the UTF-8 encoding requirement breaks memory safety! + // println!("emoji: {}", unsafe { emojis.get_unchecked(0..3) }); + // println!("char count: {}", count_chars(unsafe { + // emojis.get_unchecked(0..3) })); + } + + fn count_chars(s: &str) -> usize { + s.chars().count() + } + +-------------------------- +Writing Unsafe Functions +-------------------------- + +You can mark your own functions as ``unsafe`` if they require particular +conditions to avoid undefined behaviour. + +.. code:: rust,editable + + /// Swaps the values pointed to by the given pointers. + /// + /// # Safety + /// + /// The pointers must be valid and properly aligned. + unsafe fn swap(a: *mut u8, b: *mut u8) { + let temp = *a; + *a = *b; + *b = temp; + } + + fn main() { + let mut a = 42; + let mut b = 66; + + // SAFETY: ... + unsafe { + swap(&mut a, &mut b); + } + + println!("a = {}, b = {}", a, b); + } + +.. raw:: html + +--------- +Details +--------- + +.. _calling-unsafe-functions-1: + +-------------------------- +Calling Unsafe Functions +-------------------------- + +``get_unchecked``, like most ``_unchecked`` functions, is unsafe, +because it can create UB if the range is incorrect. ``abs`` is unsafe +for a different reason: it is an external function (FFI). Calling +external functions is usually only a problem when those functions do +things with pointers which might violate Rust's memory model, but in +general any C function might have undefined behaviour under any +arbitrary circumstances. + +The ``"C"`` in this example is the ABI; `other ABIs are available +too `__. + +.. _writing-unsafe-functions-1: + +-------------------------- +Writing Unsafe Functions +-------------------------- + +We wouldn't actually use pointers for a ``swap`` function - it can be +done safely with references. + +Note that unsafe code is allowed within an unsafe function without an +``unsafe`` block. We can prohibit this with +``#[deny(unsafe_op_in_unsafe_fn)]``. Try adding it and see what happens. +This will likely change in a future Rust edition. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst b/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst new file mode 100644 index 000000000..c951f76b2 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst @@ -0,0 +1,50 @@ +============================ +Implementing Unsafe Traits +============================ + +---------------------------- +Implementing Unsafe Traits +---------------------------- + +Like with functions, you can mark a trait as ``unsafe`` if the +implementation must guarantee particular conditions to avoid undefined +behaviour. + +For example, the ``zerocopy`` crate has an unsafe trait that looks +`something like +this `__: + +.. code:: rust,editable + + use std::{mem, slice}; + + /// ... + /// # Safety + /// The type must have a defined representation and no padding. + pub unsafe trait IntoBytes { + fn as_bytes(&self) -> &[u8] { + let len = mem::size_of_val(self); + let slf: *const Self = self; + unsafe { slice::from_raw_parts(slf.cast::(), len) } + } + } + + // SAFETY: `u32` has a defined representation and no padding. + unsafe impl IntoBytes for u32 {} + +.. raw:: html + +--------- +Details +--------- + +There should be a ``# Safety`` section on the Rustdoc for the trait +explaining the requirements for the trait to be safely implemented. + +The actual safety section for ``IntoBytes`` is rather longer and more +complicated. + +The built-in ``Send`` and ``Sync`` traits are unsafe. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst new file mode 100644 index 000000000..352611237 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst @@ -0,0 +1,99 @@ +================== +Safe FFI Wrapper +================== + +------------------ +Safe FFI Wrapper +------------------ + +Rust has great support for calling functions through a *foreign function +interface* (FFI). We will use this to build a safe wrapper for the +``libc`` functions you would use from C to read the names of files in a +directory. + +You will want to consult the manual pages: + +- ```opendir(3)`` `__ +- ```readdir(3)`` `__ +- ```closedir(3)`` `__ + +You will also want to browse the +```std::ffi`` `__ module. There you +find a number of string types which you need for the exercise: + ++-------------------------+-------------+-----------------------------+ +| Types | Encoding | Use | ++=========================+=============+=============================+ +| ```str`` `__ | | | +| and | | | +| ``` | | | +| String`` `__ | | | ++-------------------------+-------------+-----------------------------+ +| ```CStr`` `__ | | | +| and | | | +| `` | | | +| `CString`` `__ | | | ++-------------------------+-------------+-----------------------------+ +| ```OsStr`` `__ | | | +| and | | | +| ```O | | | +| sString`` `__ | | | ++-------------------------+-------------+-----------------------------+ + +You will convert between all these types: + +- ``&str`` to ``CString``: you need to allocate space for a trailing + ``\0`` character, +- ``CString`` to ``*const i8``: you need a pointer to call C functions, +- ``*const i8`` to ``&CStr``: you need something which can find the + trailing ``\0`` character, +- ``&CStr`` to ``&[u8]``: a slice of bytes is the universal interface + for "some unknown data", +- ``&[u8]`` to ``&OsStr``: ``&OsStr`` is a step towards ``OsString``, + use + ```OsStrExt`` `__ + to create it, +- ``&OsStr`` to ``OsString``: you need to clone the data in ``&OsStr`` + to be able to return it and call ``readdir`` again. + +The `Nomicon `__ also has a +very useful chapter about FFI. + +Copy the code below to https://play.rust-lang.org/ and fill in the +missing functions and methods: + +.. code:: rust,should_panic + + // TODO: remove this when you're done with your implementation. + #![allow(unused_imports, unused_variables, dead_code)] + + {{#include exercise.rs:ffi}} + + {{#include exercise.rs:DirectoryIterator}} + unimplemented!() + } + } + + {{#include exercise.rs:Iterator}} + unimplemented!() + } + } + + {{#include exercise.rs:Drop}} + unimplemented!() + } + } + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/standard_course.txt b/courses/comprehensive_rust_training/standard_course.txt new file mode 100644 index 000000000..aa3146aa0 --- /dev/null +++ b/courses/comprehensive_rust_training/standard_course.txt @@ -0,0 +1,20 @@ +020_hello_world.rst +030_types_and_values.rst +040_control_flow_basics.rst +050_tuples_and_arrays.rst +060_references.rst +070_user_defined_types.rst +080_pattern_matching.rst +090_methods_and_traits.rst +100_generics.rst +110_std_types.rst +120_std_traits.rst +130_memory_management.rst +140_smart_pointers.rst +150_borrowing.rst +160_lifetimes.rst +170_iterators.rst +180_modules.rst +190_testing.rst +200_error_handling.rst +210_unsafe_rust.rst From 3566e84b8deb900bef5cc6b5300ce29ef9cb3e89 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Fri, 24 Jan 2025 13:11:11 -0500 Subject: [PATCH 2/7] Fix TBD's generated by unprintable characters --- .../030_types_and_values/01_hello_world.rst | 2 +- .../030_types_and_values/03_values.rst | 5 +++-- .../040_control_flow_basics/06_functions.rst | 2 +- .../050_tuples_and_arrays/05_exercise.rst | 22 ++++++++++++++----- .../04_let_control_flow.rst | 2 +- .../110_std_types/03_option.rst | 4 ++-- .../110_std_types/05_string.rst | 2 +- .../110_std_types/07_hashmap.rst | 4 ++-- .../120_std_traits/04_casting.rst | 2 +- .../130_memory_management/02_approaches.rst | 2 +- .../160_lifetimes/02_lifetime_elision.rst | 2 +- .../180_modules/02_filesystem.rst | 10 ++++----- 12 files changed, 35 insertions(+), 24 deletions(-) diff --git a/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst b/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst index 881cf0da7..f44de4689 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst @@ -12,7 +12,7 @@ World program: .. code:: rust,editable fn main() { - println!("Hello TBD!"); + println!("Hello World!"); } What you see: diff --git a/courses/comprehensive_rust_training/030_types_and_values/03_values.rst b/courses/comprehensive_rust_training/030_types_and_values/03_values.rst index 696259e64..3104ff30f 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/03_values.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/03_values.rst @@ -23,8 +23,9 @@ each type. | point numbers | | ``-10.0e20``, | | | | ``2_f32`` | +---------------+-------------------------------+---------------------+ -| Unicode | ``char`` | ``'a'``, ``'TBD'``, | -| scalar values | | ``'TBD'`` | +| Unicode | ``char`` | ``'a'``, | +| scalar values | | ':math:`\alpha`', | +| | | ':math:`\infty`' | +---------------+-------------------------------+---------------------+ | Booleans | ``bool`` | ``true``, ``false`` | +---------------+-------------------------------+---------------------+ diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst b/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst index d4aee63f4..46976fac8 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst @@ -39,7 +39,7 @@ Details ``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 TBD each function has a single +- Overloading is not supported - each function has a single implementation. - Always takes a fixed number of parameters. Default arguments are diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst index e9f7244ce..c7fff5376 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst @@ -17,15 +17,25 @@ What is the type of this variable? Use an array such as the above to write a function ``transpose`` which will transpose a matrix (turn rows into columns): -.. raw:: html +Transpose - +.. math:: -.. code:: bob + \begin{bmatrix} + 1 & 2 & 3 \\ + 4 & 5 & 6 \\ + 7 & 8 & 9 + \end{bmatrix} - TBDTBD1 2 3TBDTBD TBD1 4 7TBD - "transpose"TBDTBD4 5 6TBDTBD "=="TBD2 5 8TBD - TBDTBD7 8 9TBDTBD TBD3 6 9TBD +into + +.. math:: + + \begin{bmatrix} + 1 & 4 & 7 \\ + 2 & 5 & 8 \\ + 3 & 6 & 9 + \end{bmatrix} Copy the code below to https://play.rust-lang.org/ and implement the function. This function only operates on 3x3 matrices. diff --git a/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst b/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst index 94df9e26e..7c4a9da7e 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst @@ -90,7 +90,7 @@ variant which repeatedly tests a value against a pattern: .. code:: rust,editable fn main() { - let mut name = String::from("Comprehensive Rust TBD"); + let mut name = String::from("Comprehensive Rust"); while let Some(c) = name.pop() { println!("character: {c}"); } diff --git a/courses/comprehensive_rust_training/110_std_types/03_option.rst b/courses/comprehensive_rust_training/110_std_types/03_option.rst index d0382644f..eb1767cc3 100644 --- a/courses/comprehensive_rust_training/110_std_types/03_option.rst +++ b/courses/comprehensive_rust_training/110_std_types/03_option.rst @@ -14,8 +14,8 @@ returns an ``Option``. .. code:: rust,editable,should_panic fn main() { - let name = "LTBDwe TBDTBD LTBDopard Gepardi"; - let mut position: Option = name.find('TBD'); + let name = "Alexander the Great"; + let mut position: Option = name.find('e'); println!("find returned {position:?}"); assert_eq!(position.unwrap(), 14); position = name.find('Z'); diff --git a/courses/comprehensive_rust_training/110_std_types/05_string.rst b/courses/comprehensive_rust_training/110_std_types/05_string.rst index 28b661019..e0990382e 100644 --- a/courses/comprehensive_rust_training/110_std_types/05_string.rst +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -21,7 +21,7 @@ is a growable UTF-8 encoded string: s2.push('!'); println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity()); - let s3 = String::from("TBDTBD"); + let s3 = String::from("a z"); println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count()); } diff --git a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst index e4d1f23bb..1cc62d47e 100644 --- a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -18,9 +18,9 @@ Standard hash map with protection against HashDoS attacks: page_counts.insert("Grimms' Fairy Tales", 751); page_counts.insert("Pride and Prejudice", 303); - if !page_counts.contains_key("Les MisTBDrables") { + if !page_counts.contains_key("Les Miserables") { println!( - "We know about {} books, but not Les MisTBDrables.", + "We know about {} books, but not Les Miserables.", page_counts.len() ); } diff --git a/courses/comprehensive_rust_training/120_std_traits/04_casting.rst b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst index 04d2480c0..010951b57 100644 --- a/courses/comprehensive_rust_training/120_std_traits/04_casting.rst +++ b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst @@ -20,7 +20,7 @@ with ``as``. These generally follow C semantics where those are defined. The results of ``as`` are *always* defined in Rust and consistent across platforms. This might not match your intuition for changing sign or -casting to a smaller type TBD check the docs, and comment for clarity. +casting to a smaller type - check the docs, and comment for clarity. Casting with ``as`` is a relatively sharp tool that is easy to use incorrectly, and can be a source of subtle bugs as future maintenance diff --git a/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst index 4719b56cc..9384d7550 100644 --- a/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst +++ b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst @@ -57,7 +57,7 @@ put Rust in context. Rust's ownership and borrowing model can, in many cases, get the performance of C, with alloc and free operations precisely where they -are required TBD zero cost. It also provides tools similar to C++'s smart +are required - zero cost. It also provides tools similar to C++'s smart pointers. When required, other options such as reference counting are available, and there are even crates available to support runtime garbage collection (not covered in this class). diff --git a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst index 1f36966c9..afb2b4cae 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst @@ -10,7 +10,7 @@ Lifetimes for function arguments and return values must be fully specified, but Rust allows lifetimes to be elided in most cases with `a few simple rules `__. This -is not inference TBD it is just a syntactic shorthand. +is not inference - it is just a syntactic shorthand. - Each argument which does not have a lifetime annotation is given one. - If there is only one argument lifetime, it is given to all diff --git a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst index 0246025f7..5349471b9 100644 --- a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst +++ b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst @@ -23,7 +23,7 @@ The ``crate`` root is in: - ``src/main.rs`` (for a binary crate) Modules defined in files can be documented, too, using "inner doc -comments". These document the item that contains them TBD in this case, a +comments". These document the item that contains them - in this case, a module. .. code:: rust,editable,compile_fail @@ -64,10 +64,10 @@ Details .. code:: ignore src/ - TBDTBDTBD main.rs - TBDTBDTBD top_module.rs - TBDTBDTBD top_module/ - TBDTBDTBD sub_module.rs + |-- main.rs + |-- top_module.rs + |-- top_module/ + |-- sub_module.rs - The place rust will look for modules can be changed with a compiler directive: From 2075d2d640f6f22d62c5fdeca8ea38a21cd26347 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:51:55 -0500 Subject: [PATCH 3/7] Cleanup ugly URL links --- .../040_control_flow_basics/01_if.rst | 4 +- .../040_control_flow_basics/03_loops.rst | 4 +- .../04_break_continue.rst | 4 +- .../040_control_flow_basics/07_macros.rst | 2 +- .../040_control_flow_basics/08_exercise.rst | 4 +- .../02_tuple_structs.rst | 4 +- .../070_user_defined_types/03_enums.rst | 4 +- .../070_user_defined_types/05_const.rst | 4 +- .../070_user_defined_types/06_static.rst | 6 +- .../04_let_control_flow.rst | 10 ++-- .../090_methods_and_traits/01_methods.rst | 4 +- .../100_generics/03_generic_traits.rst | 5 +- .../100_generics/06_dyn_trait.rst | 3 +- .../100_generics/07_exercise.rst | 6 +- .../110_std_types/02_docs.rst | 8 +-- .../110_std_types/03_option.rst | 2 +- .../110_std_types/05_string.rst | 10 ++-- .../110_std_types/06_vec.rst | 4 +- .../110_std_types/07_hashmap.rst | 2 +- .../110_std_types/08_exercise.rst | 4 +- .../120_std_traits/02_operators.rst | 2 +- .../120_std_traits/03_from_and_into.rst | 8 +-- .../120_std_traits/05_read_and_write.rst | 6 +- .../120_std_traits/06_default.rst | 6 +- .../120_std_traits/07_closures.rst | 6 +- .../120_std_traits/08_exercise.rst | 4 +- .../130_memory_management/01_review.rst | 8 +-- .../130_memory_management/04_move.rst | 2 +- .../130_memory_management/07_drop.rst | 2 +- .../140_smart_pointers/01_box.rst | 5 +- .../140_smart_pointers/02_rc.rst | 6 +- .../140_smart_pointers/03_trait_objects.rst | 4 +- .../160_lifetimes/02_lifetime_elision.rst | 5 +- .../160_lifetimes/04_exercise.rst | 4 +- .../170_iterators/02_iterator.rst | 4 +- .../170_iterators/04_collect.rst | 6 +- .../170_iterators/05_intoiterator.rst | 2 +- .../170_iterators/06_exercise.rst | 2 +- .../180_modules/03_visibility.rst | 4 +- .../180_modules/04_encapsulation.rst | 4 +- .../190_testing/02_other.rst | 4 +- .../200_error_handling/02_result.rst | 2 +- .../200_error_handling/06_thiserror.rst | 2 +- .../200_error_handling/07_anyhow.rst | 6 +- .../210_unsafe_rust/01_unsafe.rst | 4 +- .../210_unsafe_rust/04_unions.rst | 4 +- .../210_unsafe_rust/05_unsafe_functions.rst | 4 +- .../210_unsafe_rust/06_unsafe_traits.rst | 3 +- .../210_unsafe_rust/07_exercise.rst | 58 ++++++++----------- 49 files changed, 127 insertions(+), 144 deletions(-) diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst b/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst index 3004700a6..4422f9101 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst @@ -6,8 +6,8 @@ ``if`` expressions -------------------- -You use ```if`` -expressions `__ +You use +`if expressions `__ exactly like ``if`` statements in other languages: .. code:: rust,editable diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst b/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst index ba3565d16..a8d7508d3 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst @@ -13,8 +13,8 @@ There are three looping keywords in Rust: ``while``, ``loop``, and ``while`` ----------- -The ```while`` -keyword `__ +The +`while keyword `__ works much like in other languages, executing the loop body as long as the condition is true. diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst b/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst index d03b4a65e..51fdfc7a5 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst @@ -7,10 +7,10 @@ ---------------------------- If you want to immediately start the next iteration use -```continue`` `__. +`continue `__. If you want to exit any kind of loop early, use -```break`` `__. +`break `__. With ``loop``, this can take an optional expression that becomes the value of the ``loop`` expression. diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst b/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst index 78954b234..eccc7b051 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst @@ -12,7 +12,7 @@ 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`` `__. + `std::fmt `__. - ``format!(format, ..)`` works just like ``println!`` but returns the result as a string. - ``dbg!(expression)`` logs the value of the expression and returns it. diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst b/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst index a57a8f61b..c2a3f5993 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst @@ -6,8 +6,8 @@ Exercise: Collatz Sequence Exercise: Collatz Sequence ---------------------------- -The `Collatz -Sequence `__ is +The +`Collatz Sequence `__ is defined as follows, for an arbitrary n1 greater than zero: - If *ni* is 1, then the sequence terminates at *ni*. diff --git a/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst b/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst index dfcb160cf..a9b91c994 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst @@ -65,8 +65,8 @@ Details unwrapping or for instance using booleans as integers. - Operator overloading is discussed on Day 3 (generics). -- The example is a subtle reference to the `Mars Climate - Orbiter `__ +- The example is a subtle reference to the + `Mars Climate Orbiter `__ failure. .. raw:: html diff --git a/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst b/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst index 5aecc6ac8..721128ec9 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst @@ -92,8 +92,8 @@ More to Explore Rust has several optimizations it can employ to make enums take up less space. -- Null pointer optimization: For `some - types `__, Rust +- Null pointer optimization: For + `some types `__, Rust guarantees that ``size_of::()`` equals ``size_of::>()``. Example code if you want to show how the bitwise representation *may* diff --git a/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst b/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst index d206b53b7..9fcc4b4a8 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst @@ -39,8 +39,8 @@ wherever they are used: println!("digest: {digest:?}"); } -According to the `Rust RFC -Book `__ +According to the +`Rust RFC Book `__ these are inlined upon use. Only functions marked ``const`` can be called at compile time to diff --git a/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst b/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst index 34f25cd55..510f0c1b0 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst @@ -17,8 +17,8 @@ and therefore will not move: println!("{BANNER}"); } -As noted in the `Rust RFC -Book `__, +As noted in the +`Rust RFC Book `__, these are not inlined upon use and have an actual associated memory location. This is useful for unsafe and embedded code, and the variable lives through the entirety of the program execution. When a @@ -45,7 +45,7 @@ More to Explore Because ``static`` variables are accessible from any thread, they must be ``Sync``. Interior mutability is possible through a -```Mutex`` `__, +`Mutex `__, atomic or similar. It is common to use ``OnceLock`` in a static as a way to support diff --git a/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst b/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst index 7c4a9da7e..7b9413c2b 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst @@ -21,8 +21,8 @@ languages. They are used for pattern matching: ``if let`` expressions ------------------------ -The ```if let`` -expression `__ +The +`if let expression `__ lets you execute different code depending on whether a value matches a pattern: @@ -52,7 +52,7 @@ pattern: For the common case of matching a pattern and returning from the function, use -```let else`` `__. +`let else `__. The "else" case must diverge (``return``, ``break``, or panic - anything but falling off the end of the block). @@ -80,7 +80,7 @@ but falling off the end of the block). } Like with ``if let``, there is a -```while let`` `__ +`while let `__ variant which repeatedly tests a value against a pattern: .. raw:: html @@ -98,7 +98,7 @@ variant which repeatedly tests a value against a pattern: } Here -```String::pop`` `__ +`String::pop `__ returns ``Some(c)`` until the string is empty, after which it will return ``None``. The ``while let`` lets us keep iterating through all items. diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst b/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst index 263946375..bcec67c18 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst @@ -98,8 +98,8 @@ Key Points: be used to refer to individual fields. - This might be a good time to demonstrate how the ``&self`` differs from ``self`` by trying to run ``finish`` twice. - - Beyond variants on ``self``, there are also `special wrapper - types `__ + - Beyond variants on ``self``, there are also + `special wrapper types `__ allowed to be receiver types, such as ``Box``. .. raw:: html diff --git a/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst index 539188bb2..fb70d1bd7 100644 --- a/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst +++ b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst @@ -39,9 +39,8 @@ Details --------- - The ``From`` trait will be covered later in the course, but its - `definition in the ``std`` - docs `__ is - simple. + `definition in the std docs `__ + is simple. - Implementations of the trait do not need to cover all possible type parameters. Here, ``Foo::from("hello")`` would not compile because diff --git a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst index 9da5f9305..c64955363 100644 --- a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst @@ -70,8 +70,7 @@ Details implementation to use. - When using ``dyn Trait``, it instead uses dynamic dispatch through a - `virtual method - table `__ + `virtual method table `__ (vtable). This means that there's a single version of ``fn dynamic`` that is used regardless of what type of ``Pet`` is passed in. diff --git a/courses/comprehensive_rust_training/100_generics/07_exercise.rst b/courses/comprehensive_rust_training/100_generics/07_exercise.rst index 5dfcef6dd..c9f8d90f2 100644 --- a/courses/comprehensive_rust_training/100_generics/07_exercise.rst +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -8,7 +8,7 @@ Exercise: Generic ``min`` In this short exercise, you will implement a generic ``min`` function that determines the minimum of two values, using the -```Ord`` `__ +`Ord `__ trait. .. code:: rust,compile_fail @@ -26,9 +26,9 @@ Details --------- - Show students the - ```Ord`` `__ + `Ord `__ trait and - ```Ordering`` `__ + `Ordering `__ enum. .. raw:: html diff --git a/courses/comprehensive_rust_training/110_std_types/02_docs.rst b/courses/comprehensive_rust_training/110_std_types/02_docs.rst index ea971532c..2faa8d586 100644 --- a/courses/comprehensive_rust_training/110_std_types/02_docs.rst +++ b/courses/comprehensive_rust_training/110_std_types/02_docs.rst @@ -11,11 +11,11 @@ Rust comes with extensive documentation. For example: - All of the details about `loops `__. - Primitive types like - ```u8`` `__. + `u8 `__. - Standard library types like - ```Option`` `__ + `Option `__ or - ```BinaryHeap`` `__. + `BinaryHeap `__. Use ``rustup doc --std`` or https://std.rs to view the documentation. @@ -34,7 +34,7 @@ In fact, you can document your own code: } The contents are treated as Markdown. All published Rust library crates -are automatically documented at ```docs.rs`` `__ using +are automatically documented at `docs.rs `__ using the `rustdoc `__ tool. It is idiomatic to document all public items in an API using this pattern. diff --git a/courses/comprehensive_rust_training/110_std_types/03_option.rst b/courses/comprehensive_rust_training/110_std_types/03_option.rst index eb1767cc3..704fb1561 100644 --- a/courses/comprehensive_rust_training/110_std_types/03_option.rst +++ b/courses/comprehensive_rust_training/110_std_types/03_option.rst @@ -8,7 +8,7 @@ Option We have already seen some use of ``Option``. It stores either a value of type ``T`` or nothing. For example, -```String::find`` `__ +`String::find `__ returns an ``Option``. .. code:: rust,editable,should_panic diff --git a/courses/comprehensive_rust_training/110_std_types/05_string.rst b/courses/comprehensive_rust_training/110_std_types/05_string.rst index e0990382e..1a4c1f364 100644 --- a/courses/comprehensive_rust_training/110_std_types/05_string.rst +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -6,7 +6,7 @@ String String -------- -```String`` `__ +`String `__ is a growable UTF-8 encoded string: .. code:: rust,editable @@ -26,7 +26,7 @@ is a growable UTF-8 encoded string: } ``String`` implements -```Deref`` `__, +`Deref `__, which means that you can call all ``str`` methods on a ``String``. .. raw:: html @@ -42,8 +42,8 @@ Details can be different from its length in characters). - ``String::chars`` returns an iterator over the actual characters. Note that a ``char`` can be different from what a human will consider - a "character" due to `grapheme - clusters `__. + a "character" due to + `grapheme clusters `__. - When people refer to strings they could either be talking about ``&str`` or ``String``. - When a type implements ``Deref``, the compiler will let @@ -67,7 +67,7 @@ Details character boundaries or not. - Many types can be converted to a string with the - ```to_string`` `__ + `to_string `__ method. This trait is automatically implemented for all types that implement ``Display``, so anything that can be formatted can also be converted to a string. diff --git a/courses/comprehensive_rust_training/110_std_types/06_vec.rst b/courses/comprehensive_rust_training/110_std_types/06_vec.rst index 337d3bfe5..d72b8726c 100644 --- a/courses/comprehensive_rust_training/110_std_types/06_vec.rst +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -6,7 +6,7 @@ ``Vec`` --------- -```Vec`` `__ is the +`Vec `__ is the standard resizable heap-allocated buffer: .. code:: rust,editable @@ -34,7 +34,7 @@ standard resizable heap-allocated buffer: } ``Vec`` implements -```Deref`` `__, +`Deref `__, which means that you can call slice methods on a ``Vec``. .. raw:: html diff --git a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst index 1cc62d47e..d3e94dbae 100644 --- a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -68,7 +68,7 @@ Details macro. - Although, since Rust 1.56, HashMap implements - ```From<[(K, V); N]>`` `__, + `From<[(K, V); N]> `__, which allows us to easily initialize a hash map from a literal array: diff --git a/courses/comprehensive_rust_training/110_std_types/08_exercise.rst b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst index 9d6f69982..5b7d91a2d 100644 --- a/courses/comprehensive_rust_training/110_std_types/08_exercise.rst +++ b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst @@ -8,7 +8,7 @@ Exercise: Counter In this exercise you will take a very simple data structure and make it generic. It uses a -```std::collections::HashMap`` `__ +`std::collections::HashMap `__ to keep track of which values have been seen and how many times each one has appeared. @@ -17,7 +17,7 @@ The initial version of ``Counter`` is hard coded to only work for value being tracked, that way ``Counter`` can track any type of value. If you finish early, try using the -```entry`` `__ +`entry `__ method to halve the number of hash lookups required to implement the ``count`` method. diff --git a/courses/comprehensive_rust_training/120_std_traits/02_operators.rst b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst index f7193fbbb..2e9a7679e 100644 --- a/courses/comprehensive_rust_training/120_std_traits/02_operators.rst +++ b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst @@ -7,7 +7,7 @@ Operators ----------- Operator overloading is implemented via traits in -```std::ops`` `__: +`std::ops `__: .. code:: rust,editable diff --git a/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst b/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst index 937587ba3..deb1ff902 100644 --- a/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst +++ b/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst @@ -7,8 +7,8 @@ ----------------------- Types implement -```From`` `__ and -```Into`` `__ to +`From `__ and +`Into `__ to facilitate type conversions. Unlike ``as``, these traits correspond to lossless, infallible conversions. @@ -22,9 +22,9 @@ lossless, infallible conversions. println!("{s}, {addr}, {one}, {bigger}"); } -```Into`` `__ is +`Into `__ is automatically implemented when -```From`` `__ is +`From `__ is implemented: .. code:: rust,editable diff --git a/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst b/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst index a849cc5ab..9c93c7b08 100644 --- a/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst +++ b/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst @@ -6,9 +6,9 @@ ``Read`` and ``Write`` ------------------------ -Using ```Read`` `__ +Using `Read `__ and -```BufRead`` `__, +`BufRead `__, you can abstract over ``u8`` sources: .. code:: rust,editable @@ -30,7 +30,7 @@ you can abstract over ``u8`` sources: } Similarly, -```Write`` `__ lets +`Write `__ lets you abstract over ``u8`` sinks: .. code:: rust,editable diff --git a/courses/comprehensive_rust_training/120_std_traits/06_default.rst b/courses/comprehensive_rust_training/120_std_traits/06_default.rst index 2c7e68907..32cd57eee 100644 --- a/courses/comprehensive_rust_training/120_std_traits/06_default.rst +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -6,7 +6,7 @@ The ``Default`` Trait The ``Default`` Trait ----------------------- -```Default`` `__ +`Default `__ trait produces a default value for a type. .. code:: rust,editable @@ -57,8 +57,8 @@ Details - The partial struct initialization works nicely with default. - The Rust standard library is aware that types can implement ``Default`` and provides convenience methods that use it. -- The ``..`` syntax is called `struct update - syntax `__. +- The ``..`` syntax is called + `struct update syntax `__. .. raw:: html diff --git a/courses/comprehensive_rust_training/120_std_traits/07_closures.rst b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst index 136765953..1bb5bfd76 100644 --- a/courses/comprehensive_rust_training/120_std_traits/07_closures.rst +++ b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst @@ -8,9 +8,9 @@ Closures Closures or lambda expressions have types which cannot be named. However, they implement special -```Fn`` `__, -```FnMut`` `__, and -```FnOnce`` `__ +`Fn `__, +`FnMut `__, and +`FnOnce `__ traits: .. code:: rust,editable diff --git a/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst index ede44e997..05cd87433 100644 --- a/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst +++ b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst @@ -6,8 +6,8 @@ Exercise: ROT13 Exercise: ROT13 ----------------- -In this example, you will implement the classic `"ROT13" -cipher `__. Copy this code to the +In this example, you will implement the classic +`"ROT13" cipher `__. Copy this code to the playground, and implement the missing bits. Only rotate ASCII alphabetic characters, to ensure the result is still valid UTF-8. diff --git a/courses/comprehensive_rust_training/130_memory_management/01_review.rst b/courses/comprehensive_rust_training/130_memory_management/01_review.rst index dfd0c6e52..16e775ea7 100644 --- a/courses/comprehensive_rust_training/130_memory_management/01_review.rst +++ b/courses/comprehensive_rust_training/130_memory_management/01_review.rst @@ -59,10 +59,10 @@ Details heap. - If students ask about it, you can mention that the underlying memory - is heap allocated using the `System - Allocator `__ - and custom allocators can be implemented using the `Allocator - API `__ + is heap allocated using the + `System Allocator `__ + and custom allocators can be implemented using the + `Allocator API `__ ----------------- More to Explore diff --git a/courses/comprehensive_rust_training/130_memory_management/04_move.rst b/courses/comprehensive_rust_training/130_memory_management/04_move.rst index 23e7e1082..8cdac3d43 100644 --- a/courses/comprehensive_rust_training/130_memory_management/04_move.rst +++ b/courses/comprehensive_rust_training/130_memory_management/04_move.rst @@ -181,7 +181,7 @@ Key points: a double-free when either string goes out of scope. - C++ also has - ```std::move`` `__, + `std::move `__, which is used to indicate when a value may be moved from. If the example had been ``s2 = std::move(s1)``, no heap allocation would take place. After the move, ``s1`` would be in a valid but diff --git a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst index 866c5ec6a..e5d218a6e 100644 --- a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst +++ b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst @@ -7,7 +7,7 @@ The ``Drop`` Trait -------------------- Values which implement -```Drop`` `__ can +`Drop `__ can specify code to run when they go out of scope: .. code:: rust,editable diff --git a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst index 43b594729..0da5865ff 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -6,7 +6,7 @@ ``Box`` ------------ -```Box`` `__ is an +`Box `__ is an owned pointer to data on the heap: .. code:: rust,editable @@ -30,8 +30,7 @@ owned pointer to data on the heap: `- - - - - - -' `- - - - - - -' ``Box`` implements ``Deref``, which means that you can -`call methods from ``T`` directly on a -``Box`` `__. +`call methods from T directly on a Box `__. Recursive data types or data types with dynamic sizes cannot be stored inline without a pointer indirection. ``Box`` accomplishes that diff --git a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst index a1a2f460c..509db1b42 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -6,7 +6,7 @@ ``Rc`` -------- -```Rc`` `__ is a +`Rc `__ is a reference-counted shared pointer. Use this when you need to refer to the same data from multiple places: @@ -23,10 +23,10 @@ same data from multiple places: } - See ```Arc`` <../concurrency/shared-state/arc.md>`__ and - ```Mutex`` `__ + `Mutex `__ if you are in a multi-threaded context. - You can *downgrade* a shared pointer into a - ```Weak`` `__ + `Weak `__ pointer to create cycles that will get dropped. .. raw:: html diff --git a/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst b/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst index 10eca0308..1a64da882 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst @@ -97,8 +97,8 @@ Details data is on the heap. The two vector elements are *fat pointers*: - A fat pointer is a double-width pointer. It has two components: a - pointer to the actual object and a pointer to the `virtual method - table `__ + pointer to the actual object and a pointer to the + `virtual method table `__ (vtable) for the ``Pet`` implementation of that particular object. - The data for the ``Dog`` named Fido is the ``name`` and ``age`` fields. The ``Cat`` has a ``lives`` field. diff --git a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst index afb2b4cae..2f729b005 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst @@ -7,9 +7,8 @@ Lifetimes in Function Calls ----------------------------- Lifetimes for function arguments and return values must be fully -specified, but Rust allows lifetimes to be elided in most cases with `a -few simple -rules `__. This +specified, but Rust allows lifetimes to be elided in most cases with +`a few simple rules `__. This is not inference - it is just a syntactic shorthand. - Each argument which does not have a lifetime annotation is given one. diff --git a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst index 57341ecf0..1c82dd6cc 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst @@ -6,8 +6,8 @@ Exercise: Protobuf Parsing Exercise: Protobuf Parsing ---------------------------- -In this exercise, you will build a parser for the `protobuf binary -encoding `__. Don't +In this exercise, you will build a parser for the +`protobuf binary encoding `__. Don't worry, it's simpler than it seems! This illustrates a common parsing pattern, passing slices of data. The underlying data itself is never copied. diff --git a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst index 97847f1bb..ef9dc7db5 100644 --- a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst @@ -7,7 +7,7 @@ -------------------- The -```Iterator`` `__ +`Iterator `__ trait defines how an object can be used to produce a sequence of values. For example, if we wanted to create an iterator that can produce the elements of a slice it might look something like this: @@ -63,7 +63,7 @@ More to Explore ----------------- - The "real" version of ``SliceIter`` is the - ```slice::Iter`` `__ + `slice::Iter `__ type in the standard library, however the real version uses pointers under the hood instead of an index in order to eliminate bounds checks. diff --git a/courses/comprehensive_rust_training/170_iterators/04_collect.rst b/courses/comprehensive_rust_training/170_iterators/04_collect.rst index 8295c9423..04c73ddd6 100644 --- a/courses/comprehensive_rust_training/170_iterators/04_collect.rst +++ b/courses/comprehensive_rust_training/170_iterators/04_collect.rst @@ -7,9 +7,9 @@ ------------- The -```collect`` `__ +`collect `__ method lets you build a collection from an -```Iterator`` `__. +`Iterator `__. .. code:: rust,editable @@ -46,7 +46,7 @@ More to Explore ----------------- - If students are curious about how this works, you can bring up the - ```FromIterator`` `__ + `FromIterator `__ trait, which defines how each type of collection gets built from an iterator. - In addition to the basic implementations of ``FromIterator`` for diff --git a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst index 4c94c3953..8816d53bd 100644 --- a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst @@ -8,7 +8,7 @@ The ``Iterator`` trait tells you how to *iterate* once you have created an iterator. The related trait -```IntoIterator`` `__ +`IntoIterator `__ defines how to create an iterator for a type. It is used automatically by the ``for`` loop. diff --git a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst index 32cc53b35..4060d4b2d 100644 --- a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst +++ b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst @@ -8,7 +8,7 @@ Exercise: Iterator Method Chaining In this exercise, you will need to find and use some of the provided methods in the -```Iterator`` `__ +`Iterator `__ trait to implement a complex calculation. Copy the following code to https://play.rust-lang.org/ and make the diff --git a/courses/comprehensive_rust_training/180_modules/03_visibility.rst b/courses/comprehensive_rust_training/180_modules/03_visibility.rst index 46d431312..c5e40b8cb 100644 --- a/courses/comprehensive_rust_training/180_modules/03_visibility.rst +++ b/courses/comprehensive_rust_training/180_modules/03_visibility.rst @@ -51,8 +51,8 @@ Details Additionally, there are advanced ``pub(...)`` specifiers to restrict the scope of public visibility. -- See the `Rust - Reference `__. +- See the + `Rust Reference `__. - Configuring ``pub(crate)`` visibility is a common pattern. - Less commonly, you can give visibility to a specific path. - In any case, visibility must be granted to an ancestor module (and diff --git a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst index cb9b6f448..0b2dd2861 100644 --- a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst +++ b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst @@ -83,8 +83,8 @@ More to Explore and show how they're used to limit what can be done with an enum. - Module privacy still applies when there are ``impl`` blocks in other - modules `(example in the - playground) `__. + modules + `(example in the playground) `__. .. raw:: html diff --git a/courses/comprehensive_rust_training/190_testing/02_other.rst b/courses/comprehensive_rust_training/190_testing/02_other.rst index fbf56faa3..146224bd5 100644 --- a/courses/comprehensive_rust_training/190_testing/02_other.rst +++ b/courses/comprehensive_rust_training/190_testing/02_other.rst @@ -49,5 +49,5 @@ Rust has built-in support for documentation tests: - The code will be compiled and executed as part of ``cargo test``. - Adding ``#`` in the code will hide it from the docs, but will still compile/run it. -- Test the above code on the `Rust - Playground `__. +- Test the above code on the + `Rust Playground `__. diff --git a/courses/comprehensive_rust_training/200_error_handling/02_result.rst b/courses/comprehensive_rust_training/200_error_handling/02_result.rst index 008941a9d..8eb6f64ea 100644 --- a/courses/comprehensive_rust_training/200_error_handling/02_result.rst +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -7,7 +7,7 @@ ------------ Our primary mechanism for error handling in Rust is the -```Result`` `__ +`Result `__ enum, which we briefly saw when discussing standard library types. .. code:: rust,editable diff --git a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst index f4aff8be6..bb84afdcf 100644 --- a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -6,7 +6,7 @@ ``thiserror`` --------------- -The ```thiserror`` `__ crate provides macros +The `thiserror `__ crate provides macros to help avoid boilerplate when defining error types. It provides derive macros that assist in implementing ``From``, ``Display``, and the ``Error`` trait. diff --git a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst index e95b801a7..c920169c8 100644 --- a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -6,13 +6,13 @@ ``anyhow`` ------------ -The ```anyhow`` `__ crate provides a rich error +The `anyhow `__ crate provides a rich error type with support for carrying additional contextual information, which can be used to provide a semantic trace of what the program was doing leading up to the error. This can be combined with the convenience macros from -```thiserror`` `__ to avoid writing out +`thiserror `__ to avoid writing out trait impls explicitly for custom error types. .. code:: rust,editable,compile_fail @@ -77,7 +77,7 @@ More to Explore - ``anyhow::Error`` has support for downcasting, much like ``std::any::Any``; the specific error type stored inside can be extracted for examination if desired with - ```Error::downcast`` `__. + `Error::downcast `__. .. raw:: html diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst index d3c398c19..3dec2e85f 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst @@ -27,8 +27,8 @@ Unsafe Rust gives you access to five new capabilities: - Implement ``unsafe`` traits. We will briefly cover unsafe capabilities next. For full details, please -see `Chapter 19.1 in the Rust -Book `__ and +see +`Chapter 19.1 in the Rust Book `__ and the `Rustonomicon `__. .. raw:: html diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst index 8d0f2c5c9..7f0ee8785 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst @@ -33,9 +33,9 @@ They are occasionally needed for interacting with C library APIs. If you just want to reinterpret bytes as a different type, you probably want -```std::mem::transmute`` `__ +`std::mem::transmute `__ or a safe wrapper such as the -```zerocopy`` `__ crate. +`zerocopy `__ crate. .. raw:: html diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst b/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst index e1acf65b3..cc066daa6 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst @@ -100,8 +100,8 @@ things with pointers which might violate Rust's memory model, but in general any C function might have undefined behaviour under any arbitrary circumstances. -The ``"C"`` in this example is the ABI; `other ABIs are available -too `__. +The ``"C"`` in this example is the ABI; +`other ABIs are available too `__. .. _writing-unsafe-functions-1: diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst b/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst index c951f76b2..5af6dd949 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst @@ -11,8 +11,7 @@ implementation must guarantee particular conditions to avoid undefined behaviour. For example, the ``zerocopy`` crate has an unsafe trait that looks -`something like -this `__: +`something like this `__: .. code:: rust,editable diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst index 352611237..35a9153d3 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst @@ -13,44 +13,32 @@ directory. You will want to consult the manual pages: -- ```opendir(3)`` `__ -- ```readdir(3)`` `__ -- ```closedir(3)`` `__ +- `opendir(3) `__ +- `readdir(3) `__ +- `closedir(3) `__ You will also want to browse the -```std::ffi`` `__ module. There you +`std::ffi `__ module. There you find a number of string types which you need for the exercise: -+-------------------------+-------------+-----------------------------+ -| Types | Encoding | Use | -+=========================+=============+=============================+ -| ```str`` `__ | | | -| and | | | -| ``` | | | -| String`` `__ | | | -+-------------------------+-------------+-----------------------------+ -| ```CStr`` `__ | | | -| and | | | -| `` | | | -| `CString`` `__ | | | -+-------------------------+-------------+-----------------------------+ -| ```OsStr`` `__ | | | -| and | | | -| ```O | | | -| sString`` `__ | | | -+-------------------------+-------------+-----------------------------+ +.. list-table:: + :header-rows: 1 + + * - Types + - Encoding + - Use + + * - `str `__ and `String `__ + - UTF-8 + - Text processing in Rust + + * - `CStr `__ and `CString `__ + - NUL-terminated + - Communicating with C functions + + * - `OsStr `__ and `OsString `__ + - OS-specific + - Communicating with the OS You will convert between all these types: @@ -63,7 +51,7 @@ You will convert between all these types: for "some unknown data", - ``&[u8]`` to ``&OsStr``: ``&OsStr`` is a step towards ``OsString``, use - ```OsStrExt`` `__ + `OsStrExt `__ to create it, - ``&OsStr`` to ``OsString``: you need to clone the data in ``&OsStr`` to be able to return it and call ``readdir`` again. From 00c6a622207e1fea800201e4785ee9d7b85e4360 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:34:58 -0500 Subject: [PATCH 4/7] Auto-generates slides on Github --- contrib/rst_files_with_prelude.txt | 1 + courses/comprehensive_rust_training/course.toml | 1 + 2 files changed, 2 insertions(+) create mode 100644 courses/comprehensive_rust_training/course.toml diff --git a/contrib/rst_files_with_prelude.txt b/contrib/rst_files_with_prelude.txt index ac527f9ea..519ec3958 100644 --- a/contrib/rst_files_with_prelude.txt +++ b/contrib/rst_files_with_prelude.txt @@ -10,3 +10,4 @@ courses/gnattest/*.rst courses/gnat_project_facility/*.rst courses/gnatcoverage/*.rst courses/rust_essentials/*.rst +courses/comprehensive_rust_training/*.rst diff --git a/courses/comprehensive_rust_training/course.toml b/courses/comprehensive_rust_training/course.toml new file mode 100644 index 000000000..6da1dc1c2 --- /dev/null +++ b/courses/comprehensive_rust_training/course.toml @@ -0,0 +1 @@ +name = "Comprehensive Rust Training" From 0ef0cca878ae452f5ad023c94bab3e22abd968a7 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Tue, 4 Feb 2025 09:41:27 -0500 Subject: [PATCH 5/7] Add an introduction section --- .../010_introduction.rst | 41 +++++++++ .../010_introduction/01-about_adacore.rst | 21 +++++ .../02-about_this_training.rst | 83 +++++++++++++++++++ .../standard_course.txt | 1 + 4 files changed, 146 insertions(+) create mode 100644 courses/comprehensive_rust_training/010_introduction.rst create mode 100644 courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst create mode 100644 courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst diff --git a/courses/comprehensive_rust_training/010_introduction.rst b/courses/comprehensive_rust_training/010_introduction.rst new file mode 100644 index 000000000..1adfdfd1d --- /dev/null +++ b/courses/comprehensive_rust_training/010_introduction.rst @@ -0,0 +1,41 @@ +************ +Introduction +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. container:: PRELUDE SYMBOLS + +.. |rightarrow| replace:: :math:`\rightarrow` +.. |forall| replace:: :math:`\forall` +.. |exists| replace:: :math:`\exists` +.. |equivalent| replace:: :math:`\iff` +.. |le| replace:: :math:`\le` +.. |ge| replace:: :math:`\ge` +.. |lt| replace:: :math:`<` +.. |gt| replace:: :math:`>` +.. |checkmark| replace:: :math:`\checkmark` + +.. container:: PRELUDE REQUIRES + +.. container:: PRELUDE PROVIDES + +.. + About Adacore + About This Training + +.. container:: PRELUDE END + +.. include:: 010_introduction/01-about_adacore.rst +.. include:: 010_introduction/02-about_this_training.rst diff --git a/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst b/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst new file mode 100644 index 000000000..24d52ae24 --- /dev/null +++ b/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst @@ -0,0 +1,21 @@ +============= +About AdaCore +============= + +----------- +The Company +----------- + +.. + Taken from https://www.adacore.com/company/about + +* Founded in 1994 +* Centered around helping developers build **safe, secure and reliable** software +* Headquartered in New York and Paris + + - Representatives in countries around the globe + +* Roots in Open Source software movement + + - GNAT compiler is part of GNU Compiler Collection (GCC) + diff --git a/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst b/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst new file mode 100644 index 000000000..81b578774 --- /dev/null +++ b/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst @@ -0,0 +1,83 @@ +=================== +About This Training +=================== + +-------------------------- +Your Trainer +-------------------------- + +* Experience in software development + + - Languages + - Methodology + +* Experience teaching this class + +----------------------------- +Goals of the training session +----------------------------- + +* What you should know by the end of the training +* Syllabus overview + + - The syllabus is a guide, but we might stray off of it + - ...and that's OK: we're here to cover **your needs** + +---------- +Roundtable +---------- + +* 5 minute exercise + + - Write down your answers to the following + - Then share it with the room + +* Experience in software development + + - Languages + - Methodology + +* Experience and interest with the syllabus + + - Current and upcoming projects + - Curious for something? + +* Your personal goals for this training + + - What do you want to have coming out of this? + +* Anecdotes, stories... feel free to share! + + - Most interesting or funny bug you've encountered? + - Your own programming interests? + +------------------- +Course Presentation +------------------- + +* Slides +* Quizzes +* Labs + + - Hands-on practice + - Recommended setup: latest GNAT Studio + - Class reflection after some labs + +* Demos + + - Depending on the context + +* Daily schedule + +-------- +Styles +-------- + +* :dfn:`This` is a definition +* :filename:`this/is/a.path` +* :ada:`code is highlighted` +* :command:`commands are emphasised --like-this` + +.. warning:: This is a warning +.. note:: This is an important piece of info +.. tip:: This is a tip diff --git a/courses/comprehensive_rust_training/standard_course.txt b/courses/comprehensive_rust_training/standard_course.txt index aa3146aa0..f53aca18c 100644 --- a/courses/comprehensive_rust_training/standard_course.txt +++ b/courses/comprehensive_rust_training/standard_course.txt @@ -1,3 +1,4 @@ +010_introduction.rst 020_hello_world.rst 030_types_and_values.rst 040_control_flow_basics.rst From 94cf42f93941b0222923b26f0d880b87831d642a Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Tue, 4 Feb 2025 09:48:54 -0500 Subject: [PATCH 6/7] Create a comprehensive course with testing and unsafe rust Standard course does not have these two modules --- .../comprehensive_course.txt | 21 +++++++++++++++++++ .../standard_course.txt | 2 -- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 courses/comprehensive_rust_training/comprehensive_course.txt diff --git a/courses/comprehensive_rust_training/comprehensive_course.txt b/courses/comprehensive_rust_training/comprehensive_course.txt new file mode 100644 index 000000000..f53aca18c --- /dev/null +++ b/courses/comprehensive_rust_training/comprehensive_course.txt @@ -0,0 +1,21 @@ +010_introduction.rst +020_hello_world.rst +030_types_and_values.rst +040_control_flow_basics.rst +050_tuples_and_arrays.rst +060_references.rst +070_user_defined_types.rst +080_pattern_matching.rst +090_methods_and_traits.rst +100_generics.rst +110_std_types.rst +120_std_traits.rst +130_memory_management.rst +140_smart_pointers.rst +150_borrowing.rst +160_lifetimes.rst +170_iterators.rst +180_modules.rst +190_testing.rst +200_error_handling.rst +210_unsafe_rust.rst diff --git a/courses/comprehensive_rust_training/standard_course.txt b/courses/comprehensive_rust_training/standard_course.txt index f53aca18c..83fb911a2 100644 --- a/courses/comprehensive_rust_training/standard_course.txt +++ b/courses/comprehensive_rust_training/standard_course.txt @@ -16,6 +16,4 @@ 160_lifetimes.rst 170_iterators.rst 180_modules.rst -190_testing.rst 200_error_handling.rst -210_unsafe_rust.rst From 3667e0cbb16fef86c784370aa8306f038b841104 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:33:42 -0500 Subject: [PATCH 7/7] Update preludes to include Rust role --- courses/comprehensive_rust_training/010_introduction.rst | 3 +++ courses/comprehensive_rust_training/020_hello_world.rst | 3 +++ courses/comprehensive_rust_training/030_types_and_values.rst | 3 +++ .../comprehensive_rust_training/040_control_flow_basics.rst | 3 +++ courses/comprehensive_rust_training/050_tuples_and_arrays.rst | 3 +++ courses/comprehensive_rust_training/060_references.rst | 3 +++ courses/comprehensive_rust_training/070_user_defined_types.rst | 3 +++ courses/comprehensive_rust_training/080_pattern_matching.rst | 3 +++ courses/comprehensive_rust_training/090_methods_and_traits.rst | 3 +++ courses/comprehensive_rust_training/100_generics.rst | 3 +++ courses/comprehensive_rust_training/110_std_types.rst | 3 +++ courses/comprehensive_rust_training/120_std_traits.rst | 3 +++ courses/comprehensive_rust_training/130_memory_management.rst | 3 +++ courses/comprehensive_rust_training/140_smart_pointers.rst | 3 +++ courses/comprehensive_rust_training/150_borrowing.rst | 3 +++ courses/comprehensive_rust_training/160_lifetimes.rst | 3 +++ courses/comprehensive_rust_training/170_iterators.rst | 3 +++ courses/comprehensive_rust_training/180_modules.rst | 3 +++ courses/comprehensive_rust_training/190_testing.rst | 3 +++ courses/comprehensive_rust_training/200_error_handling.rst | 3 +++ courses/comprehensive_rust_training/210_unsafe_rust.rst | 3 +++ 21 files changed, 63 insertions(+) diff --git a/courses/comprehensive_rust_training/010_introduction.rst b/courses/comprehensive_rust_training/010_introduction.rst index 1adfdfd1d..871d7e9d0 100644 --- a/courses/comprehensive_rust_training/010_introduction.rst +++ b/courses/comprehensive_rust_training/010_introduction.rst @@ -15,6 +15,9 @@ Introduction .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/020_hello_world.rst b/courses/comprehensive_rust_training/020_hello_world.rst index e57e4504c..dfebc51e3 100644 --- a/courses/comprehensive_rust_training/020_hello_world.rst +++ b/courses/comprehensive_rust_training/020_hello_world.rst @@ -15,6 +15,9 @@ Hello World .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/030_types_and_values.rst b/courses/comprehensive_rust_training/030_types_and_values.rst index be22347cf..4444b8c0b 100644 --- a/courses/comprehensive_rust_training/030_types_and_values.rst +++ b/courses/comprehensive_rust_training/030_types_and_values.rst @@ -15,6 +15,9 @@ Types And Values .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/040_control_flow_basics.rst b/courses/comprehensive_rust_training/040_control_flow_basics.rst index bdbec3d2b..856f38bac 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics.rst @@ -15,6 +15,9 @@ Control Flow Basics .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays.rst index b5b9e080e..526591d33 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays.rst @@ -15,6 +15,9 @@ Tuples And Arrays .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/060_references.rst b/courses/comprehensive_rust_training/060_references.rst index 3c7fba0c4..cf81e3e22 100644 --- a/courses/comprehensive_rust_training/060_references.rst +++ b/courses/comprehensive_rust_training/060_references.rst @@ -15,6 +15,9 @@ References .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/070_user_defined_types.rst b/courses/comprehensive_rust_training/070_user_defined_types.rst index 95e6d4573..2823130b1 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types.rst @@ -15,6 +15,9 @@ User Defined Types .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/080_pattern_matching.rst b/courses/comprehensive_rust_training/080_pattern_matching.rst index e8b85a6ff..6104446e0 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching.rst @@ -15,6 +15,9 @@ Pattern Matching .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/090_methods_and_traits.rst b/courses/comprehensive_rust_training/090_methods_and_traits.rst index f2e751b80..d2ae4c12f 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits.rst @@ -15,6 +15,9 @@ Methods And Traits .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/100_generics.rst b/courses/comprehensive_rust_training/100_generics.rst index d1351be38..a08b63e3f 100644 --- a/courses/comprehensive_rust_training/100_generics.rst +++ b/courses/comprehensive_rust_training/100_generics.rst @@ -15,6 +15,9 @@ Generics .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/110_std_types.rst b/courses/comprehensive_rust_training/110_std_types.rst index 76f4fddbe..56e4b1186 100644 --- a/courses/comprehensive_rust_training/110_std_types.rst +++ b/courses/comprehensive_rust_training/110_std_types.rst @@ -15,6 +15,9 @@ Std Types .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/120_std_traits.rst b/courses/comprehensive_rust_training/120_std_traits.rst index aae2116c8..7025ac99a 100644 --- a/courses/comprehensive_rust_training/120_std_traits.rst +++ b/courses/comprehensive_rust_training/120_std_traits.rst @@ -15,6 +15,9 @@ Std Traits .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/130_memory_management.rst b/courses/comprehensive_rust_training/130_memory_management.rst index 020134370..328a173c9 100644 --- a/courses/comprehensive_rust_training/130_memory_management.rst +++ b/courses/comprehensive_rust_training/130_memory_management.rst @@ -15,6 +15,9 @@ Memory Management .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/140_smart_pointers.rst b/courses/comprehensive_rust_training/140_smart_pointers.rst index ef73dfa51..9ff353a0b 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -15,6 +15,9 @@ Smart Pointers .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/150_borrowing.rst b/courses/comprehensive_rust_training/150_borrowing.rst index afa79700e..17b9f5f84 100644 --- a/courses/comprehensive_rust_training/150_borrowing.rst +++ b/courses/comprehensive_rust_training/150_borrowing.rst @@ -15,6 +15,9 @@ Borrowing .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/160_lifetimes.rst b/courses/comprehensive_rust_training/160_lifetimes.rst index 5318fc755..e2fc4be60 100644 --- a/courses/comprehensive_rust_training/160_lifetimes.rst +++ b/courses/comprehensive_rust_training/160_lifetimes.rst @@ -15,6 +15,9 @@ Lifetimes .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/170_iterators.rst b/courses/comprehensive_rust_training/170_iterators.rst index a5f89d40f..b3c922f17 100644 --- a/courses/comprehensive_rust_training/170_iterators.rst +++ b/courses/comprehensive_rust_training/170_iterators.rst @@ -15,6 +15,9 @@ Iterators .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/180_modules.rst b/courses/comprehensive_rust_training/180_modules.rst index f76f8e3b1..beda480e2 100644 --- a/courses/comprehensive_rust_training/180_modules.rst +++ b/courses/comprehensive_rust_training/180_modules.rst @@ -15,6 +15,9 @@ Modules .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/190_testing.rst b/courses/comprehensive_rust_training/190_testing.rst index 3b2d61dbe..68b64b594 100644 --- a/courses/comprehensive_rust_training/190_testing.rst +++ b/courses/comprehensive_rust_training/190_testing.rst @@ -15,6 +15,9 @@ Testing .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/200_error_handling.rst b/courses/comprehensive_rust_training/200_error_handling.rst index 7b378a558..e8a88c20b 100644 --- a/courses/comprehensive_rust_training/200_error_handling.rst +++ b/courses/comprehensive_rust_training/200_error_handling.rst @@ -15,6 +15,9 @@ Error Handling .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow` diff --git a/courses/comprehensive_rust_training/210_unsafe_rust.rst b/courses/comprehensive_rust_training/210_unsafe_rust.rst index d39fd99bf..5dcfeb734 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust.rst @@ -15,6 +15,9 @@ Unsafe Rust .. role:: cpp(code) :language: C++ +.. role:: rust(code) + :language: Rust + .. container:: PRELUDE SYMBOLS .. |rightarrow| replace:: :math:`\rightarrow`