From f9d4b4704ca3ad6a6c92168800d05a4a2aa7a1f0 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:34:08 -0500 Subject: [PATCH 01/55] Auto-replace ``token`` with :rust:`token` --- .../020_hello_world/01_what_is_rust.rst | 2 +- .../020_hello_world/02_benefits.rst | 2 +- .../020_hello_world/03_playground.rst | 2 +- .../030_types_and_values/01_hello_world.rst | 8 +-- .../030_types_and_values/02_variables.rst | 14 ++--- .../030_types_and_values/03_values.rst | 32 +++++------ .../030_types_and_values/04_arithmetic.rst | 8 +-- .../030_types_and_values/05_inference.rst | 8 +-- .../030_types_and_values/06_exercise.rst | 6 +-- .../040_control_flow_basics/01_if.rst | 26 ++++----- .../040_control_flow_basics/02_match.rst | 38 ++++++------- .../040_control_flow_basics/03_loops.rst | 8 +-- .../04_break_continue.rst | 14 ++--- .../05_blocks_and_scopes.rst | 10 ++-- .../040_control_flow_basics/06_functions.rst | 12 ++--- .../040_control_flow_basics/07_macros.rst | 14 ++--- .../040_control_flow_basics/08_exercise.rst | 4 +- .../050_tuples_and_arrays/01_arrays.rst | 16 +++--- .../050_tuples_and_arrays/02_tuples.rst | 8 +-- .../050_tuples_and_arrays/03_iteration.rst | 12 ++--- .../04_destructuring.rst | 8 +-- .../050_tuples_and_arrays/05_exercise.rst | 4 +- .../060_references/01_shared.rst | 18 +++---- .../060_references/02_exclusive.rst | 12 ++--- .../060_references/03_slices.rst | 20 +++---- .../060_references/04_strings.rst | 48 ++++++++--------- .../060_references/05_dangling.rst | 8 +-- .../060_references/06_exercise.rst | 4 +- .../01_named_structs.rst | 6 +-- .../02_tuple_structs.rst | 10 ++-- .../070_user_defined_types/03_enums.rst | 24 ++++----- .../070_user_defined_types/04_aliases.rst | 6 +-- .../070_user_defined_types/05_const.rst | 14 ++--- .../070_user_defined_types/06_static.rst | 24 ++++----- .../070_user_defined_types/07_exercise.rst | 8 +-- .../080_pattern_matching/01_match.rst | 36 ++++++------- .../02_destructuring_structs.rst | 10 ++-- .../03_destructuring_enums.rst | 14 ++--- .../04_let_control_flow.rst | 46 ++++++++-------- .../080_pattern_matching/05_exercise.rst | 18 +++---- .../090_methods_and_traits/01_methods.rst | 34 ++++++------ .../090_methods_and_traits/02_traits.rst | 2 +- .../090_methods_and_traits/03_deriving.rst | 6 +-- .../090_methods_and_traits/04_exercise.rst | 12 ++--- .../100_generics/01_generic_functions.rst | 12 ++--- .../100_generics/02_generic_data.rst | 16 +++--- .../100_generics/03_generic_traits.rst | 8 +-- .../100_generics/04_trait_bounds.rst | 18 +++---- .../100_generics/05_impl_trait.rst | 24 ++++----- .../100_generics/06_dyn_trait.rst | 32 +++++------ .../100_generics/07_exercise.rst | 8 +-- .../110_std_types/01_std.rst | 18 +++---- .../110_std_types/02_docs.rst | 10 ++-- .../110_std_types/03_option.rst | 26 ++++----- .../110_std_types/04_result.rst | 16 +++--- .../110_std_types/05_string.rst | 42 +++++++-------- .../110_std_types/06_vec.rst | 30 +++++------ .../110_std_types/07_hashmap.rst | 20 +++---- .../110_std_types/08_exercise.rst | 10 ++-- .../120_std_traits/01_comparisons.rst | 28 +++++----- .../120_std_traits/02_operators.rst | 22 ++++---- .../120_std_traits/03_from_and_into.rst | 20 +++---- .../120_std_traits/04_casting.rst | 22 ++++---- .../120_std_traits/05_read_and_write.rst | 12 ++--- .../120_std_traits/06_default.rst | 18 +++---- .../120_std_traits/07_closures.rst | 36 ++++++------- .../120_std_traits/08_exercise.rst | 4 +- .../130_memory_management/01_review.rst | 10 ++-- .../130_memory_management/02_approaches.rst | 6 +-- .../130_memory_management/03_ownership.rst | 2 +- .../130_memory_management/04_move.rst | 54 +++++++++---------- .../130_memory_management/05_clone.rst | 14 ++--- .../130_memory_management/06_copy_types.rst | 26 ++++----- .../130_memory_management/07_drop.rst | 28 +++++----- .../130_memory_management/08_exercise.rst | 2 +- .../140_smart_pointers/01_box.rst | 36 ++++++------- .../140_smart_pointers/02_rc.rst | 22 ++++---- .../140_smart_pointers/03_trait_objects.rst | 24 ++++----- .../140_smart_pointers/04_exercise.rst | 2 +- .../150_borrowing/01_shared.rst | 10 ++-- .../150_borrowing/02_borrowck.rst | 16 +++--- .../150_borrowing/03_examples.rst | 4 +- .../160_lifetimes/01_lifetime_annotations.rst | 22 ++++---- .../160_lifetimes/02_lifetime_elision.rst | 10 ++-- .../160_lifetimes/03_struct_lifetimes.rst | 12 ++--- .../160_lifetimes/04_exercise.rst | 34 ++++++------ .../170_iterators/01_motivation.rst | 14 ++--- .../170_iterators/02_iterator.rst | 22 ++++---- .../170_iterators/03_helpers.rst | 16 +++--- .../170_iterators/04_collect.rst | 34 ++++++------ .../170_iterators/05_intoiterator.rst | 44 +++++++-------- .../170_iterators/06_exercise.rst | 2 +- .../180_modules/01_modules.rst | 8 +-- .../180_modules/02_filesystem.rst | 28 +++++----- .../180_modules/03_visibility.rst | 12 ++--- .../180_modules/04_encapsulation.rst | 14 ++--- .../180_modules/05_paths.rst | 26 ++++----- .../180_modules/06_exercise.rst | 10 ++-- .../190_testing/01_unit_tests.rst | 10 ++-- .../190_testing/02_other.rst | 14 ++--- .../190_testing/03_lints.rst | 8 +-- .../190_testing/04_exercise.rst | 8 +-- .../200_error_handling/01_panics.rst | 16 +++--- .../200_error_handling/02_result.rst | 20 +++---- .../200_error_handling/03_try.rst | 26 ++++----- .../200_error_handling/04_try_conversions.rst | 38 ++++++------- .../200_error_handling/05_error.rst | 12 ++--- .../200_error_handling/06_thiserror.rst | 18 +++---- .../200_error_handling/07_anyhow.rst | 28 +++++----- .../200_error_handling/08_exercise.rst | 8 +-- .../210_unsafe_rust/01_unsafe.rst | 6 +-- .../210_unsafe_rust/02_dereferencing.rst | 10 ++-- .../210_unsafe_rust/03_mutable_static.rst | 8 +-- .../210_unsafe_rust/04_unions.rst | 2 +- .../210_unsafe_rust/05_unsafe_functions.rst | 20 +++---- .../210_unsafe_rust/06_unsafe_traits.rst | 12 ++--- .../210_unsafe_rust/07_exercise.rst | 22 ++++---- 117 files changed, 969 insertions(+), 969 deletions(-) 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 index 152c69241..58845362a 100644 --- 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 @@ -11,7 +11,7 @@ Rust is a new programming language which had its `1.0 release in - Rust is a statically compiled language in a similar role as C++ - - ``rustc`` uses LLVM as its backend. + - :rust:`rustc` uses LLVM as its backend. - Rust supports many `platforms and architectures `__: diff --git a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst index aa7f15f39..3cacd4c2a 100644 --- a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst +++ b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst @@ -14,7 +14,7 @@ Some unique selling points of Rust: - No uninitialized variables. - No double-frees. - No use-after-free. - - No ``NULL`` pointers. + - No :rust:`NULL` pointers. - No forgotten locked mutexes. - No data races between threads. - No iterator invalidation. diff --git a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst index 4edb31cec..1fef7491e 100644 --- a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst +++ b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst @@ -11,7 +11,7 @@ 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 +- Under "Tools", use the :rust:`rustfmt` option to format your code in the "standard" way. - Rust has two main "profiles" for generating code: Debug (extra 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 f44de4689..e28644dfd 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 @@ -9,7 +9,7 @@ Hello, World Let us jump into the simplest possible Rust program, a classic Hello World program: -.. code:: rust,editable +.. code:: rust fn main() { println!("Hello World!"); @@ -17,10 +17,10 @@ World program: What you see: -- Functions are introduced with ``fn``. +- Functions are introduced with :rust:`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. +- The :rust:`main` function is the entry point of the program. +- Rust has hygienic macros, :rust:`println!` is an example of this. - Rust strings are UTF-8 encoded and can contain any Unicode character. .. 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 index d61956bc4..c2be512fc 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst @@ -7,9 +7,9 @@ Variables ----------- Rust provides type safety via static typing. Variable bindings are made -with ``let``: +with :rust:`let`: -.. code:: rust,editable,warnunused +.. code:: rust fn main() { let x: i32 = 10; @@ -24,15 +24,15 @@ with ``let``: Details --------- -- Uncomment the ``x = 20`` to demonstrate that variables are immutable - by default. Add the ``mut`` keyword to allow changes. +- Uncomment the :rust:`x = 20` to demonstrate that variables are immutable + by default. Add the :rust:`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 + unnecessary :rust:`mut`. These are omitted in most slides to avoid distracting warnings. Try removing the mutation but leaving the - ``mut`` keyword in place. + :rust:`mut` keyword in place. -- The ``i32`` here is the type of the variable. This must be known at +- The :rust:`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. 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 3104ff30f..0a4298578 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 @@ -12,30 +12,30 @@ each type. +---------------+-------------------------------+---------------------+ | | Types | Literals | +===============+===============================+=====================+ -| Signed | ``i8``, ``i16``, ``i32``, | ``-10``, ``0``, | -| integers | ``i64``, ``i128``, ``isize`` | ``1_000``, | -| | | ``123_i64`` | +| Signed | :rust:`i8`, :rust:`i16`, :rust:`i32`, | :rust:`-10`, :rust:`0`, | +| integers | :rust:`i64`, :rust:`i128`, :rust:`isize` | :rust:`1_000`, | +| | | :rust:`123_i64` | +---------------+-------------------------------+---------------------+ -| Unsigned | ``u8``, ``u16``, ``u32``, | ``0``, ``123``, | -| integers | ``u64``, ``u128``, ``usize`` | ``10_u16`` | +| Unsigned | :rust:`u8`, :rust:`u16`, :rust:`u32`, | :rust:`0`, :rust:`123`, | +| integers | :rust:`u64`, :rust:`u128`, :rust:`usize` | :rust:`10_u16` | +---------------+-------------------------------+---------------------+ -| Floating | ``f32``, ``f64`` | ``3.14``, | -| point numbers | | ``-10.0e20``, | -| | | ``2_f32`` | +| Floating | :rust:`f32`, :rust:`f64` | :rust:`3.14`, | +| point numbers | | :rust:`-10.0e20`, | +| | | :rust:`2_f32` | +---------------+-------------------------------+---------------------+ -| Unicode | ``char`` | ``'a'``, | +| Unicode | :rust:`char` | :rust:`'a'`, | | scalar values | | ':math:`\alpha`', | | | | ':math:`\infty`' | +---------------+-------------------------------+---------------------+ -| Booleans | ``bool`` | ``true``, ``false`` | +| Booleans | :rust:`bool` | :rust:`true`, :rust:`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. +- :rust:`iN`, :rust:`uN`, and :rust:`fN` are *N* bits wide, +- :rust:`isize` and :rust:`usize` are the width of a pointer, +- :rust:`char` is 32 bits wide, +- :rust:`bool` is 8 bits wide. .. raw:: html @@ -46,8 +46,8 @@ 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``. + only. So :rust:`1_000` can be written as :rust:`1000` (or :rust:`10_00`), and + :rust:`123_i64` can be written as :rust:`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 index 0aaea06f0..7ae7a0ca5 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst @@ -6,7 +6,7 @@ Arithmetic Arithmetic ------------ -.. code:: rust,editable +.. code:: rust fn interproduct(a: i32, b: i32, c: i32) -> i32 { return a * b + b * c + c * a; @@ -22,7 +22,7 @@ Arithmetic Details --------- -This is the first time we've seen a function other than ``main``, but +This is the first time we've seen a function other than :rust:`main`, but the meaning should be clear: it takes three integers, and returns an integer. Functions will be covered in more detail later. @@ -32,11 +32,11 @@ 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 +Change the :rust:`i32`\ 's to :rust:`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)``. +:rust:`(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. 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 index 01d20908c..47a1a5f5c 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst @@ -12,7 +12,7 @@ Rust will look at how the variable is *used* to determine the type: -.. code:: rust,editable +.. code:: rust fn takes_u32(x: u32) { println!("u32: {x}"); @@ -47,10 +47,10 @@ 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``. +:rust:`i32`. This sometimes appears as :rust:`{integer}` in error messages. +Similarly, floating-point literals default to :rust:`f64`. -.. code:: rust,compile_fail +.. code:: rust fn main() { let x = 3.14; 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 index de6527d43..64669332e 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst @@ -6,14 +6,14 @@ Exercise: Fibonacci Exercise: Fibonacci --------------------- -The Fibonacci sequence begins with ``[0,1]``. For n>1, the n'th +The Fibonacci sequence begins with :rust:`[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. +Write a function :rust:`fib(n)` that calculates the n'th Fibonacci number. When will this function panic? -.. code:: rust,editable,should_panic +.. code:: rust {{#include exercise.rs:fib}} if n < 2 { 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 4422f9101..8d44dfa59 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 @@ -1,16 +1,16 @@ ==================== -``if`` expressions +:rust:`if` expressions ==================== -------------------- -``if`` expressions +:rust:`if` expressions -------------------- You use `if expressions `__ -exactly like ``if`` statements in other languages: +exactly like :rust:`if` statements in other languages: -.. code:: rust,editable +.. code:: rust fn main() { let x = 10; @@ -23,10 +23,10 @@ exactly like ``if`` statements in other languages: } } -In addition, you can use ``if`` as an expression. The last expression of -each block becomes the value of the ``if`` expression: +In addition, you can use :rust:`if` as an expression. The last expression of +each block becomes the value of the :rust:`if` expression: -.. code:: rust,editable +.. code:: rust fn main() { let x = 10; @@ -40,14 +40,14 @@ each block becomes the value of the ``if`` expression: Details --------- -Because ``if`` is an expression and must have a particular type, both of +Because :rust:`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. +:rust:`;` after :rust:`"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. +An :rust:`if` expression should be used in the same way as the other +expressions. For example, when it is used in a :rust:`let` statement, the +statement must be terminated with a :rust:`;` as well. Remove the :rust:`;` +before :rust:`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 index d6834e96c..f44938dd1 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst @@ -1,14 +1,14 @@ ======================= -``match`` Expressions +:rust:`match` Expressions ======================= ----------------------- -``match`` Expressions +:rust:`match` Expressions ----------------------- -``match`` can be used to check a value against one or more options: +:rust:`match` can be used to check a value against one or more options: -.. code:: rust,editable +.. code:: rust fn main() { let val = 1; @@ -22,9 +22,9 @@ } } -Like ``if`` expressions, ``match`` can also return a value; +Like :rust:`if` expressions, :rust:`match` can also return a value; -.. code:: rust,editable +.. code:: rust fn main() { let flag = true; @@ -41,24 +41,24 @@ Like ``if`` expressions, ``match`` can also return a value; Details --------- -- ``match`` arms are evaluated from top to bottom, and the first one +- :rust:`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 +- There is no fall-through between cases the way that :rust:`switch` works in other languages. -- The body of a ``match`` arm can be a single expression or a block. +- The body of a :rust:`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 +- :rust:`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 + as :rust:`_`. 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. + a :rust:`bool`, which is the simplest primitive type. -- This slide introduces ``match`` without talking about pattern +- This slide introduces :rust:`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 @@ -68,14 +68,14 @@ Details 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`` +- To further motivate the usage of :rust:`match`, you can compare the + examples to their equivalents written with :rust:`if`. In the second case + matching on a :rust:`bool` an :rust:`if {} else {}` block is pretty similar. + But in the first example that checks multiple cases, a :rust:`match` expression can be more concise than - ``if {} else if {} else if {} else``. + :rust:`if {} else if {} else if {} else`. -- ``match`` also supports match guards, which allow you to add an +- :rust:`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 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 a8d7508d3..3aea2fd16 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 @@ -6,11 +6,11 @@ Loops Loops ------- -There are three looping keywords in Rust: ``while``, ``loop``, and -``for``: +There are three looping keywords in Rust: :rust:`while`, :rust:`loop`, and +:rust:`for`: ----------- -``while`` +:rust:`while` ----------- The @@ -18,7 +18,7 @@ The works much like in other languages, executing the loop body as long as the condition is true. -.. code:: rust,editable +.. code:: rust fn main() { let mut x = 200; 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 51fdfc7a5..d5cb6e981 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 @@ -1,9 +1,9 @@ ============================ -``break`` and ``continue`` +:rust:`break` and :rust:`continue` ============================ ---------------------------- -``break`` and ``continue`` +:rust:`break` and :rust:`continue` ---------------------------- If you want to immediately start the next iteration use @@ -11,10 +11,10 @@ If you want to immediately start the next iteration use 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. +With :rust:`loop`, this can take an optional expression that becomes the +value of the :rust:`loop` expression. -.. code:: rust,editable +.. code:: rust fn main() { let mut i = 0; @@ -36,9 +36,9 @@ value of the ``loop`` expression. Details --------- -Note that ``loop`` is the only looping construct which can return a +Note that :rust:`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 +:rust:`break` statement (unlike :rust:`while` and :rust:`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 index 9fbc8024a..d9ef46472 100644 --- 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 @@ -11,10 +11,10 @@ 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 +:rust:`{}`. Each block has a value and a type, which are those of the last expression of the block: -.. code:: rust,editable +.. code:: rust fn main() { let z = 13; @@ -26,8 +26,8 @@ expression of the block: println!("x: {x}"); } -If the last expression ends with ``;``, then the resulting value and -type is ``()``. +If the last expression ends with :rust:`;`, then the resulting value and +type is :rust:`()`. .. raw:: html @@ -37,7 +37,7 @@ 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``. + a :rust:`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 index 46976fac8..b723d6c0a 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 @@ -10,7 +10,7 @@ Functions -.. code:: rust,editable +.. code:: rust fn gcd(a: u32, b: u32) -> u32 { if b > 0 { @@ -33,12 +33,12 @@ 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``). + return value. Simply omit the :rust:`;` at the end of the expression. The + :rust:`return` keyword can be used for early return, but the "bare value" + form is idiomatic at the end of a function (refactor :rust:`gcd` to use a + :rust:`return`). - Some functions have no return value, and return the 'unit type', - ``()``. The compiler will infer this if the return type is omitted. + :rust:`()`. The compiler will infer this if the return type is omitted. - Overloading is not supported - each function has a single implementation. 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 eccc7b051..2bfa94388 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 @@ -7,21 +7,21 @@ Macros -------- Macros are expanded into Rust code during compilation, and can take a -variable number of arguments. They are distinguished by a ``!`` at the +variable number of arguments. They are distinguished by a :rust:`!` at the end. The Rust standard library includes an assortment of useful macros. -- ``println!(format, ..)`` prints a line to standard output, applying +- :rust:`println!(format, ..)` prints a line to standard output, applying formatting described in `std::fmt `__. -- ``format!(format, ..)`` works just like ``println!`` but returns the +- :rust:`format!(format, ..)` works just like :rust:`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, +- :rust:`dbg!(expression)` logs the value of the expression and returns it. +- :rust:`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, +- :rust:`unreachable!()` marks a bit of code as unreachable. If executed, it will panic. -.. code:: rust,editable +.. code:: rust fn factorial(n: u32) -> u32 { let mut product = 1; 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 c2a3f5993..2825f2e69 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 @@ -26,9 +26,9 @@ For example, beginning with *n1* = 3: - the sequence terminates. Write a function to calculate the length of the collatz sequence for a -given initial ``n``. +given initial :rust:`n`. -.. code:: rust,editable,should_panic +.. code:: rust {{#include exercise.rs:collatz_length}} todo!("Implement this") 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 index 4230596ef..0ee9d10eb 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst @@ -10,7 +10,7 @@ Arrays -.. code:: rust,editable +.. code:: rust fn main() { let mut a: [i8; 10] = [42; 10]; @@ -24,10 +24,10 @@ Arrays 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 +- A value of the array type :rust:`[T; N]` holds :rust:`N` (a compile-time + constant) elements of the same type :rust:`T`. Note that the length of + the array is *part of its type*, which means that :rust:`[u8; 3]` and + :rust:`[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 @@ -36,13 +36,13 @@ Details - 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, ``{:?}`` +- The :rust:`println!` macro asks for the debug implementation with the + :rust:`?` format parameter: :rust:`{}` gives the default output, :rust:`{:?}` 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, +- Adding :rust:`#`, eg :rust:`{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 index e998ccc99..24d1e90f4 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst @@ -10,7 +10,7 @@ Tuples -.. code:: rust,editable +.. code:: rust fn main() { let t: (i8, bool) = (7, true); @@ -29,10 +29,10 @@ Details - 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``. + value, e.g. :rust:`t.0`, :rust:`t.1`. -- The empty tuple ``()`` is referred to as the "unit type" and - signifies absence of a return value, akin to ``void`` in other +- The empty tuple :rust:`()` is referred to as the "unit type" and + signifies absence of a return value, akin to :rust:`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 index 761da0eb5..fb73398b5 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst @@ -6,9 +6,9 @@ Array Iteration Array Iteration ----------------- -The ``for`` statement supports iterating over arrays (but not tuples). +The :rust:`for` statement supports iterating over arrays (but not tuples). -.. code:: rust,editable +.. code:: rust fn main() { let primes = [2, 3, 5, 7, 11, 13, 17, 19]; @@ -25,12 +25,12 @@ The ``for`` statement supports iterating over arrays (but not tuples). Details --------- -This functionality uses the ``IntoIterator`` trait, but we haven't +This functionality uses the :rust:`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. +The :rust:`assert_ne!` macro is new here. There are also :rust:`assert_eq!` and +:rust:`assert!` macros. These are always checked, while debug-only variants +like :rust:`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 index a670c7525..5958b45f4 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst @@ -10,7 +10,7 @@ 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 +.. code:: rust fn print_tuple(tuple: (i32, i32)) { let left = tuple.0; @@ -21,7 +21,7 @@ manually by directly accessing the inner values: However, Rust also supports using pattern matching to destructure a larger value into its constituent parts: -.. code:: rust,editable +.. code:: rust fn print_tuple(tuple: (i32, i32)) { let (left, right) = tuple; @@ -35,10 +35,10 @@ Details --------- - The patterns used here are "irrefutable", meaning that the compiler - can statically verify that the value on the right of ``=`` has the + can statically verify that the value on the right of :rust:`=` 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 + value, hence why we can also use :rust:`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. 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 c7fff5376..8db643227 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 @@ -14,7 +14,7 @@ Arrays can contain other arrays: What is the type of this variable? -Use an array such as the above to write a function ``transpose`` which +Use an array such as the above to write a function :rust:`transpose` which will transpose a matrix (turn rows into columns): Transpose @@ -40,7 +40,7 @@ into 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 +.. code:: rust // TODO: remove this when you're done with your implementation. #![allow(unused_variables, dead_code)] diff --git a/courses/comprehensive_rust_training/060_references/01_shared.rst b/courses/comprehensive_rust_training/060_references/01_shared.rst index a6274aa2c..331122c88 100644 --- a/courses/comprehensive_rust_training/060_references/01_shared.rst +++ b/courses/comprehensive_rust_training/060_references/01_shared.rst @@ -14,7 +14,7 @@ references are read-only, and the referenced data cannot change. -.. code:: rust,editable +.. code:: rust fn main() { let a = 'A'; @@ -25,8 +25,8 @@ references are read-only, and the referenced data cannot change. 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 +A shared reference to a type :rust:`T` has type :rust:`&T`. A reference value is +made with the :rust:`&` operator. The :rust:`*` operator "dereferences" a reference, yielding its value. .. raw:: html @@ -49,24 +49,24 @@ Details 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 +- Rust does not automatically create references for you - the :rust:`&` 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 ``->`` + methods (try :rust:`r.is_ascii()`). There is no need for an :rust:`->` 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 +- In this example, :rust:`r` is mutable so that it can be reassigned + (:rust:`r = &b`). Note that this re-binds :rust:`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'``. + even if that value was mutable. Try :rust:`*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 + :rust:`x_axis` would return a reference to :rust:`point`, but :rust:`point` will be deallocated when the function returns, so this will not compile. - We will talk more about borrowing when we get to ownership. diff --git a/courses/comprehensive_rust_training/060_references/02_exclusive.rst b/courses/comprehensive_rust_training/060_references/02_exclusive.rst index b9ec44233..264ab6c52 100644 --- a/courses/comprehensive_rust_training/060_references/02_exclusive.rst +++ b/courses/comprehensive_rust_training/060_references/02_exclusive.rst @@ -7,13 +7,13 @@ Exclusive References ---------------------- Exclusive references, also known as mutable references, allow changing -the value they refer to. They have type ``&mut T``. +the value they refer to. They have type :rust:`&mut T`. .. raw:: html -.. code:: rust,editable +.. code:: rust fn main() { let mut point = (1, 2); @@ -33,11 +33,11 @@ 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. + exclusive reference exists. Try making an :rust:`&point.0` or changing + :rust:`point.0` while :rust:`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 +- Be sure to note the difference between :rust:`let mut x_coord: &i32` and + :rust:`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. diff --git a/courses/comprehensive_rust_training/060_references/03_slices.rst b/courses/comprehensive_rust_training/060_references/03_slices.rst index 046fe9e54..8af661dd7 100644 --- a/courses/comprehensive_rust_training/060_references/03_slices.rst +++ b/courses/comprehensive_rust_training/060_references/03_slices.rst @@ -12,7 +12,7 @@ A slice gives you a view into a larger collection: -.. code:: rust,editable +.. code:: rust fn main() { let a: [i32; 6] = [10, 20, 30, 40, 50, 60]; @@ -31,24 +31,24 @@ A slice gives you a view into a larger collection: Details --------- -- We create a slice by borrowing ``a`` and specifying the starting and +- We create a slice by borrowing :rust:`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 starting index, meaning that :rust:`&a[0..a.len()]` and + :rust:`&a[..a.len()]` are identical. -- The same is true for the last index, so ``&a[2..a.len()]`` and - ``&a[2..]`` are identical. +- The same is true for the last index, so :rust:`&a[2..a.len()]` and + :rust:`&a[2..]` are identical. - To easily create a slice of the full array, we can therefore use - ``&a[..]``. + :rust:`&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 +- :rust:`s` is a reference to a slice of :rust:`i32`\ s. Notice that the type + of :rust:`s` (:rust:`&[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 +- Slices always borrow from another object. In this example, :rust:`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 index fa2a77c28..2b2944926 100644 --- a/courses/comprehensive_rust_training/060_references/04_strings.rst +++ b/courses/comprehensive_rust_training/060_references/04_strings.rst @@ -14,16 +14,16 @@ 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``. +- :rust:`&str` is a slice of UTF-8 encoded bytes, similar to :rust:`&[u8]`. +- :rust:`String` is an owned buffer of UTF-8 encoded bytes, similar to + :rust:`Vec`. .. raw:: html -.. code:: rust,editable +.. code:: rust fn main() { let s1: &str = "World"; @@ -44,56 +44,56 @@ We can now understand the two string types in Rust: Details --------- -- ``&str`` introduces a string slice, which is an immutable reference +- :rust:`&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. + literals (:rust:`"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. +- Rust's :rust:`String` type is a wrapper around a vector of bytes. As with + a :rust:`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. +- As with many other types :rust:`String::from()` creates a string from a + string literal; :rust:`String::new()` creates a new empty string, to + which string data can be added using the :rust:`push()` and + :rust:`push_str()` methods. -- The ``format!()`` macro is a convenient way to generate an owned +- The :rust:`format!()` macro is a convenient way to generate an owned string from dynamic values. It accepts the same format specification - as ``println!()``. + as :rust:`println!()`. -- You can borrow ``&str`` slices from ``String`` via ``&`` and +- You can borrow :rust:`&str` slices from :rust:`String` via :rust:`&` 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 + :rust:`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 +- For C++ programmers: think of :rust:`&str` as :rust:`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 + :rust:`String` is a rough equivalent of :rust:`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: +- Byte strings literals allow you to create a :rust:`&[u8]` value directly: .. raw:: html - .. code:: rust,editable + .. code:: rust 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 strings allow you to create a :rust:`&str` value with escapes + disabled: :rust:`r"\n" == "\\n"`. You can embed double-quotes by using an + equal amount of :rust:`#` on either side of the quotes: .. raw:: html - .. code:: rust,editable + .. code:: rust fn main() { println!(r#"link"#); diff --git a/courses/comprehensive_rust_training/060_references/05_dangling.rst b/courses/comprehensive_rust_training/060_references/05_dangling.rst index 1e32cc8fa..27185641a 100644 --- a/courses/comprehensive_rust_training/060_references/05_dangling.rst +++ b/courses/comprehensive_rust_training/060_references/05_dangling.rst @@ -7,15 +7,15 @@ 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 +safe to use. One rule is that references can never be :rust:`null`, making +them safe to use without :rust:`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 +.. code:: rust fn main() { let x_ref = { @@ -42,7 +42,7 @@ Details More to Explore ----------------- -- Rust's equivalent of nullability is the ``Option`` type, which can be +- Rust's equivalent of nullability is the :rust:`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. diff --git a/courses/comprehensive_rust_training/060_references/06_exercise.rst b/courses/comprehensive_rust_training/060_references/06_exercise.rst index 927816f8d..faf9f1b5c 100644 --- a/courses/comprehensive_rust_training/060_references/06_exercise.rst +++ b/courses/comprehensive_rust_training/060_references/06_exercise.rst @@ -7,10 +7,10 @@ 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 +representing a point as :rust:`[f64;3]`. It is up to you to determine the function signatures. -.. code:: rust,compile_fail +.. code:: rust // 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 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 index 9c9ea7464..6ffa1749d 100644 --- 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 @@ -8,7 +8,7 @@ Named Structs Like C and C++, Rust has support for custom structs: -.. code:: rust,editable +.. code:: rust struct Person { name: String, @@ -52,7 +52,7 @@ Key Points: - 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 + - Zero-sized structs (e.g. :rust:`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 @@ -60,7 +60,7 @@ Key Points: - 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 +- The syntax :rust:`..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. 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 a9b91c994..74f21b570 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 @@ -14,7 +14,7 @@ Tuple Structs If the field names are unimportant, you can use a tuple struct: -.. code:: rust,editable +.. code:: rust struct Point(i32, i32); @@ -25,7 +25,7 @@ If the field names are unimportant, you can use a tuple struct: This is often used for single-field wrappers (called newtypes): -.. code:: rust,editable,compile_fail +.. code:: rust struct PoundsOfForce(f64); struct Newtons(f64); @@ -52,13 +52,13 @@ 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 + - The number is measured in some units: :rust:`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)``. + :rust:`PhoneNumber(String)` or :rust:`OddNumber(u32)`. -- Demonstrate how to add a ``f64`` value to a ``Newtons`` type by +- Demonstrate how to add a :rust:`f64` value to a :rust:`Newtons` type by accessing the single field in the newtype. - Rust generally doesn't like inexplicit things, like automatic 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 721128ec9..38dde7e68 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 @@ -6,10 +6,10 @@ Enums Enums ------- -The ``enum`` keyword allows the creation of a type which has a few +The :rust:`enum` keyword allows the creation of a type which has a few different variants: -.. code:: rust,editable +.. code:: rust #[derive(Debug)] enum Direction { @@ -38,11 +38,11 @@ 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 +- :rust:`Direction` is a type with variants. There are two values of + :rust:`Direction`: :rust:`Direction::Left` and :rust:`Direction::Right`. +- :rust:`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. + which variant is in a :rust:`PlayerMove` value. - This might be a good time to compare structs and enums: - In both, you can have a simple version without fields (unit @@ -57,8 +57,8 @@ Key Points: - 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. + "niche optimization"). For example, :rust:`Option<&u8>` stores either + a pointer to an integer or :rust:`NULL` for the :rust:`None` variant. - You can control the discriminant if needed (e.g., for compatibility with C): @@ -67,7 +67,7 @@ Key Points: - .. code:: rust,editable + .. code:: rust #[repr(u32)] enum Bar { @@ -82,7 +82,7 @@ Key Points: println!("C: {}", Bar::C as u32); } - Without ``repr``, the discriminant type takes 2 bytes, because + Without :rust:`repr`, the discriminant type takes 2 bytes, because 10001 fits 2 bytes. ----------------- @@ -94,7 +94,7 @@ space. - Null pointer optimization: For `some types `__, Rust - guarantees that ``size_of::()`` equals ``size_of::>()``. + guarantees that :rust:`size_of::()` equals :rust:`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 @@ -105,7 +105,7 @@ space. - .. code:: rust,editable + .. code:: rust use std::mem::transmute; 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 index 75752ebca..5395db5c1 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst @@ -9,7 +9,7 @@ Type Aliases A type alias creates a name for another type. The two types can be used interchangeably. -.. code:: rust,editable +.. code:: rust enum CarryableConcreteItem { Left, @@ -31,9 +31,9 @@ Details - A `newtype `__ is often a better alternative since it creates a distinct type. Prefer - ``struct InventoryCount(usize)`` to ``type InventoryCount = usize``. + :rust:`struct InventoryCount(usize)` to :rust:`type InventoryCount = usize`. -- C programmers will recognize this as similar to a ``typedef``. +- C programmers will recognize this as similar to a :rust:`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 index 9fcc4b4a8..36e12798f 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 @@ -1,9 +1,9 @@ =========== -``const`` +:rust:`const` =========== ----------- -``const`` +:rust:`const` ----------- Constants are evaluated at compile time and their values are inlined @@ -13,7 +13,7 @@ wherever they are used: -.. code:: rust,editable +.. code:: rust const DIGEST_SIZE: usize = 3; const FILL_VALUE: u8 = calculate_fill_value(); @@ -43,8 +43,8 @@ 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 +Only functions marked :rust:`const` can be called at compile time to +generate :rust:`const` values. :rust:`const` functions can however be called at runtime. .. raw:: html @@ -53,8 +53,8 @@ runtime. Details --------- -- Mention that ``const`` behaves semantically similar to C++'s - ``constexpr`` +- Mention that :rust:`const` behaves semantically similar to C++'s + :rust:`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 index 510f0c1b0..d18f2c537 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 @@ -1,15 +1,15 @@ ============ -``static`` +:rust:`static` ============ ------------ -``static`` +:rust:`static` ------------ Static variables will live during the whole execution of the program, and therefore will not move: -.. code:: rust,editable +.. code:: rust static BANNER: &str = "Welcome to RustOS 3.14"; @@ -23,7 +23,7 @@ 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. +:rust:`const` is generally preferred. .. raw:: html @@ -31,9 +31,9 @@ globally-scoped value does not have a reason to need object identity, 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``. +- :rust:`static` is similar to mutable global variables in C++. +- :rust:`static` provides object identity: an address in memory and state + as required by types with interior mutability such as :rust:`Mutex`. ================= More to Explore @@ -43,16 +43,16 @@ More to Explore More to Explore ----------------- -Because ``static`` variables are accessible from any thread, they must -be ``Sync``. Interior mutability is possible through a +Because :rust:`static` variables are accessible from any thread, they must +be :rust:`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 +It is common to use :rust:`OnceLock` in a static as a way to support +initialization on first use. :rust:`OnceCell` is not :rust:`Sync` and thus cannot be used in this context. -Thread-local data can be created with the macro ``std::thread_local``. +Thread-local data can be created with the macro :rust:`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 index d89c8d97e..bbb5c104e 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst @@ -8,14 +8,14 @@ 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 ``{:?}``. +construct various events. Use :rust:`#[derive(Debug)]` to allow the types to +be formatted with :rust:`{:?}`. This exercise only requires creating and populating data structures so -that ``main`` runs without errors. The next part of the course will +that :rust:`main` runs without errors. The next part of the course will cover getting data out of these structures. -.. code:: rust,editable,should_panic +.. code:: rust {{#include exercise.rs:event}} // TODO: add required variants diff --git a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst index a15013aeb..eafa897cf 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst @@ -6,12 +6,12 @@ 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`` +The :rust:`match` keyword lets you match a value against one or more +*patterns*. The patterns can be simple values, similarly to :rust:`switch` in C and C++, but they can also be used to express more complex conditions: -.. code:: rust,editable +.. code:: rust #[rustfmt::skip] fn main() { @@ -25,7 +25,7 @@ conditions: } } -A variable in the pattern (``key`` in this example) will create a +A variable in the pattern (:rust:`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. @@ -43,21 +43,21 @@ 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 + - :rust:`|` as an :rust:`or` + - :rust:`..` can expand as much as it needs to be + - :rust:`1..=5` represents an inclusive range + - :rust:`_` 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`` +- They are not the same as separate :rust:`if` expression inside of the + match arm. An :rust:`if` expression inside of the branch block (after + :rust:`=>`) happens after the match arm is selected. Failing the :rust:`if` condition inside of that block won't result in other arms of the - original ``match`` expression being considered. + original :rust:`match` expression being considered. - The condition defined in the guard applies to every expression in a - pattern with an ``|``. + pattern with an :rust:`|`. ================= More To Explore @@ -67,7 +67,7 @@ More To Explore More To Explore ----------------- -- Another piece of pattern syntax you can show students is the ``@`` +- Another piece of pattern syntax you can show students is the :rust:`@` syntax which binds a part of a pattern to a variable. For example: .. code:: rust @@ -80,10 +80,10 @@ More To Explore 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 + In this example :rust:`inner` has the value 123 which it pulled from the + :rust:`Option` via destructuring, :rust:`outer` captures the entire + :rust:`Some(inner)` expression, so it contains the full + :rust:`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 index f6f3f7e2c..3342d8ea2 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst @@ -8,7 +8,7 @@ Structs Like tuples, Struct can also be destructured by matching: -.. code:: rust,editable +.. code:: rust {{#include ../../third_party/rust-by-example/destructuring-structs.rs}} @@ -18,12 +18,12 @@ Like tuples, Struct can also be destructured by matching: Details --------- -- Change the literal values in ``foo`` to match with the other +- Change the literal values in :rust:`foo` to match with the other patterns. -- Add a new field to ``Foo`` and make changes to the pattern as needed. +- Add a new field to :rust:`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 + hard to spot. Try changing the :rust:`2` in the second arm to a variable, + and see that it subtly doesn't work. Change it to a :rust:`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 index cc974fec6..326252068 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst @@ -10,9 +10,9 @@ 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: +simple :rust:`enum` type: -.. code:: rust,editable +.. code:: rust enum Result { Ok(i32), @@ -35,9 +35,9 @@ simple ``enum`` type: } } -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. +Here we have used the arms to *destructure* the :rust:`Result` value. In the +first arm, :rust:`half` is bound to the value inside the :rust:`Ok` variant. In +the second arm, :rust:`msg` is bound to the error message. .. raw:: html @@ -45,8 +45,8 @@ the second arm, ``msg`` is bound to the error message. Details --------- -- The ``if``/``else`` expression is returning an enum that is later - unpacked with a ``match``. +- The :rust:`if`/:rust:`else` expression is returning an enum that is later + unpacked with a :rust:`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 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 7b9413c2b..bebb69bb9 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 @@ -9,16 +9,16 @@ 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 +- :rust:`if let` expressions +- :rust:`let else` expressions +- :rust:`while let` expressions ======================== -``if let`` expressions +:rust:`if let` expressions ======================== ------------------------ -``if let`` expressions +:rust:`if let` expressions ------------------------ The @@ -26,7 +26,7 @@ The lets you execute different code depending on whether a value matches a pattern: -.. code:: rust,editable +.. code:: rust use std::time::Duration; @@ -43,20 +43,20 @@ pattern: } ========================== -``let else`` expressions +:rust:`let else` expressions ========================== -------------------------- -``let else`` expressions +:rust:`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 +The "else" case must diverge (:rust:`return`, :rust:`break`, or panic - anything but falling off the end of the block). -.. code:: rust,editable +.. code:: rust fn hex_or_die_trying(maybe_string: Option) -> Result { // TODO: The structure of this code is difficult to follow -- rewrite it with let-else! @@ -79,7 +79,7 @@ but falling off the end of the block). println!("result: {:?}", hex_or_die_trying(Some(String::from("foo")))); } -Like with ``if let``, there is a +Like with :rust:`if let`, there is a `while let `__ variant which repeatedly tests a value against a pattern: @@ -87,7 +87,7 @@ variant which repeatedly tests a value against a pattern: -.. code:: rust,editable +.. code:: rust fn main() { let mut name = String::from("Comprehensive Rust"); @@ -99,8 +99,8 @@ variant which repeatedly tests a value against a pattern: 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 +returns :rust:`Some(c)` until the string is empty, after which it will +return :rust:`None`. The :rust:`while let` lets us keep iterating through all items. .. raw:: html @@ -113,18 +113,18 @@ 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 +- Unlike :rust:`match`, :rust:`if let` does not have to cover all branches. + This can make it more concise than :rust:`match`. +- A common usage is handling :rust:`Some` values when working with + :rust:`Option`. +- Unlike :rust:`match`, :rust:`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 +:rust:`if-let`\ s can pile up, as shown. The :rust:`let-else` construct supports flattening this nested code. Rewrite the awkward version for students, so they can see the transformation. @@ -156,11 +156,11 @@ while-let while-let ----------- -- Point out that the ``while let`` loop will keep going as long as the +- Point out that the :rust:`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 +- You could rewrite the :rust:`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 + :rust:`name.pop()`. The :rust:`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 index c27dc8729..7585e9f7e 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst @@ -8,8 +8,8 @@ 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: +An example of a small arithmetic expression could be :rust:`10 + 20`, which +evaluates to :rust:`30`. We can represent the expression as a tree: .. raw:: html @@ -26,7 +26,7 @@ evaluates to ``30``. We can represent the expression as a tree: '--------' '--------' A bigger and more complex expression would be -``(10 * 9) + ((3 - 4) * 5)``, which evaluate to ``85``. We represent +:rust:`(10 * 9) + ((3 - 4) * 5)`, which evaluate to :rust:`85`. We represent this as a much bigger tree: .. raw:: html @@ -59,15 +59,15 @@ In code, we will represent the tree with two types: {{#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 +The :rust:`Box` type here is a smart pointer, and will be covered in detail +later in the course. An expression can be "boxed" with :rust:`Box::new` as seen in the tests. To evaluate a boxed expression, use the deref -operator (``*``) to "unbox" it: ``eval(*boxed_expr)``. +operator (:rust:`*`) to "unbox" it: :rust:`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]``: +:rust:`eval`. The final product should pass the tests. It may be helpful to +use :rust:`todo!()` and get the tests to pass one-by-one. You can also skip +a test temporarily with :rust:`#[ignore]`: .. code:: none 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 bcec67c18..72a04db1e 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 @@ -7,9 +7,9 @@ Methods --------- Rust allows you to associate functions with your new types. You do this -with an ``impl`` block: +with an :rust:`impl` block: -.. code:: rust,editable +.. code:: rust #[derive(Debug)] struct CarRace { @@ -54,21 +54,21 @@ with an ``impl`` block: // race.add_lap(42); } -The ``self`` arguments specify the "receiver" - the object the method +The :rust:`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 +- :rust:`&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 +- :rust:`&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 +- :rust:`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. +- :rust:`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. + used to create constructors which are called :rust:`new` by convention. .. raw:: html @@ -82,25 +82,25 @@ Key Points: functions. - Methods are called on an instance of a type (such as a struct or - enum), the first parameter represents the instance as ``self``. + enum), the first parameter represents the instance as :rust:`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. +- Point out the use of the keyword :rust:`self`, a method receiver. - - Show that it is an abbreviated term for ``self: Self`` and perhaps + - Show that it is an abbreviated term for :rust:`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`` + - Explain that :rust:`Self` is a type alias for the type the :rust:`impl` block is in and can be used elsewhere in the block. - - Note how ``self`` is used like other structs and dot notation can + - Note how :rust:`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 + - This might be a good time to demonstrate how the :rust:`&self` differs + from :rust:`self` by trying to run :rust:`finish` twice. + - Beyond variants on :rust:`self`, there are also `special wrapper types `__ - allowed to be receiver types, such as ``Box``. + allowed to be receiver types, such as :rust:`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 index 576f78323..4ca3f1d7c 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst @@ -9,7 +9,7 @@ Traits Rust lets you abstract over types with traits. They're similar to interfaces: -.. code:: rust,editable +.. code:: rust trait Pet { /// Return a sentence from this pet. 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 index 71637bfe2..4b62ac2cc 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst @@ -9,7 +9,7 @@ Deriving Supported traits can be automatically implemented for your custom types, as follows: -.. code:: rust,editable +.. code:: rust #[derive(Debug, Clone, Default)] struct Player { @@ -33,9 +33,9 @@ Details --------- Derivation is implemented with macros, and many crates provide useful -derive macros to add useful functionality. For example, ``serde`` can +derive macros to add useful functionality. For example, :rust:`serde` can derive serialization support for a struct using -``#[derive(Serialize)]``. +:rust:`#[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 index 9470d4fe0..b33499f1e 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst @@ -6,21 +6,21 @@ 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 +Let's design a simple logging utility, using a trait :rust:`Logger` with a +:rust:`log` method. Code which might log its progress can then take an +:rust:`&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 +However, the :rust:`StderrLogger` given below logs all messages, regardless +of verbosity. Your task is to write a :rust:`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 +.. code:: rust {{#include exercise.rs:setup}} diff --git a/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst index c997d1650..d8af0e06e 100644 --- a/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst +++ b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst @@ -10,7 +10,7 @@ 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 +.. code:: rust /// Pick `even` or `odd` depending on the value of `n`. fn pick(n: i32, even: T, odd: T) -> T { @@ -35,11 +35,11 @@ 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 +- In this example we only use the primitive types :rust:`i32` and :rust:`&str` + for :rust:`T`, but we can use any type here, including user-defined types: - .. code:: rust,ignore + .. code:: rust struct Foo { val: u8, @@ -49,8 +49,8 @@ Details - 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`` + types matching the constraints. For example, try modifying :rust:`pick` + to return :rust:`even + odd` if :rust:`n == 0`. Even if only the :rust:`pick` instantiation with integers is used, Rust still considers it invalid. C++ would let you do this. diff --git a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst index e4f10203c..283394a08 100644 --- a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst +++ b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst @@ -8,7 +8,7 @@ Generic Data Types You can use generics to abstract over the concrete field type: -.. code:: rust,editable +.. code:: rust #[derive(Debug)] struct Point { @@ -39,21 +39,21 @@ You can use generics to abstract over the concrete field type: Details --------- -- *Q:* Why ``T`` is specified twice in ``impl Point {}``? Isn't +- *Q:* Why :rust:`T` is specified twice in :rust:`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 { .. }``. + - It means these methods are defined for any :rust:`T`. + - It is possible to write :rust:`impl Point { .. }`. - - ``Point`` is still generic and you can use ``Point``, but + - :rust:`Point` is still generic and you can use :rust:`Point`, but methods in this block will only be available for - ``Point``. + :rust:`Point`. -- Try declaring a new variable ``let p = Point { x: 5, y: 10.0 };``. +- Try declaring a new variable :rust:`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``. + types, by using two type variables, e.g., :rust:`T` and :rust:`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 index fb70d1bd7..0d5b122dc 100644 --- a/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst +++ b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst @@ -9,7 +9,7 @@ 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 +.. code:: rust #[derive(Debug)] struct Foo(String); @@ -38,13 +38,13 @@ parameters get concrete types when it is used. Details --------- -- The ``From`` trait will be covered later in the course, but its +- The :rust:`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``. + parameters. Here, :rust:`Foo::from("hello")` would not compile because + there is no :rust:`From<&str>` implementation for :rust:`Foo`. - Generic traits take types as "input", while associated types are a kind of "output" type. A trait can have multiple implementations for diff --git a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst index 142d3c81b..47b6eda3d 100644 --- a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst +++ b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst @@ -9,9 +9,9 @@ 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``: +You can do this with :rust:`T: Trait`: -.. code:: rust,editable +.. code:: rust fn duplicate(a: T) -> (T, T) { (a.clone(), a.clone()) @@ -31,14 +31,14 @@ You can do this with ``T: Trait``: Details --------- -- Try making a ``NonCloneable`` and passing it to ``duplicate``. +- Try making a :rust:`NonCloneable` and passing it to :rust:`duplicate`. -- When multiple traits are necessary, use ``+`` to join them. +- When multiple traits are necessary, use :rust:`+` to join them. -- Show a ``where`` clause, students will encounter it when reading +- Show a :rust:`where` clause, students will encounter it when reading code. - .. code:: rust,ignore + .. code:: rust fn duplicate(a: T) -> (T, T) where @@ -51,11 +51,11 @@ Details - 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``. + of ":" can be arbitrary, like :rust:`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)``. + given the original :rust:`duplicate`, it is invalid to add a specialized + :rust:`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 index d93da92dd..db1761b6b 100644 --- a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst @@ -1,15 +1,15 @@ ================ -``impl Trait`` +:rust:`impl Trait` ================ ---------------- -``impl Trait`` +:rust:`impl Trait` ---------------- -Similar to trait bounds, an ``impl Trait`` syntax can be used in +Similar to trait bounds, an :rust:`impl Trait` syntax can be used in function arguments and return values: -.. code:: rust,editable +.. code:: rust // Syntactic sugar for: // fn add_42_millions>(x: T) -> i32 { @@ -36,10 +36,10 @@ function arguments and return values: 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. +:rust:`impl Trait` allows you to work with types which you cannot name. The +meaning of :rust:`impl Trait` is a bit different in the different positions. -- For a parameter, ``impl Trait`` is like an anonymous generic +- For a parameter, :rust:`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 @@ -48,14 +48,14 @@ meaning of ``impl Trait`` is a bit different in the different positions. API. Inference is hard in return position. A function returning - ``impl Foo`` picks the concrete type it returns, without writing it + :rust:`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 + :rust:`collect() -> B` can return any type satisfying :rust:`B`, and the caller may need to choose one, such as with - ``let x: Vec<_> = foo.collect()`` or with the turbofish, - ``foo.collect::>()``. + :rust:`let x: Vec<_> = foo.collect()` or with the turbofish, + :rust:`foo.collect::>()`. -What is the type of ``debuggable``? Try ``let debuggable: () = ..`` to +What is the type of :rust:`debuggable`? Try :rust:`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 index c64955363..76d0e199d 100644 --- a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst @@ -1,15 +1,15 @@ =============== -``dyn Trait`` +:rust:`dyn Trait` =============== --------------- -``dyn Trait`` +:rust:`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 +.. code:: rust struct Dog { name: String, @@ -62,33 +62,33 @@ supports using them for type-erased, dynamic dispatch via trait objects: Details --------- -- Generics, including ``impl Trait``, use monomorphization to create a +- Generics, including :rust:`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 +- When using :rust:`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. + (vtable). This means that there's a single version of :rust:`fn dynamic` + that is used regardless of what type of :rust:`Pet` is passed in. -- When using ``dyn Trait``, the trait object needs to be behind some +- When using :rust:`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 + pointer types like :rust:`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 +- At runtime, a :rust:`&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`` + implements :rust:`Pet`, and the other points to the vtable for the trait + implementation for that type. When calling the :rust:`talk` method on + :rust:`&dyn Pet` the compiler looks up the function pointer for :rust:`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. + the :rust:`Dog` or :rust:`Cat` into that function. The compiler doesn't need + to know the concrete type of the :rust:`Pet` in order to do this. -- A ``dyn Trait`` is considered to be "type-erased", because we no +- A :rust:`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 index c9f8d90f2..03ce2ee2d 100644 --- a/courses/comprehensive_rust_training/100_generics/07_exercise.rst +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -1,17 +1,17 @@ =========================== -Exercise: Generic ``min`` +Exercise: Generic :rust:`min` =========================== --------------------------- -Exercise: Generic ``min`` +Exercise: Generic :rust:`min` --------------------------- -In this short exercise, you will implement a generic ``min`` function +In this short exercise, you will implement a generic :rust:`min` function that determines the minimum of two values, using the `Ord `__ trait. -.. code:: rust,compile_fail +.. code:: rust use std::cmp::Ordering; diff --git a/courses/comprehensive_rust_training/110_std_types/01_std.rst b/courses/comprehensive_rust_training/110_std_types/01_std.rst index 2ca301da9..5901ea3ae 100644 --- a/courses/comprehensive_rust_training/110_std_types/01_std.rst +++ b/courses/comprehensive_rust_training/110_std_types/01_std.rst @@ -8,15 +8,15 @@ 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. +work together smoothly because they both use the same :rust:`String` type. -In fact, Rust contains several layers of the Standard Library: ``core``, -``alloc`` and ``std``. +In fact, Rust contains several layers of the Standard Library: :rust:`core`, +:rust:`alloc` and :rust:`std`. -- ``core`` includes the most basic types and functions that don't - depend on ``libc``, allocator or even the presence of an operating +- :rust:`core` includes the most basic types and functions that don't + depend on :rust:`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``. +- :rust:`alloc` includes types which require a global heap allocator, such + as :rust:`Vec`, :rust:`Box` and :rust:`Arc`. +- Embedded Rust applications often only use :rust:`core`, and sometimes + :rust:`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 index 2faa8d586..08b49a9a9 100644 --- a/courses/comprehensive_rust_training/110_std_types/02_docs.rst +++ b/courses/comprehensive_rust_training/110_std_types/02_docs.rst @@ -17,11 +17,11 @@ Rust comes with extensive documentation. For example: or `BinaryHeap `__. -Use ``rustup doc --std`` or https://std.rs to view the documentation. +Use :rust:`rustup doc --std` or https://std.rs to view the documentation. In fact, you can document your own code: -.. code:: rust,editable +.. code:: rust /// Determine whether the first argument is divisible by the second argument. /// @@ -40,9 +40,9 @@ tool. It is idiomatic to document all public items in an API using this pattern. To document an item from inside the item (such as inside a module), use -``//!`` or ``/*! .. */``, called "inner doc comments": +:rust:`//!` or :rust:`/*! .. */`, called "inner doc comments": -.. code:: rust,editable +.. code:: rust //! This module contains functionality relating to divisibility of integers. @@ -52,7 +52,7 @@ To document an item from inside the item (such as inside a module), use Details --------- -- Show students the generated docs for the ``rand`` crate at +- Show students the generated docs for the :rust:`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 index 704fb1561..bfdee318d 100644 --- a/courses/comprehensive_rust_training/110_std_types/03_option.rst +++ b/courses/comprehensive_rust_training/110_std_types/03_option.rst @@ -6,12 +6,12 @@ Option Option -------- -We have already seen some use of ``Option``. It stores either a value -of type ``T`` or nothing. For example, +We have already seen some use of :rust:`Option`. It stores either a value +of type :rust:`T` or nothing. For example, `String::find `__ -returns an ``Option``. +returns an :rust:`Option`. -.. code:: rust,editable,should_panic +.. code:: rust fn main() { let name = "Alexander the Great"; @@ -29,21 +29,21 @@ returns an ``Option``. 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. +- :rust:`Option` is widely used, not just in the standard library. +- :rust:`unwrap` will return the value in an :rust:`Option`, or panic. + :rust:`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 + - It's common to :rust:`unwrap`/:rust:`expect` all over the place when hacking something together, but production code typically handles - ``None`` in a nicer fashion. + :rust:`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 +- The "niche optimization" means that :rust:`Option` often has the same + size in memory as :rust:`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``. + :rust:`Option<&T>` automatically uses NULL to represent the :rust:`None` + variant, and thus can be stored in the same memory as :rust:`&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 index ab5840e70..89ab12959 100644 --- a/courses/comprehensive_rust_training/110_std_types/04_result.rst +++ b/courses/comprehensive_rust_training/110_std_types/04_result.rst @@ -6,12 +6,12 @@ Result Result -------- -``Result`` is similar to ``Option``, but indicates the success or +:rust:`Result` is similar to :rust:`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. +generic: :rust:`Result` where :rust:`T` is used in the :rust:`Ok` variant and +:rust:`E` appears in the :rust:`Err` variant. -.. code:: rust,editable +.. code:: rust use std::fs::File; use std::io::Read; @@ -39,15 +39,15 @@ generic: ``Result`` where ``T`` is used in the ``Ok`` variant and Details --------- -- As with ``Option``, the successful value sits inside of ``Result``, +- As with :rust:`Option`, the successful value sits inside of :rust:`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 + :rust:`unwrap()` or :rust:`expect()` can be called, and this is a signal of the developer intent too. -- ``Result`` documentation is a recommended read. Not during the +- :rust:`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 +- :rust:`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 index 1a4c1f364..65ca41ef3 100644 --- a/courses/comprehensive_rust_training/110_std_types/05_string.rst +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -9,7 +9,7 @@ String `String `__ is a growable UTF-8 encoded string: -.. code:: rust,editable +.. code:: rust fn main() { let mut s1 = String::new(); @@ -25,9 +25,9 @@ is a growable UTF-8 encoded string: println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count()); } -``String`` implements +:rust:`String` implements `Deref `__, -which means that you can call all ``str`` methods on a ``String``. +which means that you can call all :rust:`str` methods on a :rust:`String`. .. raw:: html @@ -35,41 +35,41 @@ which means that you can call all ``str`` methods on a ``String``. Details --------- -- ``String::new`` returns a new empty string, use - ``String::with_capacity`` when you know how much data you want to +- :rust:`String::new` returns a new empty string, use + :rust:`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 +- :rust:`String::len` returns the size of the :rust:`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 +- :rust:`String::chars` returns an iterator over the actual characters. + Note that a :rust:`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``. + :rust:`&str` or :rust:`String`. +- When a type implements :rust:`Deref`, the compiler will let + you transparently call methods from :rust:`T`. - - We haven't discussed the ``Deref`` trait yet, so at this point + - We haven't discussed the :rust:`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;``. + - :rust:`String` implements :rust:`Deref` which transparently + gives it access to :rust:`str`\ 's methods. + - Write and compare :rust:`let s3 = s1.deref();` and :rust:`let s3 = &*s1;`. -- ``String`` is implemented as a wrapper around a vector of bytes, many +- :rust:`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``: + :rust:`String`, but with some extra guarantees. +- Compare the different ways to index a :rust:`String`: - - To a character by using ``s3.chars().nth(i).unwrap()`` where ``i`` + - To a character by using :rust:`s3.chars().nth(i).unwrap()` where :rust:`i` is in-bound, out-of-bounds. - - To a substring by using ``s3[0..4]``, where that slice is on + - To a substring by using :rust:`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 + implement :rust:`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 index d72b8726c..f68fccbda 100644 --- a/courses/comprehensive_rust_training/110_std_types/06_vec.rst +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -1,15 +1,15 @@ ========= -``Vec`` +:rust:`Vec` ========= --------- -``Vec`` +:rust:`Vec` --------- `Vec `__ is the standard resizable heap-allocated buffer: -.. code:: rust,editable +.. code:: rust fn main() { let mut v1 = Vec::new(); @@ -33,9 +33,9 @@ standard resizable heap-allocated buffer: println!("{v3:?}"); } -``Vec`` implements +:rust:`Vec` implements `Deref `__, -which means that you can call slice methods on a ``Vec``. +which means that you can call slice methods on a :rust:`Vec`. .. raw:: html @@ -43,20 +43,20 @@ which means that you can call slice methods on a ``Vec``. Details --------- -- ``Vec`` is a type of collection, along with ``String`` and - ``HashMap``. The data it contains is stored on the heap. This means +- :rust:`Vec` is a type of collection, along with :rust:`String` and + :rust:`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()`` +- Notice how :rust:`Vec` is a generic type too, but you don't have to + specify :rust:`T` explicitly. As always with Rust type inference, the + :rust:`T` was established during the first :rust:`push` call. +- :rust:`vec![...]` is a canonical macro to use instead of :rust:`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. +- To index the vector you use :rust:`[` :rust:`]`, but they will panic if out + of bounds. Alternatively, using :rust:`get` will return an :rust:`Option`. + The :rust:`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 + a value of type :rust:`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 index d3e94dbae..8b79cd83c 100644 --- a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -1,14 +1,14 @@ ============= -``HashMap`` +:rust:`HashMap` ============= ------------- -``HashMap`` +:rust:`HashMap` ------------- Standard hash map with protection against HashDoS attacks: -.. code:: rust,editable +.. code:: rust use std::collections::HashMap; @@ -47,7 +47,7 @@ Standard hash map with protection against HashDoS attacks: Details --------- -- ``HashMap`` is not defined in the prelude and needs to be brought +- :rust:`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 @@ -55,7 +55,7 @@ Details line will insert the alternative value in the hashmap if the book is not found. - .. code:: rust,ignore + .. code:: rust let pc1 = page_counts .get("Harry Potter and the Sorcerer's Stone") @@ -64,7 +64,7 @@ Details .entry("The Hunger Games") .or_insert(374); -- Unlike ``vec!``, there is unfortunately no standard ``hashmap!`` +- Unlike :rust:`vec!`, there is unfortunately no standard :rust:`hashmap!` macro. - Although, since Rust 1.56, HashMap implements @@ -72,20 +72,20 @@ Details which allows us to easily initialize a hash map from a literal array: - .. code:: rust,ignore + .. code:: rust 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 +- Alternatively HashMap can be built from any :rust:`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 + :rust:`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. + the helpful link back to the :rust:`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 index 5b7d91a2d..0539903d2 100644 --- a/courses/comprehensive_rust_training/110_std_types/08_exercise.rst +++ b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst @@ -12,16 +12,16 @@ generic. It uses a 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. +The initial version of :rust:`Counter` is hard coded to only work for +:rust:`u32` values. Make the struct and its methods generic over the type of +value being tracked, that way :rust:`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. +:rust:`count` method. -.. code:: rust,compile_fail,editable +.. code:: rust use std::collections::HashMap; diff --git a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst index 0c1167a3a..9b22ab1db 100644 --- a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst +++ b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst @@ -10,14 +10,14 @@ These traits support comparisons between values. All traits can be derived for types containing fields that implement these traits. -------------------------- -``PartialEq`` and ``Eq`` +:rust:`PartialEq` and :rust:`Eq` -------------------------- -``PartialEq`` is a partial equivalence relation, with required method -``eq`` and provided method ``ne``. The ``==`` and ``!=`` operators will +:rust:`PartialEq` is a partial equivalence relation, with required method +:rust:`eq` and provided method :rust:`ne`. The :rust:`==` and :rust:`!=` operators will call these methods. -.. code:: rust,editable +.. code:: rust struct Key { id: u32, @@ -29,19 +29,19 @@ call these methods. } } -``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. +:rust:`Eq` is a full equivalence relation (reflexive, symmetric, and +transitive) and implies :rust:`PartialEq`. Functions that require full +equivalence will use :rust:`Eq` as a trait bound. ---------------------------- -``PartialOrd`` and ``Ord`` +:rust:`PartialOrd` and :rust:`Ord` ---------------------------- -``PartialOrd`` defines a partial ordering, with a ``partial_cmp`` -method. It is used to implement the ``<``, ``<=``, ``>=``, and ``>`` +:rust:`PartialOrd` defines a partial ordering, with a :rust:`partial_cmp` +method. It is used to implement the :rust:`<`, :rust:`<=`, :rust:`>=`, and :rust:`>` operators. -.. code:: rust,editable +.. code:: rust use std::cmp::Ordering; #[derive(Eq, PartialEq)] @@ -58,7 +58,7 @@ operators. } } -``Ord`` is a total ordering, with ``cmp`` returning ``Ordering``. +:rust:`Ord` is a total ordering, with :rust:`cmp` returning :rust:`Ordering`. .. raw:: html @@ -66,10 +66,10 @@ operators. Details --------- -``PartialEq`` can be implemented between different types, but ``Eq`` +:rust:`PartialEq` can be implemented between different types, but :rust:`Eq` cannot, because it is reflexive: -.. code:: rust,editable +.. code:: rust struct Key { id: u32, 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 2e9a7679e..256c64fcd 100644 --- a/courses/comprehensive_rust_training/120_std_traits/02_operators.rst +++ b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst @@ -9,7 +9,7 @@ Operators Operator overloading is implemented via traits in `std::ops `__: -.. code:: rust,editable +.. code:: rust #[derive(Debug, Copy, Clone)] struct Point { @@ -39,28 +39,28 @@ Details Discussion points: -- You could implement ``Add`` for ``&Point``. In which situations is +- You could implement :rust:`Add` for :rust:`&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 + - Answer: :rust:`Add:add` consumes :rust:`self`. If type :rust:`T` for which you + are overloading the operator is not :rust:`Copy`, you should consider + overloading the operator for :rust:`&T` as well. This avoids unnecessary cloning on the call site. -- Why is ``Output`` an associated type? Could it be made a type +- Why is :rust:`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 + caller, but associated types (like :rust:`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``. +- You could implement :rust:`Add` for two different types, e.g. + :rust:`impl Add<(i32, i32)> for Point` would add a tuple to a :rust:`Point`. -The ``Not`` trait (``!`` operator) is notable because it does not +The :rust:`Not` trait (:rust:`!` 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``. +equivalent to subtracting it from -1: :rust:`!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 index deb1ff902..7fef0b9c8 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 @@ -1,18 +1,18 @@ ======================= -``From`` and ``Into`` +:rust:`From` and :rust:`Into` ======================= ----------------------- -``From`` and ``Into`` +:rust:`From` and :rust:`Into` ----------------------- Types implement `From `__ and `Into `__ to -facilitate type conversions. Unlike ``as``, these traits correspond to +facilitate type conversions. Unlike :rust:`as`, these traits correspond to lossless, infallible conversions. -.. code:: rust,editable +.. code:: rust fn main() { let s = String::from("hello"); @@ -27,7 +27,7 @@ automatically implemented when `From `__ is implemented: -.. code:: rust,editable +.. code:: rust fn main() { let s: String = "hello".into(); @@ -43,12 +43,12 @@ implemented: Details --------- -- That's why it is common to only implement ``From``, as your type will - get ``Into`` implementation too. +- That's why it is common to only implement :rust:`From`, as your type will + get :rust:`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``. + be converted into a :rust:`String`", the rule is opposite, you should use + :rust:`Into`. Your function will accept types that implement :rust:`From` and + those that *only* implement :rust:`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 index 010951b57..5a5541468 100644 --- a/courses/comprehensive_rust_training/120_std_traits/04_casting.rst +++ b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst @@ -7,9 +7,9 @@ Casting --------- Rust has no *implicit* type conversions, but does support explicit casts -with ``as``. These generally follow C semantics where those are defined. +with :rust:`as`. These generally follow C semantics where those are defined. -.. code:: rust,editable +.. code:: rust fn main() { let value: i64 = 1000; @@ -18,20 +18,20 @@ with ``as``. These generally follow C semantics where those are defined. println!("as u8: {}", value as u8); } -The results of ``as`` are *always* defined in Rust and consistent across +The results of :rust:`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 - check the docs, and comment for clarity. -Casting with ``as`` is a relatively sharp tool that is easy to use +Casting with :rust:`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). +truncation (e.g. selecting the bottom 32 bits of a :rust:`u64` with +:rust:`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 +For infallible casts (e.g. :rust:`u32` to :rust:`u64`), prefer using :rust:`From` or +:rust:`Into` over :rust:`as` to confirm that the cast is in fact infallible. For +fallible casts, :rust:`TryFrom` and :rust:`TryInto` are available when you want to handle casts that fit differently from those that don't. .. raw:: html @@ -42,11 +42,11 @@ Details Consider taking a break after this slide. -``as`` is similar to a C++ static cast. Use of ``as`` in cases where +:rust:`as` is similar to a C++ static cast. Use of :rust:`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. +This is common in casting integers to :rust:`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 index 9c93c7b08..6cefe66d2 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 @@ -1,17 +1,17 @@ ======================== -``Read`` and ``Write`` +:rust:`Read` and :rust:`Write` ======================== ------------------------ -``Read`` and ``Write`` +:rust:`Read` and :rust:`Write` ------------------------ Using `Read `__ and `BufRead `__, -you can abstract over ``u8`` sources: +you can abstract over :rust:`u8` sources: -.. code:: rust,editable +.. code:: rust use std::io::{BufRead, BufReader, Read, Result}; @@ -31,9 +31,9 @@ you can abstract over ``u8`` sources: Similarly, `Write `__ lets -you abstract over ``u8`` sinks: +you abstract over :rust:`u8` sinks: -.. code:: rust,editable +.. code:: rust use std::io::{Result, Write}; 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 32cd57eee..a59eacda0 100644 --- a/courses/comprehensive_rust_training/120_std_traits/06_default.rst +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -1,15 +1,15 @@ ======================= -The ``Default`` Trait +The :rust:`Default` Trait ======================= ----------------------- -The ``Default`` Trait +The :rust:`Default` Trait ----------------------- `Default `__ trait produces a default value for a type. -.. code:: rust,editable +.. code:: rust #[derive(Debug, Default)] struct Derived { @@ -46,18 +46,18 @@ Details --------- - It can be implemented directly or it can be derived via - ``#[derive(Default)]``. + :rust:`#[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. + - This means all types in the struct must implement :rust:`Default` too. -- Standard Rust types often implement ``Default`` with reasonable - values (e.g. ``0``, ``""``, etc). +- Standard Rust types often implement :rust:`Default` with reasonable + values (e.g. :rust:`0`, :rust:`""`, 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 + :rust:`Default` and provides convenience methods that use it. +- The :rust:`..` 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 1bb5bfd76..441c33e8d 100644 --- a/courses/comprehensive_rust_training/120_std_traits/07_closures.rst +++ b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst @@ -13,7 +13,7 @@ However, they implement special `FnOnce `__ traits: -.. code:: rust,editable +.. code:: rust fn apply_and_log(func: impl FnOnce(i32) -> i32, func_name: &str, input: i32) { println!("Calling {func_name}({input}): {}", func(input)) @@ -43,41 +43,41 @@ traits: Details --------- -An ``Fn`` (e.g. ``add_3``) neither consumes nor mutates captured values. +An :rust:`Fn` (e.g. :rust:`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 +An :rust:`FnMut` (e.g. :rust:`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 +If you have an :rust:`FnOnce` (e.g. :rust:`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. +:rust:`FnMut` is a subtype of :rust:`FnOnce`. :rust:`Fn` is a subtype of :rust:`FnMut` +and :rust:`FnOnce`. I.e. you can use an :rust:`FnMut` wherever an :rust:`FnOnce` is +called for, and you can use an :rust:`Fn` wherever an :rust:`FnMut` or +:rust:`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. +:rust:`FnOnce` if you can (i.e. you call it once), or :rust:`FnMut` else, and +last :rust:`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``. +:rust:`Fn` (which can be passed to a consumer of any of the 3 closure +traits), then :rust:`FnMut`, and lastly :rust:`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``. +The compiler also infers :rust:`Copy` (e.g. for :rust:`add_3`) and :rust:`Clone` +(e.g. :rust:`multiply_sum`), depending on what the closure captures. +Function pointers (references to :rust:`fn` items) implement :rust:`Copy` and +:rust:`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 +possible, then exclusive reference, then by move). The :rust:`move` keyword forces capture by value. -.. code:: rust,editable +.. code:: rust fn make_greeter(prefix: String) -> impl Fn(&str) { return move |name| println!("{} {}", prefix, name); 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 05cd87433..f1a432cd0 100644 --- a/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst +++ b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst @@ -11,7 +11,7 @@ In this example, you will implement the classic playground, and implement the missing bits. Only rotate ASCII alphabetic characters, to ensure the result is still valid UTF-8. -.. code:: rust,compile_fail +.. code:: rust {{#include exercise.rs:head }} @@ -19,5 +19,5 @@ characters, to ensure the result is still valid UTF-8. {{#include exercise.rs:main }} -What happens if you chain two ``RotDecoder`` instances together, each +What happens if you chain two :rust:`RotDecoder` instances together, each rotating by 13 characters? 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 16e775ea7..ad97d8b5e 100644 --- a/courses/comprehensive_rust_training/130_memory_management/01_review.rst +++ b/courses/comprehensive_rust_training/130_memory_management/01_review.rst @@ -25,10 +25,10 @@ Programs allocate memory in two ways: Example --------- -Creating a ``String`` puts fixed-sized metadata on the stack and +Creating a :rust:`String` puts fixed-sized metadata on the stack and dynamically sized data, the actual string, on the heap: -.. code:: rust,editable +.. code:: rust fn main() { let s1 = String::from("Hello"); @@ -54,7 +54,7 @@ dynamically sized data, the actual string, on the heap: Details --------- -- Mention that a ``String`` is backed by a ``Vec``, so it has a +- Mention that a :rust:`String` is backed by a :rust:`Vec`, so it has a capacity and length and can grow if mutable via reallocation on the heap. @@ -68,10 +68,10 @@ Details More to Explore ----------------- -We can inspect the memory layout with ``unsafe`` Rust. However, you +We can inspect the memory layout with :rust:`unsafe` Rust. However, you should point out that this is rightfully unsafe! -.. code:: rust,editable +.. code:: rust fn main() { let mut s1 = String::from("Hello"); 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 9384d7550..67a2db382 100644 --- a/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst +++ b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst @@ -39,12 +39,12 @@ 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 +- C must manage heap manually with :rust:`malloc` and :rust:`free`. Common + errors include forgetting to call :rust:`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``) +- C++ has tools like smart pointers (:rust:`unique_ptr`, :rust:`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. diff --git a/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst index a196d4dae..7cbe61e8d 100644 --- a/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst +++ b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst @@ -13,7 +13,7 @@ error to use a variable outside its scope: -.. code:: rust,editable,compile_fail +.. code:: rust struct Point(i32, i32); 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 8cdac3d43..938abd984 100644 --- a/courses/comprehensive_rust_training/130_memory_management/04_move.rst +++ b/courses/comprehensive_rust_training/130_memory_management/04_move.rst @@ -8,7 +8,7 @@ Move Semantics An assignment will transfer *ownership* between variables: -.. code:: rust,editable +.. code:: rust fn main() { let s1: String = String::from("Hello!"); @@ -17,12 +17,12 @@ An assignment will transfer *ownership* between variables: // println!("s1: {s1}"); } -- The assignment of ``s1`` to ``s2`` transfers ownership. -- When ``s1`` goes out of scope, nothing happens: it does not own +- The assignment of :rust:`s1` to :rust:`s2` transfers ownership. +- When :rust:`s1` goes out of scope, nothing happens: it does not own anything. -- When ``s2`` goes out of scope, the string data is freed. +- When :rust:`s2` goes out of scope, the string data is freed. -Before move to ``s2``: +Before move to :rust:`s2`: .. code:: bob @@ -39,7 +39,7 @@ Before move to ``s2``: : : `- - - - - - - - - - - - - -' -After move to ``s2``: +After move to :rust:`s2`: .. code:: bob @@ -65,7 +65,7 @@ After move to ``s2``: When you pass a value to a function, the value is assigned to the function parameter. This transfers ownership: -.. code:: rust,editable +.. code:: rust fn say_hello(name: String) { println!("Hello {name}") @@ -84,29 +84,29 @@ Details --------- - Mention that this is the opposite of the defaults in C++, which - copies by value unless you use ``std::move`` (and the move + copies by value unless you use :rust:`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 +- Simple values (such as integers) can be marked :rust:`Copy` (see later slides). -- In Rust, clones are explicit (by using ``clone``). +- In Rust, clones are explicit (by using :rust:`clone`). -In the ``say_hello`` example: +In the :rust:`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()``). +- With the first call to :rust:`say_hello`, :rust:`main` gives up ownership of + :rust:`name`. Afterwards, :rust:`name` cannot be used anymore within + :rust:`main`. +- The heap memory allocated for :rust:`name` will be freed at the end of + the :rust:`say_hello` function. +- :rust:`main` can retain ownership if it passes :rust:`name` as a reference + (:rust:`&name`) and if :rust:`say_hello` accepts a reference as a parameter. +- Alternatively, :rust:`main` can pass a clone of :rust:`name` in the first + call (:rust:`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. @@ -130,9 +130,9 @@ Modern C++ solves this differently: 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 +- The heap data from :rust:`s1` is duplicated and :rust:`s2` gets its own independent copy. -- When ``s1`` and ``s2`` go out of scope, they each free their own +- When :rust:`s1` and :rust:`s2` go out of scope, they each free their own memory. Before copy-assignment: @@ -176,19 +176,19 @@ After copy-assignment: Key points: -- C++ has made a slightly different choice than Rust. Because ``=`` +- C++ has made a slightly different choice than Rust. Because :rust:`=` 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 + example had been :rust:`s2 = std::move(s1)`, no heap allocation would + take place. After the move, :rust:`s1` would be in a valid but unspecified state. Unlike Rust, the programmer is allowed to keep - using ``s1``. + using :rust:`s1`. -- Unlike Rust, ``=`` in C++ can run arbitrary code as determined by the +- Unlike Rust, :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 index a9a029b88..c70daa943 100644 --- a/courses/comprehensive_rust_training/130_memory_management/05_clone.rst +++ b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst @@ -6,10 +6,10 @@ Clone Clone ------- -Sometimes you *want* to make a copy of a value. The ``Clone`` trait +Sometimes you *want* to make a copy of a value. The :rust:`Clone` trait accomplishes this. -.. code:: rust,editable +.. code:: rust fn say_hello(name: String) { println!("Hello {name}") @@ -27,18 +27,18 @@ accomplishes this. 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``. +- The idea of :rust:`Clone` is to make it easy to spot where heap + allocations are occurring. Look for :rust:`.clone()` and a few others + like :rust:`vec!` or :rust:`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 +- :rust:`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 +- The behavior for :rust:`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 index 0e2c21589..72bef3e0e 100644 --- a/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst +++ b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst @@ -13,7 +13,7 @@ default: -.. code:: rust,editable +.. code:: rust fn main() { let x = 42; @@ -22,7 +22,7 @@ default: println!("y: {y}"); } -These types implement the ``Copy`` trait. +These types implement the :rust:`Copy` trait. You can opt-in your own types to use copy semantics: @@ -30,7 +30,7 @@ You can opt-in your own types to use copy semantics: -.. code:: rust,editable +.. code:: rust #[derive(Copy, Clone, Debug)] struct Point(i32, i32); @@ -42,8 +42,8 @@ You can opt-in your own types to use copy semantics: 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. +- After the assignment, both :rust:`p1` and :rust:`p2` own their own data. +- We can also use :rust:`p1.clone()` to explicitly copy the data. .. raw:: html @@ -58,16 +58,16 @@ Copying and cloning are not the same thing: - 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. + behavior by implementing the :rust:`Clone` trait. +- Copying does not work on types that implement the :rust:`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. +- Add a :rust:`String` field to :rust:`struct Point`. It will not compile + because :rust:`String` is not a :rust:`Copy` type. +- Remove :rust:`Copy` from the :rust:`derive` attribute. The compiler error is + now in the :rust:`println!` for :rust:`p1`. +- Show that it works if you clone :rust:`p1` instead. ================= More to Explore @@ -77,7 +77,7 @@ More to Explore More to Explore ----------------- -- Shared references are ``Copy``/``Clone``, mutable references are not. +- Shared references are :rust:`Copy`/:rust:`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. 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 e5d218a6e..2ce173cdb 100644 --- a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst +++ b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst @@ -1,16 +1,16 @@ ==================== -The ``Drop`` Trait +The :rust:`Drop` Trait ==================== -------------------- -The ``Drop`` Trait +The :rust:`Drop` Trait -------------------- Values which implement `Drop `__ can specify code to run when they go out of scope: -.. code:: rust,editable +.. code:: rust struct Droppable { name: &'static str, @@ -43,31 +43,31 @@ specify code to run when they go out of scope: Details --------- -- Note that ``std::mem::drop`` is not the same as - ``std::ops::Drop::drop``. +- Note that :rust:`std::mem::drop` is not the same as + :rust:`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. +- When a value is dropped, if it implements :rust:`std::ops::Drop` then its + :rust:`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. + :rust:`Drop`. +- :rust:`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``: + - This can be useful for objects that do some work on :rust:`drop`: releasing locks, closing files, etc. Discussion points: -- Why doesn't ``Drop::drop`` take ``self``? +- Why doesn't :rust:`Drop::drop` take :rust:`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 + - Short-answer: If it did, :rust:`std::mem::drop` would be called at the + end of the block, resulting in another call to :rust:`Drop::drop`, and a stack overflow! -- Try replacing ``drop(a)`` with ``a.drop()``. +- Try replacing :rust:`drop(a)` with :rust:`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 index 869bfbf6e..86d9a9ca7 100644 --- a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst +++ b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst @@ -12,7 +12,7 @@ value piece-by-piece, using convenience functions. Fill in the missing pieces. -.. code:: rust,should_panic,editable +.. code:: rust {{#include exercise.rs:Package}} {{#include exercise.rs:as_dependency}} 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 0da5865ff..9376026e5 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -1,15 +1,15 @@ ============ -``Box`` +:rust:`Box` ============ ------------ -``Box`` +:rust:`Box` ------------ `Box `__ is an owned pointer to data on the heap: -.. code:: rust,editable +.. code:: rust fn main() { let five = Box::new(5); @@ -29,14 +29,14 @@ owned pointer to data on the heap: : : : : `- - - - - - -' `- - - - - - -' -``Box`` implements ``Deref``, which means that you can +:rust:`Box` implements :rust:`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 +inline without a pointer indirection. :rust:`Box` accomplishes that indirection: -.. code:: rust,editable +.. code:: rust #[derive(Debug)] enum List { @@ -71,33 +71,33 @@ indirection: Details --------- -- ``Box`` is like ``std::unique_ptr`` in C++, except that it's +- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's guaranteed to be not null. -- A ``Box`` can be useful when you: +- A :rust:`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. + on the heap in a :rust:`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 +- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly + into the :rust:`List`, the compiler would not be able to compute a fixed + size for the struct in memory (the :rust:`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 +- :rust:`Box` solves this problem as it has the same size as a regular + pointer and just points at the next element of the :rust:`List` in the heap. -- Remove the ``Box`` in the List definition and show the compiler +- Remove the :rust:`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 + for data recursion, we have to use indirection, a :rust:`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 +- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be + empty/null. This makes :rust:`Box` one of the types that allow the compiler to optimize storage of some enums (the "niche optimization"). 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 509db1b42..16418bfba 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -1,16 +1,16 @@ ======== -``Rc`` +:rust:`Rc` ======== -------- -``Rc`` +:rust:`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 +.. code:: rust use std::rc::Rc; @@ -22,7 +22,7 @@ same data from multiple places: println!("b: {b}"); } -- See ```Arc`` <../concurrency/shared-state/arc.md>`__ and +- See :rust:`Arc`` <../concurrency/shared-state/arc.md>`__ and `Mutex `__ if you are in a multi-threaded context. - You can *downgrade* a shared pointer into a @@ -35,18 +35,18 @@ same data from multiple places: Details --------- -- ``Rc``\ 's count ensures that its contained value is valid for as +- :rust:`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 +- :rust:`Rc` in Rust is like :rust:`std::shared_ptr` in C++. +- :rust:`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 +- :rust:`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 +- Use :rust:`Rc::strong_count` to check the reference count. +- :rust:`Rc::downgrade` gives you a *weakly reference-counted* object to create cycles that will be dropped properly (likely in combination - with ``RefCell``). + with :rust:`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 index 1a64da882..bf645384e 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 @@ -7,10 +7,10 @@ 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``. +:rust:`&dyn Pet`. However, we can also use trait objects with smart pointers +like :rust:`Box` to create an owned trait object: :rust:`Box`. -.. code:: rust,editable +.. code:: rust struct Dog { name: String, @@ -46,7 +46,7 @@ like ``Box`` to create an owned trait object: ``Box``. } } -Memory layout after allocating ``pets``: +Memory layout after allocating :rust:`pets`: .. code:: bob @@ -87,25 +87,25 @@ Details --------- - Types that implement a given trait may be of different sizes. This - makes it impossible to have things like ``Vec`` in the + makes it impossible to have things like :rust:`Vec` in the example above. -- ``dyn Pet`` is a way to tell the compiler about a dynamically sized - type that implements ``Pet``. +- :rust:`dyn Pet` is a way to tell the compiler about a dynamically sized + type that implements :rust:`Pet`. -- In the example, ``pets`` is allocated on the stack and the vector +- In the example, :rust:`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. + (vtable) for the :rust:`Pet` implementation of that particular object. + - The data for the :rust:`Dog` named Fido is the :rust:`name` and :rust:`age` + fields. The :rust:`Cat` has a :rust:`lives` field. - Compare these outputs in the above example: - .. code:: rust,ignore + .. code:: rust println!("{} {}", std::mem::size_of::(), std::mem::size_of::()); println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>()); diff --git a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst index e9bcba20f..1b3934b7b 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst @@ -17,7 +17,7 @@ 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 +.. code:: rust {{#include exercise.rs:types}} diff --git a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst index b66dcef5d..dc837429f 100644 --- a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst +++ b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst @@ -13,7 +13,7 @@ function, you can let a function *borrow* the value: -.. code:: rust,editable +.. code:: rust #[derive(Debug)] struct Point(i32, i32); @@ -29,7 +29,7 @@ function, you can let a function *borrow* the value: println!("{p1:?} + {p2:?} = {p3:?}"); } -- The ``add`` function *borrows* two points and returns a new point. +- The :rust:`add` function *borrows* two points and returns a new point. - The caller retains ownership of the inputs. .. raw:: html @@ -51,7 +51,7 @@ More to Explore Notes on stack returns and inlining: -- Demonstrate that the return from ``add`` is cheap because the +- Demonstrate that the return from :rust:`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 @@ -64,7 +64,7 @@ Notes on stack returns and inlining: - .. code:: rust,editable + .. code:: rust #[derive(Debug)] struct Point(i32, i32); @@ -84,7 +84,7 @@ Notes on stack returns and inlining: } - The Rust compiler can do automatic inlining, that can be disabled on - a function level with ``#[inline(never)]``. + a function level with :rust:`#[inline(never)]`. - Once disabled, the printed address will change on all optimization levels. Looking at Godbolt or Playground, one can see that in this diff --git a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst index d17e15bf7..2b0dd20e3 100644 --- a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst +++ b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst @@ -14,7 +14,7 @@ it borrows: -.. code:: rust,editable,compile_fail +.. code:: rust fn main() { let x_ref = { @@ -34,7 +34,7 @@ There's also a second main rule that the borrow checker enforces: The -.. code:: rust,editable,compile_fail +.. code:: rust fn main() { let mut a: i32 = 10; @@ -61,12 +61,12 @@ Details - Note that the requirement is that conflicting references not *exist* at the same point. It does not matter where the reference is dereferenced. -- The above code does not compile because ``a`` is borrowed as mutable - (through ``c``) and as immutable (through ``b``) at the same time. -- Move the ``println!`` statement for ``b`` before the scope that - introduces ``c`` to make the code compile. -- After that change, the compiler realizes that ``b`` is only ever used - before the new mutable borrow of ``a`` through ``c``. This is a +- The above code does not compile because :rust:`a` is borrowed as mutable + (through :rust:`c`) and as immutable (through :rust:`b`) at the same time. +- Move the :rust:`println!` statement for :rust:`b` before the scope that + introduces :rust:`c` to make the code compile. +- After that change, the compiler realizes that :rust:`b` is only ever used + before the new mutable borrow of :rust:`a` through :rust:`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 diff --git a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst index 406bf994d..027e33027 100644 --- a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst +++ b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst @@ -10,7 +10,7 @@ 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 +.. code:: rust fn main() { let mut vec = vec![1, 2, 3, 4, 5]; @@ -21,7 +21,7 @@ references to its elements: Similarly, consider the case of iterator invalidation: -.. code:: rust,editable,compile_fail +.. code:: rust fn main() { let mut vec = vec![1, 2, 3, 4, 5]; diff --git a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst index 3bb19ba62..9a5e0ba5d 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst @@ -10,10 +10,10 @@ 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 can also be explicit: :rust:`&'a Point`, :rust:`&'document str`. +Lifetimes start with :rust:`'` and :rust:`'a` is a typical default name. Read +:rust:`&'a Point` as "a borrowed :rust:`Point` which is valid for at least the +lifetime :rust:`a`". Lifetimes are always inferred by the compiler: you cannot assign a lifetime yourself. Explicit lifetime annotations create constraints @@ -28,7 +28,7 @@ returning values from functions. -.. code:: rust,editable,compile_fail +.. code:: rust #[derive(Debug)] struct Point(i32, i32); @@ -55,19 +55,19 @@ 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 +:rust:`p3`. Looking inside the function body shows that it can only safely +assume that :rust:`p3`\ 's lifetime is the shorter of :rust:`p1` and :rust:`p2`. But just like types, Rust requires explicit annotations of lifetimes on function arguments and return values. -Add ``'a`` appropriately to ``left_most``: +Add :rust:`'a` appropriately to :rust:`left_most`: -.. code:: rust,ignore +.. code:: rust 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``. +This says, "given p1 and p2 which both outlive :rust:`'a`, the return value +lives for at least :rust:`'a`. In common cases, lifetimes can be elided, as described on the next slide. 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 2f729b005..0ba0010a6 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst @@ -15,9 +15,9 @@ is not inference - it is just a syntactic shorthand. - 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. + :rust:`self`, that lifetime is given to all un-annotated return values. -.. code:: rust,editable +.. code:: rust #[derive(Debug)] struct Point(i32, i32); @@ -52,14 +52,14 @@ is not inference - it is just a syntactic shorthand. Details --------- -In this example, ``cab_distance`` is trivially elided. +In this example, :rust:`cab_distance` is trivially elided. -The ``nearest`` function provides another example of a function with +The :rust:`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 +.. code:: rust fn nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> { diff --git a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst index 6b8a6bfd4..8deaa6ed2 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst @@ -9,7 +9,7 @@ Lifetimes in Data Structures If a data type stores borrowed data, it must be annotated with a lifetime: -.. code:: rust,editable +.. code:: rust #[derive(Debug)] struct Highlight<'doc>(&'doc str); @@ -33,11 +33,11 @@ lifetime: 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. +- In the above example, the annotation on :rust:`Highlight` enforces that + the data underlying the contained :rust:`&str` lives at least as long as + any instance of :rust:`Highlight` that uses that data. +- If :rust:`text` is consumed before the end of the lifetime of :rust:`fox` (or + :rust:`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. diff --git a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst index 1c82dd6cc..0880cad3f 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst @@ -14,8 +14,8 @@ 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. +:rust:`proto` file. In this exercise, we'll encode that information into +:rust:`match` statements in functions that get called for each field. We'll use the following proto: @@ -38,17 +38,17 @@ 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) +field number (e.g., :rust:`2` for the :rust:`id` field of a :rust:`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. +:rust:`unpack_tag` below. -------- Varint -------- Integers, including the tag, are represented with a variable-length -encoding called VARINT. Luckily, ``parse_varint`` is defined for you +encoding called VARINT. Luckily, :rust:`parse_varint` is defined for you below. ------------ @@ -58,31 +58,31 @@ 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 :rust:`Varint` wire type contains a single varint, and is used to encode +proto values of type :rust:`int32` such as :rust:`Person.id`. -The ``Len`` wire type contains a length expressed as a varint, followed +The :rust:`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``, +values of type :rust:`string` such as :rust:`Person.name`. It is also used to +encode proto values containing sub-messages such as :rust:`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 +The given code also defines callbacks to handle :rust:`Person` and +:rust:`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``. +What remains for you is to implement the :rust:`parse_field` function and +the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. .. raw:: html -.. code:: rust,editable,compile_fail +.. code:: rust {{#include exercise.rs:preliminaries }} @@ -110,9 +110,9 @@ 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 + fail, e.g. if you try to parse an :rust:`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 + with the :rust:`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. diff --git a/courses/comprehensive_rust_training/170_iterators/01_motivation.rst b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst index 2d4938485..55ea95cb1 100644 --- a/courses/comprehensive_rust_training/170_iterators/01_motivation.rst +++ b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst @@ -17,7 +17,7 @@ define: In a C-style for loop you declare these things directly: -.. code:: c,editable +.. code:: c for (int i = 0; i < array_len; i += 1) { int elem = array[i]; @@ -33,15 +33,15 @@ Details --------- - This slide provides context for what Rust iterators do under the - hood. We use the (hopefully) familiar construct of a C-style ``for`` + hood. We use the (hopefully) familiar construct of a C-style :rust:`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``: +- Rust doesn't have a C-style :rust:`for` loop, but we can express the same + thing with :rust:`while`: - .. code:: rust,editable + .. code:: rust let array = [2, 4, 6, 8]; let mut i = 0; @@ -54,12 +54,12 @@ Details More to Explore ----------------- -There's another way to express array iteration using ``for`` in C and +There's another way to express array iteration using :rust:`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 +.. code:: c for (int *ptr = array; ptr < array + len; ptr += 1) { int elem = *ptr; diff --git a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst index ef9dc7db5..320465675 100644 --- a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst @@ -1,9 +1,9 @@ ==================== -``Iterator`` Trait +:rust:`Iterator` Trait ==================== -------------------- -``Iterator`` Trait +:rust:`Iterator` Trait -------------------- The @@ -12,7 +12,7 @@ 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 +.. code:: rust struct SliceIter<'s> { slice: &'s [i32], @@ -47,32 +47,32 @@ elements of a slice it might look something like this: Details --------- -- The ``SliceIter`` example implements the same logic as the C-style - ``for`` loop demonstrated on the last slide. +- The :rust:`SliceIter` example implements the same logic as the C-style + :rust:`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. + work. No work happens until the :rust:`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. + range like :rust:`0..` will keep going until integer overflow occurs. ----------------- More to Explore ----------------- -- The "real" version of ``SliceIter`` is the +- The "real" version of :rust:`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 +- The :rust:`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]``). +- You can also demonstrate adding a generic parameter to :rust:`SliceIter` + to allow it to work with any kind of slice (not just :rust:`&[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 index 1246fd39d..36055d910 100644 --- a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst +++ b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst @@ -1,16 +1,16 @@ ============================= -``Iterator`` Helper Methods +:rust:`Iterator` Helper Methods ============================= ----------------------------- -``Iterator`` Helper Methods +:rust:`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 +In addition to the :rust:`next` method that defines how an iterator behaves, +the :rust:`Iterator` trait provides 70+ helper methods that can be used to build customized iterators. -.. code:: rust,editable +.. code:: rust let result: i32 = (1..=10) // Create a range from 1 to 10 .filter(|&x| x % 2 == 0) // Keep only even numbers @@ -25,8 +25,8 @@ build customized iterators. Details --------- -- The ``Iterator`` trait implements many common functional programming - operations over collections (e.g. ``map``, ``filter``, ``reduce``, +- The :rust:`Iterator` trait implements many common functional programming + operations over collections (e.g. :rust:`map`, :rust:`filter`, :rust:`reduce`, etc). This is the trait where you can find all the documentation about them. @@ -34,7 +34,7 @@ Details new iterator with different behavior. These are know as "iterator adapter methods". -- Some methods, like ``sum`` and ``count``, consume the iterator and +- Some methods, like :rust:`sum` and :rust:`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 diff --git a/courses/comprehensive_rust_training/170_iterators/04_collect.rst b/courses/comprehensive_rust_training/170_iterators/04_collect.rst index 04c73ddd6..2a5011a27 100644 --- a/courses/comprehensive_rust_training/170_iterators/04_collect.rst +++ b/courses/comprehensive_rust_training/170_iterators/04_collect.rst @@ -1,9 +1,9 @@ ============= -``collect`` +:rust:`collect` ============= ------------- -``collect`` +:rust:`collect` ------------- The @@ -11,7 +11,7 @@ The method lets you build a collection from an `Iterator `__. -.. code:: rust,editable +.. code:: rust fn main() { let primes = vec![2, 3, 5, 7]; @@ -25,20 +25,20 @@ method lets you build a collection from an 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``. +- Any iterator can be collected in to a :rust:`Vec`, :rust:`VecDeque`, or + :rust:`HashSet`. Iterators that produce key-value pairs (i.e. a + two-element tuple) can also be collected into :rust:`HashMap` and + :rust:`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 +Show the students the definition for :rust:`collect` in the standard library +docs. There are two ways to specify the generic type :rust:`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 the "turbofish": :rust:`some_iterator.collect::()`, + as shown. The :rust:`_` shorthand used here lets Rust infer the type of + the :rust:`Vec` elements. - With type inference: - ``let prime_squares: Vec<_> = some_iterator.collect()``. Rewrite the + :rust:`let prime_squares: Vec<_> = some_iterator.collect()`. Rewrite the example to use this form. ----------------- @@ -49,11 +49,11 @@ More to Explore `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 +- In addition to the basic implementations of :rust:`FromIterator` for + :rust:`Vec`, :rust:`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 + :rust:`Iterator>` into a :rust:`Result, E>`. +- The reason type annotations are often needed with :rust:`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. diff --git a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst index 8816d53bd..ef2154de3 100644 --- a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst @@ -1,18 +1,18 @@ ================== -``IntoIterator`` +:rust:`IntoIterator` ================== ------------------ -``IntoIterator`` +:rust:`IntoIterator` ------------------ -The ``Iterator`` trait tells you how to *iterate* once you have created +The :rust:`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. +by the :rust:`for` loop. -.. code:: rust,editable +.. code:: rust struct Grid { x_coords: Vec, @@ -63,35 +63,35 @@ by the ``for`` loop. 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 +- :rust:`IntoIterator` is the trait that makes for loops work. It is + implemented by collection types such as :rust:`Vec` and references to + them such as :rust:`&Vec` and :rust:`&[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. + :rust:`for i in some_vec { .. }` but :rust:`some_vec.next()` doesn't exist. -Click through to the docs for ``IntoIterator``. Every implementation of -``IntoIterator`` must declare two types: +Click through to the docs for :rust:`IntoIterator`. Every implementation of +:rust:`IntoIterator` must declare two types: -- ``Item``: the type to iterate over, such as ``i8``, -- ``IntoIter``: the ``Iterator`` type returned by the ``into_iter`` +- :rust:`Item`: the type to iterate over, such as :rust:`i8`, +- :rust:`IntoIter`: the :rust:`Iterator` type returned by the :rust:`into_iter` method. -Note that ``IntoIter`` and ``Item`` are linked: the iterator must have -the same ``Item`` type, which means that it returns ``Option`` +Note that :rust:`IntoIter` and :rust:`Item` are linked: the iterator must have +the same :rust:`Item` type, which means that it returns :rust:`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``. +Try iterating over the grid twice in :rust:`main`. Why does this fail? Note +that :rust:`IntoIterator::into_iter` takes ownership of :rust:`self`. -Fix this issue by implementing ``IntoIterator`` for ``&Grid`` and -storing a reference to the ``Grid`` in ``GridIter``. +Fix this issue by implementing :rust:`IntoIterator` for :rust:`&Grid` and +storing a reference to the :rust:`Grid` in :rust:`GridIter`. The same problem can occur for standard library types: -``for e in some_vector`` will take ownership of ``some_vector`` and +:rust:`for e in some_vector` will take ownership of :rust:`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``. +:rust:`for e in &some_vector` instead, to iterate over references to +elements of :rust:`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 index 4060d4b2d..fc7c432bc 100644 --- a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst +++ b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst @@ -12,7 +12,7 @@ methods in the 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 +tests pass. Use an iterator expression and :rust:`collect` the result to construct the return value. .. code:: rust diff --git a/courses/comprehensive_rust_training/180_modules/01_modules.rst b/courses/comprehensive_rust_training/180_modules/01_modules.rst index d56709e5b..8e0acda4e 100644 --- a/courses/comprehensive_rust_training/180_modules/01_modules.rst +++ b/courses/comprehensive_rust_training/180_modules/01_modules.rst @@ -6,11 +6,11 @@ Modules Modules --------- -We have seen how ``impl`` blocks let us namespace functions to a type. +We have seen how :rust:`impl` blocks let us namespace functions to a type. -Similarly, ``mod`` lets us namespace types and functions: +Similarly, :rust:`mod` lets us namespace types and functions: -.. code:: rust,editable +.. code:: rust mod foo { pub fn do_something() { @@ -35,7 +35,7 @@ Similarly, ``mod`` lets us namespace types and functions: Details --------- -- Packages provide functionality and include a ``Cargo.toml`` file that +- Packages provide functionality and include a :rust:`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. diff --git a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst index 5349471b9..734559852 100644 --- a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst +++ b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst @@ -9,24 +9,24 @@ Filesystem Hierarchy Omitting the module content will tell Rust to look for it in another file: -.. code:: rust,editable,compile_fail +.. code:: rust 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``. +This tells Rust that the :rust:`garden` module content is found at +:rust:`src/garden.rs`. Similarly, a :rust:`garden::vegetables` module can be +found at :rust:`src/garden/vegetables.rs`. -The ``crate`` root is in: +The :rust:`crate` root is in: -- ``src/lib.rs`` (for a library crate) -- ``src/main.rs`` (for a binary crate) +- :rust:`src/lib.rs` (for a library crate) +- :rust:`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 - in this case, a module. -.. code:: rust,editable,compile_fail +.. code:: rust //! This module implements the garden, including a highly performant germination //! implementation. @@ -51,12 +51,12 @@ module. 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 +- Before Rust 2018, modules needed to be located at :rust:`module/mod.rs` + instead of :rust:`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 +- The main reason to introduce :rust:`filename.rs` as alternative to + :rust:`filename/mod.rs` was because many files named :rust:`mod.rs` can be hard to distinguish in IDEs. - Deeper nesting can use folders, even if the main module is a file: @@ -72,13 +72,13 @@ Details - The place rust will look for modules can be changed with a compiler directive: - .. code:: rust,ignore + .. code:: rust #[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 + module in a file named :rust:`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 index c5e40b8cb..3a2ec9606 100644 --- a/courses/comprehensive_rust_training/180_modules/03_visibility.rst +++ b/courses/comprehensive_rust_training/180_modules/03_visibility.rst @@ -10,10 +10,10 @@ 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``. +- In other words, if an item is visible in module :rust:`foo`, it's visible + in all the descendants of :rust:`foo`. -.. code:: rust,editable +.. code:: rust mod outer { fn private() { @@ -46,14 +46,14 @@ Modules are a privacy boundary: Details --------- -- Use the ``pub`` keyword to make modules public. +- Use the :rust:`pub` keyword to make modules public. -Additionally, there are advanced ``pub(...)`` specifiers to restrict the +Additionally, there are advanced :rust:`pub(...)` specifiers to restrict the scope of public visibility. - See the `Rust Reference `__. -- Configuring ``pub(crate)`` visibility is a common pattern. +- Configuring :rust:`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). diff --git a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst index 0b2dd2861..3725852b6 100644 --- a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst +++ b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst @@ -12,7 +12,7 @@ Private fields are likewise visible within the rest of the module details of struct, controlling what data and functionality is visible externally. -.. code:: rust,editable +.. code:: rust use outer::Foo; @@ -58,10 +58,10 @@ Details 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``). +- Note how the :rust:`is_big` field is fully controlled by :rust:`Foo`, + allowing :rust:`Foo` to control how it's initialized and enforce any + invariants it needs to (e.g. that :rust:`is_big` is only :rust:`true` if + :rust:`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 @@ -79,10 +79,10 @@ More to Explore ----------------- - If students want more information about privacy (or lack thereof) in - enums, you can bring up ``#[doc_hidden]`` and ``#[non_exhaustive]`` + enums, you can bring up :rust:`#[doc_hidden]` and :rust:`#[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 +- Module privacy still applies when there are :rust:`impl` blocks in other modules `(example in the playground) `__. diff --git a/courses/comprehensive_rust_training/180_modules/05_paths.rst b/courses/comprehensive_rust_training/180_modules/05_paths.rst index a0f41faab..365068f91 100644 --- a/courses/comprehensive_rust_training/180_modules/05_paths.rst +++ b/courses/comprehensive_rust_training/180_modules/05_paths.rst @@ -6,10 +6,10 @@ use, super, self use, super, self ------------------ -A module can bring symbols from another module into scope with ``use``. +A module can bring symbols from another module into scope with :rust:`use`. You will typically see something like this at the top of each module: -.. code:: rust,editable +.. code:: rust use std::collections::HashSet; use std::process::abort; @@ -22,13 +22,13 @@ 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. + - :rust:`foo` or :rust:`self::foo` refers to :rust:`foo` in the current module, + - :rust:`super::foo` refers to :rust:`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. + - :rust:`crate::foo` refers to :rust:`foo` in the root of the current crate, + - :rust:`bar::foo` refers to :rust:`foo` in the :rust:`bar` crate. .. raw:: html @@ -37,25 +37,25 @@ Details --------- - It is common to "re-export" symbols at a shorter path. For example, - the top-level ``lib.rs`` in a crate might have + the top-level :rust:`lib.rs` in a crate might have - .. code:: rust,ignore + .. code:: rust mod storage; pub use storage::disk::DiskStorage; pub use storage::network::NetworkStorage; - making ``DiskStorage`` and ``NetworkStorage`` available to other + making :rust:`DiskStorage` and :rust:`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 + :rust:`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``. + scope. For example, to use the :rust:`read_to_string` method on a type + implementing the :rust:`Read` trait, you need to :rust:`use std::io::Read`. -- The ``use`` statement can have a wildcard: ``use std::io::*``. This +- The :rust:`use` statement can have a wildcard: :rust:`use std::io::*`. This is discouraged because it is not clear which items are imported, and those might change over time. diff --git a/courses/comprehensive_rust_training/180_modules/06_exercise.rst b/courses/comprehensive_rust_training/180_modules/06_exercise.rst index d4b094ad5..78e9e1b94 100644 --- a/courses/comprehensive_rust_training/180_modules/06_exercise.rst +++ b/courses/comprehensive_rust_training/180_modules/06_exercise.rst @@ -7,8 +7,8 @@ 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. +implementation. This library defines a :rust:`Widget` trait and a few +implementations of that trait, as well as a :rust:`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. @@ -26,8 +26,8 @@ Cargo project on your local filesystem: cd gui-modules cargo run -Edit the resulting ``src/main.rs`` to add ``mod`` statements, and add -additional files in the ``src`` directory. +Edit the resulting :rust:`src/main.rs` to add :rust:`mod` statements, and add +additional files in the :rust:`src` directory. -------- Source @@ -46,7 +46,7 @@ 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`` +them, and get accustomed to the required :rust:`mod`, :rust:`use`, and :rust:`pub` declarations. Afterward, discuss what organizations are most idiomatic. .. raw:: html diff --git a/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst index a69211bfd..340e87592 100644 --- a/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst +++ b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst @@ -7,11 +7,11 @@ 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 +with :rust:`#[test]`. Unit tests are often put in a nested :rust:`tests` module, +using :rust:`#[cfg(test)]` to conditionally compile them only when building tests. -.. code:: rust,editable,ignore +.. code:: rust fn first_word(text: &str) -> &str { match text.find(' ') { @@ -41,8 +41,8 @@ tests. } - This lets you unit test private helpers. -- The ``#[cfg(test)]`` attribute is only active when you run - ``cargo test``. +- The :rust:`#[cfg(test)]` attribute is only active when you run + :rust:`cargo test`. .. 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 146224bd5..685a8cc73 100644 --- a/courses/comprehensive_rust_training/190_testing/02_other.rst +++ b/courses/comprehensive_rust_training/190_testing/02_other.rst @@ -12,9 +12,9 @@ Integration Tests If you want to test your library as a client, use an integration test. -Create a ``.rs`` file under ``tests/``: +Create a :rust:`.rs` file under :rust:`tests/`: -.. code:: rust,ignore +.. code:: rust // tests/my_library.rs use my_library::init; @@ -36,18 +36,18 @@ Rust has built-in support for documentation tests: /// Shortens a string to the given length. /// - /// ``` + /// :rust:` /// # use playground::shorten_string; /// assert_eq!(shorten_string("Hello World", 5), "Hello"); /// assert_eq!(shorten_string("Hello World", 20), "Hello World"); - /// ``` + /// :rust:` 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 +- Code blocks in :rust:`///` comments are automatically seen as Rust code. +- The code will be compiled and executed as part of :rust:`cargo test`. +- Adding :rust:`#` 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 index 510dca137..aa08344c6 100644 --- a/courses/comprehensive_rust_training/190_testing/03_lints.rst +++ b/courses/comprehensive_rust_training/190_testing/03_lints.rst @@ -10,7 +10,7 @@ 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 +.. code:: rust #[deny(clippy::cast_possible_truncation)] fn main() { @@ -28,12 +28,12 @@ Details --------- There are compiler lints visible here, but not clippy lints. Run -``clippy`` on the playground site to show clippy warnings. Clippy has +:rust:`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. +Note that errors or warnings with :rust:`help: ...` can be fixed with +:rust:`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 index 633f84215..c8243b27f 100644 --- a/courses/comprehensive_rust_training/190_testing/04_exercise.rst +++ b/courses/comprehensive_rust_training/190_testing/04_exercise.rst @@ -13,15 +13,15 @@ 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``. + number :rust:`1234`, we double :rust:`3` and :rust:`1`. For the number :rust:`98765`, + we double :rust:`6` and :rust:`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``. + 9. So doubling :rust:`7` becomes :rust:`14` which becomes :rust:`1 + 4 = 5`. - Sum all the undoubled and doubled digits. -- The credit card number is valid if the sum ends with ``0``. +- The credit card number is valid if the sum ends with :rust:`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 diff --git a/courses/comprehensive_rust_training/200_error_handling/01_panics.rst b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst index 79499cab7..3164ade61 100644 --- a/courses/comprehensive_rust_training/200_error_handling/01_panics.rst +++ b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst @@ -10,7 +10,7 @@ Rust handles fatal errors with a "panic". Rust will trigger a panic if a fatal error happens at runtime: -.. code:: rust,editable,should_panic +.. code:: rust fn main() { let v = vec![10, 20, 30]; @@ -21,12 +21,12 @@ Rust will trigger a panic if a fatal error happens at runtime: - 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. + - Assertions (such as :rust:`assert!`) panic on failure + - Purpose-specific panics can use the :rust:`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 +- Use non-panicking APIs (such as :rust:`Vec::get`) if crashing is not acceptable. .. raw:: html @@ -38,7 +38,7 @@ Details By default, a panic will cause the stack to unwind. The unwinding can be caught: -.. code:: rust,editable +.. code:: rust use std::panic; @@ -53,11 +53,11 @@ caught: } - Catching is unusual; do not attempt to implement exceptions with - ``catch_unwind``! + :rust:`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``. +- This does not work if :rust:`panic = 'abort'` is set in your + :rust:`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 index 8eb6f64ea..8a6bf907f 100644 --- a/courses/comprehensive_rust_training/200_error_handling/02_result.rst +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -1,16 +1,16 @@ ============ -``Result`` +:rust:`Result` ============ ------------ -``Result`` +:rust:`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 +.. code:: rust use std::fs::File; use std::io::Read; @@ -38,17 +38,17 @@ enum, which we briefly saw when discussing standard library types. Details --------- -- ``Result`` has two variants: ``Ok`` which contains the success value, - and ``Err`` which contains an error value of some kind. +- :rust:`Result` has two variants: :rust:`Ok` which contains the success value, + and :rust:`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`` + function's type signature by having the function return a :rust:`Result` value. -- Like with ``Option``, there is no way to forget to handle an error: +- Like with :rust:`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 + first pattern matching on the :rust:`Result` to check which variant you + have. Methods like :rust:`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. @@ -77,7 +77,7 @@ Exceptions 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 + a :rust:`try` block is reached. An error originating deep in the call stack may impact an unrelated function further up. --------------- diff --git a/courses/comprehensive_rust_training/200_error_handling/03_try.rst b/courses/comprehensive_rust_training/200_error_handling/03_try.rst index 2074a0289..81c16c7b0 100644 --- a/courses/comprehensive_rust_training/200_error_handling/03_try.rst +++ b/courses/comprehensive_rust_training/200_error_handling/03_try.rst @@ -7,11 +7,11 @@ 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 +with the :rust:`Result` type, but matching this type on every call can be +cumbersome. The try-operator :rust:`?` is used to return errors to the caller. It lets you turn the common -.. code:: rust,ignore +.. code:: rust match some_expression { Ok(value) => value, @@ -20,13 +20,13 @@ caller. It lets you turn the common into the much simpler -.. code:: rust,ignore +.. code:: rust some_expression? We can use this to simplify our error handling code: -.. code:: rust,editable +.. code:: rust use std::io::Read; use std::{fs, io}; @@ -57,18 +57,18 @@ We can use this to simplify our error handling code: Details --------- -Simplify the ``read_username`` function to use ``?``. +Simplify the :rust:`read_username` function to use :rust:`?`. 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 +- The :rust:`username` variable can be either :rust:`Ok(string)` or + :rust:`Err(error)`. +- Use the :rust:`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. +- Note that :rust:`main` can return a :rust:`Result<(), E>` as long as it + implements :rust:`std::process::Termination`. In practice, this means + that :rust:`E` implements :rust:`Debug`. The executable will print the + :rust:`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 index aa772da79..2b8dc3bbf 100644 --- a/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst +++ b/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst @@ -6,23 +6,23 @@ Try Conversions Try Conversions ----------------- -The effective expansion of ``?`` is a little more complicated than +The effective expansion of :rust:`?` is a little more complicated than previously indicated: -.. code:: rust,ignore +.. code:: rust expression? works the same as -.. code:: rust,ignore +.. code:: rust 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 +The :rust:`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. @@ -30,7 +30,7 @@ errors into higher-level errors. Example --------- -.. code:: rust,editable +.. code:: rust use std::error::Error; use std::io::Read; @@ -80,24 +80,24 @@ Example 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``. +The :rust:`?` operator must return a value compatible with the return type +of the function. For :rust:`Result`, it means that the error types have to +be compatible. A function that returns :rust:`Result` can +only use :rust:`?` on a value of type :rust:`Result` if +:rust:`ErrorOuter` and :rust:`ErrorInner` are the same type or if :rust:`ErrorOuter` +implements :rust:`From`. -A common alternative to a ``From`` implementation is -``Result::map_err``, especially when the conversion only happens in one +A common alternative to a :rust:`From` implementation is +:rust:`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. +There is no compatibility requirement for :rust:`Option`. A function +returning :rust:`Option` can use the :rust:`?` operator on :rust:`Option` for +arbitrary :rust:`T` and :rust:`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``. +A function that returns :rust:`Result` cannot use :rust:`?` on :rust:`Option` and +vice versa. However, :rust:`Option::ok_or` converts :rust:`Option` to :rust:`Result` +whereas :rust:`Result::ok` turns :rust:`Result` into :rust:`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 index 465936d86..334051b20 100644 --- a/courses/comprehensive_rust_training/200_error_handling/05_error.rst +++ b/courses/comprehensive_rust_training/200_error_handling/05_error.rst @@ -8,10 +8,10 @@ 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 +:rust:`std::error::Error` trait makes it easy to create a trait object that can contain any error. -.. code:: rust,editable +.. code:: rust use std::error::Error; use std::fs; @@ -38,16 +38,16 @@ can contain any error. Details --------- -The ``read_count`` function can return ``std::io::Error`` (from file -operations) or ``std::num::ParseIntError`` (from ``String::parse``). +The :rust:`read_count` function can return :rust:`std::io::Error` (from file +operations) or :rust:`std::num::ParseIntError` (from :rust:`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 +not a good idea to use :rust:`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 +Make sure to implement the :rust:`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 index bb84afdcf..d415ab728 100644 --- a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -1,17 +1,17 @@ =============== -``thiserror`` +:rust:`thiserror` =============== --------------- -``thiserror`` +:rust:`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. +macros that assist in implementing :rust:`From`, :rust:`Display`, and the +:rust:`Error` trait. -.. code:: rust,editable,compile_fail +.. code:: rust use std::io::Read; use std::{fs, io}; @@ -48,12 +48,12 @@ macros that assist in implementing ``From``, ``Display``, and the Details --------- -- The ``Error`` derive macro is provided by ``thiserror``, and has lots +- The :rust:`Error` derive macro is provided by :rust:`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`` +- The message from :rust:`#[error]` is used to derive the :rust:`Display` trait. -- Note that the (``thiserror::``)\ ``Error`` derive macro, while it has - the effect of implementing the (``std::error::``)\ ``Error`` trait, +- Note that the (:rust:`thiserror::`)\ :rust:`Error` derive macro, while it has + the effect of implementing the (:rust:`std::error::`)\ :rust:`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 index c920169c8..415c78c2b 100644 --- a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -1,9 +1,9 @@ ============ -``anyhow`` +:rust:`anyhow` ============ ------------ -``anyhow`` +:rust:`anyhow` ------------ The `anyhow `__ crate provides a rich error @@ -15,7 +15,7 @@ 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 +.. code:: rust use anyhow::{bail, Context, Result}; use std::fs; @@ -52,19 +52,19 @@ trait impls explicitly for custom error types. Details --------- -- ``anyhow::Error`` is essentially a wrapper around ``Box``. +- :rust:`anyhow::Error` is essentially a wrapper around :rust:`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)`` +- :rust:`anyhow::Result` is a type alias for + :rust:`Result`. +- Functionality provided by :rust:`anyhow::Error` may be familiar to Go + developers, as it provides similar behavior to the Go :rust:`error` type + and :rust:`Result` is much like a Go :rust:`(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. +- :rust:`anyhow::Context` is a trait implemented for the standard + :rust:`Result` and :rust:`Option` types. :rust:`use anyhow::Context` is necessary + to enable :rust:`.context()` and :rust:`.with_context()` on those types. ================= More to Explore @@ -74,8 +74,8 @@ 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 +- :rust:`anyhow::Error` has support for downcasting, much like + :rust:`std::any::Any`; the specific error type stored inside can be extracted for examination if desired with `Error::downcast `__. diff --git a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst index b65584ce6..36ef2c0d5 100644 --- a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst +++ b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst @@ -8,12 +8,12 @@ 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 +Dividing by zero! Rewrite :rust:`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``. +We provide a simple :rust:`DivideByZeroError` type to use as the error type +for :rust:`eval`. -.. code:: rust,editable +.. code:: rust {{#include exercise.rs:types}} 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 3dec2e85f..f90f05c7a 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst @@ -22,9 +22,9 @@ 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. +- Access :rust:`union` fields. +- Call :rust:`unsafe` functions, including :rust:`extern` functions. +- Implement :rust:`unsafe` traits. We will briefly cover unsafe capabilities next. For full details, please see diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst index c290932b1..43cf8c156 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst @@ -6,9 +6,9 @@ Dereferencing Raw Pointers Dereferencing Raw Pointers ---------------------------- -Creating pointers is safe, but dereferencing them requires ``unsafe``: +Creating pointers is safe, but dereferencing them requires :rust:`unsafe`: -.. code:: rust,editable +.. code:: rust fn main() { let mut s = String::from("careful!"); @@ -42,7 +42,7 @@ 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 +write a comment for each :rust:`unsafe` block explaining how the code inside it satisfies the safety requirements of the unsafe operations it is doing. @@ -62,8 +62,8 @@ i.e.: 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 +:rust:`*r1` has the :rust:`'static` lifetime, so :rust:`r3` has type +:rust:`&'static String`, and thus outlives :rust:`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 index cdbc9027d..1688e1246 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst @@ -8,7 +8,7 @@ Mutable Static Variables It is safe to read an immutable static variable: -.. code:: rust,editable +.. code:: rust static HELLO_WORLD: &str = "Hello, world!"; @@ -19,7 +19,7 @@ It is safe to read an immutable static variable: However, since data races can occur, it is unsafe to read and write mutable static variables: -.. code:: rust,editable +.. code:: rust static mut COUNTER: u32 = 0; @@ -47,11 +47,11 @@ 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 + the :rust:`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 + cases where it might make sense in low-level :rust:`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 index 7f0ee8785..a1a9c0e27 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst @@ -8,7 +8,7 @@ Unions Unions are like enums, but you need to track the active field yourself: -.. code:: rust,editable +.. code:: rust #[repr(C)] union MyUnion { 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 cc066daa6..6d948d793 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 @@ -10,10 +10,10 @@ Unsafe Functions Calling Unsafe Functions -------------------------- -A function or method can be marked ``unsafe`` if it has extra +A function or method can be marked :rust:`unsafe` if it has extra preconditions you must uphold to avoid undefined behaviour: -.. code:: rust,editable +.. code:: rust extern "C" { fn abs(input: i32) -> i32; @@ -52,10 +52,10 @@ preconditions you must uphold to avoid undefined behaviour: Writing Unsafe Functions -------------------------- -You can mark your own functions as ``unsafe`` if they require particular +You can mark your own functions as :rust:`unsafe` if they require particular conditions to avoid undefined behaviour. -.. code:: rust,editable +.. code:: rust /// Swaps the values pointed to by the given pointers. /// @@ -92,15 +92,15 @@ Details Calling Unsafe Functions -------------------------- -``get_unchecked``, like most ``_unchecked`` functions, is unsafe, -because it can create UB if the range is incorrect. ``abs`` is unsafe +:rust:`get_unchecked`, like most :rust:`_unchecked` functions, is unsafe, +because it can create UB if the range is incorrect. :rust:`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; +The :rust:`"C"` in this example is the ABI; `other ABIs are available too `__. .. _writing-unsafe-functions-1: @@ -109,12 +109,12 @@ The ``"C"`` in this example is the ABI; Writing Unsafe Functions -------------------------- -We wouldn't actually use pointers for a ``swap`` function - it can be +We wouldn't actually use pointers for a :rust:`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. +:rust:`unsafe` block. We can prohibit this with +:rust:`#[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 index 5af6dd949..c6d038ac1 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 @@ -6,14 +6,14 @@ Implementing Unsafe Traits Implementing Unsafe Traits ---------------------------- -Like with functions, you can mark a trait as ``unsafe`` if the +Like with functions, you can mark a trait as :rust:`unsafe` if the implementation must guarantee particular conditions to avoid undefined behaviour. -For example, the ``zerocopy`` crate has an unsafe trait that looks +For example, the :rust:`zerocopy` crate has an unsafe trait that looks `something like this `__: -.. code:: rust,editable +.. code:: rust use std::{mem, slice}; @@ -37,13 +37,13 @@ For example, the ``zerocopy`` crate has an unsafe trait that looks Details --------- -There should be a ``# Safety`` section on the Rustdoc for the trait +There should be a :rust:`# 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 +The actual safety section for :rust:`IntoBytes` is rather longer and more complicated. -The built-in ``Send`` and ``Sync`` traits are unsafe. +The built-in :rust:`Send` and :rust:`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 index 35a9153d3..69b9082e4 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst @@ -8,7 +8,7 @@ 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 +:rust:`libc` functions you would use from C to read the names of files in a directory. You will want to consult the manual pages: @@ -42,19 +42,19 @@ find a number of string types which you need for the exercise: 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 +- :rust:`&str` to :rust:`CString`: you need to allocate space for a trailing + :rust:`\0` character, +- :rust:`CString` to :rust:`*const i8`: you need a pointer to call C functions, +- :rust:`*const i8` to :rust:`&CStr`: you need something which can find the + trailing :rust:`\0` character, +- :rust:`&CStr` to :rust:`&[u8]`: a slice of bytes is the universal interface for "some unknown data", -- ``&[u8]`` to ``&OsStr``: ``&OsStr`` is a step towards ``OsString``, +- :rust:`&[u8]` to :rust:`&OsStr`: :rust:`&OsStr` is a step towards :rust:`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. +- :rust:`&OsStr` to :rust:`OsString`: you need to clone the data in :rust:`&OsStr` + to be able to return it and call :rust:`readdir` again. The `Nomicon `__ also has a very useful chapter about FFI. @@ -62,7 +62,7 @@ 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 +.. code:: rust // TODO: remove this when you're done with your implementation. #![allow(unused_imports, unused_variables, dead_code)] From 4c8c375cc810e8e2a7350cebe51251bb32d2c9b6 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:46:52 -0500 Subject: [PATCH 02/55] Remove "raw" blocks --- .../020_hello_world/01_what_is_rust.rst | 5 ----- .../020_hello_world/02_benefits.rst | 5 ----- .../020_hello_world/03_playground.rst | 5 ----- .../030_types_and_values/01_hello_world.rst | 5 ----- .../030_types_and_values/02_variables.rst | 5 ----- .../030_types_and_values/03_values.rst | 5 ----- .../030_types_and_values/04_arithmetic.rst | 5 ----- .../030_types_and_values/05_inference.rst | 9 -------- .../040_control_flow_basics/01_if.rst | 5 ----- .../040_control_flow_basics/02_match.rst | 5 ----- .../04_break_continue.rst | 5 ----- .../05_blocks_and_scopes.rst | 5 ----- .../040_control_flow_basics/06_functions.rst | 9 -------- .../040_control_flow_basics/07_macros.rst | 5 ----- .../050_tuples_and_arrays/01_arrays.rst | 9 -------- .../050_tuples_and_arrays/02_tuples.rst | 9 -------- .../050_tuples_and_arrays/03_iteration.rst | 5 ----- .../04_destructuring.rst | 5 ----- .../060_references/01_shared.rst | 9 -------- .../060_references/02_exclusive.rst | 9 -------- .../060_references/03_slices.rst | 9 -------- .../060_references/04_strings.rst | 21 ------------------- .../060_references/05_dangling.rst | 9 -------- .../01_named_structs.rst | 5 ----- .../02_tuple_structs.rst | 11 ---------- .../070_user_defined_types/03_enums.rst | 13 ------------ .../070_user_defined_types/04_aliases.rst | 5 ----- .../070_user_defined_types/05_const.rst | 9 -------- .../070_user_defined_types/06_static.rst | 5 ----- .../080_pattern_matching/01_match.rst | 5 ----- .../02_destructuring_structs.rst | 5 ----- .../03_destructuring_enums.rst | 5 ----- .../04_let_control_flow.rst | 9 -------- .../080_pattern_matching/05_exercise.rst | 8 ------- .../090_methods_and_traits/01_methods.rst | 5 ----- .../090_methods_and_traits/02_traits.rst | 5 ----- .../090_methods_and_traits/03_deriving.rst | 5 ----- .../100_generics/01_generic_functions.rst | 5 ----- .../100_generics/02_generic_data.rst | 5 ----- .../100_generics/03_generic_traits.rst | 5 ----- .../100_generics/04_trait_bounds.rst | 5 ----- .../100_generics/05_impl_trait.rst | 5 ----- .../100_generics/06_dyn_trait.rst | 5 ----- .../100_generics/07_exercise.rst | 5 ----- .../110_std_types/02_docs.rst | 5 ----- .../110_std_types/03_option.rst | 5 ----- .../110_std_types/04_result.rst | 5 ----- .../110_std_types/05_string.rst | 5 ----- .../110_std_types/06_vec.rst | 5 ----- .../110_std_types/07_hashmap.rst | 5 ----- .../120_std_traits/01_comparisons.rst | 5 ----- .../120_std_traits/02_operators.rst | 5 ----- .../120_std_traits/03_from_and_into.rst | 5 ----- .../120_std_traits/04_casting.rst | 5 ----- .../120_std_traits/06_default.rst | 5 ----- .../120_std_traits/07_closures.rst | 5 ----- .../130_memory_management/01_review.rst | 5 ----- .../130_memory_management/02_approaches.rst | 5 ----- .../130_memory_management/03_ownership.rst | 9 -------- .../130_memory_management/04_move.rst | 5 ----- .../130_memory_management/05_clone.rst | 5 ----- .../130_memory_management/06_copy_types.rst | 13 ------------ .../130_memory_management/07_drop.rst | 5 ----- .../140_smart_pointers/01_box.rst | 5 ----- .../140_smart_pointers/02_rc.rst | 5 ----- .../140_smart_pointers/03_trait_objects.rst | 5 ----- .../150_borrowing/01_shared.rst | 13 ------------ .../150_borrowing/02_borrowck.rst | 13 ------------ .../150_borrowing/03_examples.rst | 5 ----- .../150_borrowing/04_interior_mutability.rst | 5 ----- .../160_lifetimes/01_lifetime_annotations.rst | 10 --------- .../160_lifetimes/02_lifetime_elision.rst | 5 ----- .../160_lifetimes/03_struct_lifetimes.rst | 5 ----- .../160_lifetimes/04_exercise.rst | 9 -------- .../170_iterators/01_motivation.rst | 5 ----- .../170_iterators/02_iterator.rst | 5 ----- .../170_iterators/03_helpers.rst | 5 ----- .../170_iterators/04_collect.rst | 5 ----- .../170_iterators/05_intoiterator.rst | 5 ----- .../180_modules/01_modules.rst | 5 ----- .../180_modules/02_filesystem.rst | 5 ----- .../180_modules/03_visibility.rst | 5 ----- .../180_modules/04_encapsulation.rst | 5 ----- .../180_modules/05_paths.rst | 5 ----- .../180_modules/06_exercise.rst | 5 ----- .../190_testing/01_unit_tests.rst | 5 ----- .../190_testing/03_lints.rst | 5 ----- .../200_error_handling/01_panics.rst | 5 ----- .../200_error_handling/02_result.rst | 5 ----- .../200_error_handling/03_try.rst | 5 ----- .../200_error_handling/04_try_conversions.rst | 5 ----- .../200_error_handling/05_error.rst | 5 ----- .../200_error_handling/06_thiserror.rst | 5 ----- .../200_error_handling/07_anyhow.rst | 5 ----- .../200_error_handling/08_exercise.rst | 3 --- .../210_unsafe_rust/01_unsafe.rst | 5 ----- .../210_unsafe_rust/02_dereferencing.rst | 5 ----- .../210_unsafe_rust/03_mutable_static.rst | 5 ----- .../210_unsafe_rust/04_unions.rst | 5 ----- .../210_unsafe_rust/05_unsafe_functions.rst | 5 ----- .../210_unsafe_rust/06_unsafe_traits.rst | 5 ----- 101 files changed, 613 deletions(-) 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 index 58845362a..7ba69ab11 100644 --- 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 @@ -27,8 +27,6 @@ Rust is a new programming language which had its `1.0 release in - desktops, - servers. -.. raw:: html - --------- Details --------- @@ -41,6 +39,3 @@ Rust fits in the same area as C++: 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 index 3cacd4c2a..5203a9644 100644 --- a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst +++ b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst @@ -37,8 +37,6 @@ Some unique selling points of Rust: - Built-in support for testing. - Excellent Language Server Protocol support. -.. raw:: html - --------- Details --------- @@ -60,6 +58,3 @@ Depending on the answer you can highlight different features of Rust: 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 index 1fef7491e..af0099428 100644 --- a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst +++ b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst @@ -21,8 +21,6 @@ starts with. It comes with a few handy features: - If you're interested, use "ASM" under "..." to see the generated assembly code. -.. raw:: html - --------- Details --------- @@ -32,6 +30,3 @@ 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/01_hello_world.rst b/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst index e28644dfd..6661e6cc8 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 @@ -23,8 +23,6 @@ What you see: - Rust has hygienic macros, :rust:`println!` is an example of this. - Rust strings are UTF-8 encoded and can contain any Unicode character. -.. raw:: html - --------- Details --------- @@ -56,6 +54,3 @@ Key points: 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 index c2be512fc..f1f361c19 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst @@ -18,8 +18,6 @@ with :rust:`let`: // println!("x: {x}"); } -.. raw:: html - --------- Details --------- @@ -35,6 +33,3 @@ Details - The :rust:`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 index 0a4298578..3c41227b9 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 @@ -37,8 +37,6 @@ The types have widths as follows: - :rust:`char` is 32 bits wide, - :rust:`bool` is 8 bits wide. -.. raw:: html - --------- Details --------- @@ -48,6 +46,3 @@ There are a few syntaxes which are not shown above: - All underscores in numbers can be left out, they are for legibility only. So :rust:`1_000` can be written as :rust:`1000` (or :rust:`10_00`), and :rust:`123_i64` can be written as :rust:`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 index 7ae7a0ca5..b5f85ce7c 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst @@ -16,8 +16,6 @@ Arithmetic println!("result: {}", interproduct(120, 100, 248)); } -.. raw:: html - --------- Details --------- @@ -40,6 +38,3 @@ are accessed with method syntax, e.g., 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 index 47a1a5f5c..c555187b1 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst @@ -8,10 +8,6 @@ Type Inference Rust will look at how the variable is *used* to determine the type: -.. raw:: html - - - .. code:: rust fn takes_u32(x: u32) { @@ -31,8 +27,6 @@ Rust will look at how the variable is *used* to determine the type: // takes_u32(y); } -.. raw:: html - --------- Details --------- @@ -58,6 +52,3 @@ Similarly, floating-point literals default to :rust:`f64`. assert_eq!(x, y); // ERROR: no implementation for `{float} == {integer}` } - -.. raw:: html - 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 8d44dfa59..ea5d9637d 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 @@ -34,8 +34,6 @@ each block becomes the value of the :rust:`if` expression: println!("number size: {}", size); } -.. raw:: html - --------- Details --------- @@ -48,6 +46,3 @@ An :rust:`if` expression should be used in the same way as the other expressions. For example, when it is used in a :rust:`let` statement, the statement must be terminated with a :rust:`;` as well. Remove the :rust:`;` before :rust:`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 index f44938dd1..5f31ff379 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst @@ -35,8 +35,6 @@ Like :rust:`if` expressions, :rust:`match` can also return a value; println!("The value of {flag} is {val}"); } -.. raw:: html - --------- Details --------- @@ -80,6 +78,3 @@ More to Explore 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/04_break_continue.rst b/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst index d5cb6e981..7f2d53816 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 @@ -30,8 +30,6 @@ value of the :rust:`loop` expression. } } -.. raw:: html - --------- Details --------- @@ -40,6 +38,3 @@ Note that :rust:`loop` is the only looping construct which can return a non-trivial value. This is because it's guaranteed to only return at a :rust:`break` statement (unlike :rust:`while` and :rust:`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 index d9ef46472..f5450196d 100644 --- 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 @@ -29,8 +29,6 @@ expression of the block: If the last expression ends with :rust:`;`, then the resulting value and type is :rust:`()`. -.. raw:: html - --------- Details --------- @@ -38,6 +36,3 @@ 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 :rust:`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 index b723d6c0a..979f3b4b3 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 @@ -6,10 +6,6 @@ Functions Functions ----------- -.. raw:: html - - - .. code:: rust fn gcd(a: u32, b: u32) -> u32 { @@ -24,8 +20,6 @@ Functions println!("gcd: {}", gcd(143, 52)); } -.. raw:: html - --------- Details --------- @@ -46,6 +40,3 @@ Details 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 index 2bfa94388..7cb3bf031 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 @@ -40,8 +40,6 @@ end. The Rust standard library includes an assortment of useful macros. println!("{n}! = {}", factorial(n)); } -.. raw:: html - --------- Details --------- @@ -52,6 +50,3 @@ 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/050_tuples_and_arrays/01_arrays.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst index 0ee9d10eb..8103f0766 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst @@ -6,10 +6,6 @@ Arrays Arrays -------- -.. raw:: html - - - .. code:: rust fn main() { @@ -18,8 +14,6 @@ Arrays println!("a: {a:?}"); } -.. raw:: html - --------- Details --------- @@ -44,6 +38,3 @@ Details - Adding :rust:`#`, eg :rust:`{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 index 24d1e90f4..7fcb167fb 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst @@ -6,10 +6,6 @@ Tuples Tuples -------- -.. raw:: html - - - .. code:: rust fn main() { @@ -18,8 +14,6 @@ Tuples println!("t.1: {}", t.1); } -.. raw:: html - --------- Details --------- @@ -34,6 +28,3 @@ Details - The empty tuple :rust:`()` is referred to as the "unit type" and signifies absence of a return value, akin to :rust:`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 index fb73398b5..740ba9c71 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst @@ -19,8 +19,6 @@ The :rust:`for` statement supports iterating over arrays (but not tuples). } } -.. raw:: html - --------- Details --------- @@ -31,6 +29,3 @@ covered that yet. The :rust:`assert_ne!` macro is new here. There are also :rust:`assert_eq!` and :rust:`assert!` macros. These are always checked, while debug-only variants like :rust:`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 index 5958b45f4..21ff863f5 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst @@ -28,8 +28,6 @@ larger value into its constituent parts: println!("left: {left}, right: {right}"); } -.. raw:: html - --------- Details --------- @@ -45,6 +43,3 @@ Details 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/060_references/01_shared.rst b/courses/comprehensive_rust_training/060_references/01_shared.rst index 331122c88..fca3aa92e 100644 --- a/courses/comprehensive_rust_training/060_references/01_shared.rst +++ b/courses/comprehensive_rust_training/060_references/01_shared.rst @@ -10,10 +10,6 @@ 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 fn main() { @@ -29,8 +25,6 @@ A shared reference to a type :rust:`T` has type :rust:`&T`. A reference value is made with the :rust:`&` operator. The :rust:`*` operator "dereferences" a reference, yielding its value. -.. raw:: html - --------- Details --------- @@ -70,6 +64,3 @@ Details 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 index 264ab6c52..a8b8c6b50 100644 --- a/courses/comprehensive_rust_training/060_references/02_exclusive.rst +++ b/courses/comprehensive_rust_training/060_references/02_exclusive.rst @@ -9,10 +9,6 @@ Exclusive References Exclusive references, also known as mutable references, allow changing the value they refer to. They have type :rust:`&mut T`. -.. raw:: html - - - .. code:: rust fn main() { @@ -22,8 +18,6 @@ the value they refer to. They have type :rust:`&mut T`. println!("point: {point:?}"); } -.. raw:: html - --------- Details --------- @@ -40,6 +34,3 @@ Key points: :rust:`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 index 8af661dd7..85d05f2d1 100644 --- a/courses/comprehensive_rust_training/060_references/03_slices.rst +++ b/courses/comprehensive_rust_training/060_references/03_slices.rst @@ -8,10 +8,6 @@ Slices A slice gives you a view into a larger collection: -.. raw:: html - - - .. code:: rust fn main() { @@ -25,8 +21,6 @@ A slice gives you a view into a larger collection: - Slices borrow data from the sliced type. -.. raw:: html - --------- Details --------- @@ -50,6 +44,3 @@ Details - Slices always borrow from another object. In this example, :rust:`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 index 2b2944926..fd7e446ff 100644 --- a/courses/comprehensive_rust_training/060_references/04_strings.rst +++ b/courses/comprehensive_rust_training/060_references/04_strings.rst @@ -1,9 +1,3 @@ -.. raw:: html - - - ========= Strings ========= @@ -18,8 +12,6 @@ We can now understand the two string types in Rust: - :rust:`String` is an owned buffer of UTF-8 encoded bytes, similar to :rust:`Vec`. -.. raw:: html - @@ -38,8 +30,6 @@ We can now understand the two string types in Rust: println!("s3: {s3}"); } -.. raw:: html - --------- Details --------- @@ -74,10 +64,6 @@ Details - Byte strings literals allow you to create a :rust:`&[u8]` value directly: - .. raw:: html - - - .. code:: rust fn main() { @@ -89,16 +75,9 @@ Details disabled: :rust:`r"\n" == "\\n"`. You can embed double-quotes by using an equal amount of :rust:`#` on either side of the quotes: - .. raw:: html - - - .. code:: rust 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 index 27185641a..de790861f 100644 --- a/courses/comprehensive_rust_training/060_references/05_dangling.rst +++ b/courses/comprehensive_rust_training/060_references/05_dangling.rst @@ -11,10 +11,6 @@ safe to use. One rule is that references can never be :rust:`null`, making them safe to use without :rust:`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 fn main() { @@ -25,8 +21,6 @@ for now is that references can't *outlive* the data they point to. println!("x: {x_ref}"); } -.. raw:: html - --------- Details --------- @@ -46,6 +40,3 @@ More to Explore 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/070_user_defined_types/01_named_structs.rst b/courses/comprehensive_rust_training/070_user_defined_types/01_named_structs.rst index 6ffa1749d..2d0a1fe06 100644 --- 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 @@ -35,8 +35,6 @@ Like C and C++, Rust has support for custom structs: describe(&jackie); } -.. raw:: html - --------- Details --------- @@ -63,6 +61,3 @@ Key Points: - The syntax :rust:`..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 index 74f21b570..e0722fa1a 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 @@ -1,9 +1,3 @@ -.. raw:: html - - - =============== Tuple Structs =============== @@ -43,8 +37,6 @@ This is often used for single-field wrappers (called newtypes): set_thruster_force(force); } -.. raw:: html - --------- Details --------- @@ -68,6 +60,3 @@ Details - 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 38dde7e68..073166f40 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 @@ -29,8 +29,6 @@ different variants: println!("On this turn: {player_move:?}"); } -.. raw:: html - --------- Details --------- @@ -63,10 +61,6 @@ Key Points: - You can control the discriminant if needed (e.g., for compatibility with C): - .. raw:: html - - - .. code:: rust #[repr(u32)] @@ -101,10 +95,6 @@ space. provides no guarantees regarding this representation, therefore this is totally unsafe. - .. raw:: html - - - .. code:: rust use std::mem::transmute; @@ -137,6 +127,3 @@ space. 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 index 5395db5c1..c479b7821 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst @@ -23,8 +23,6 @@ interchangeably. use std::sync::{Arc, RwLock}; type PlayerInventory = RwLock>>>; -.. raw:: html - --------- Details --------- @@ -34,6 +32,3 @@ Details :rust:`struct InventoryCount(usize)` to :rust:`type InventoryCount = usize`. - C programmers will recognize this as similar to a :rust:`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 index 36e12798f..d4d143949 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 @@ -9,10 +9,6 @@ Constants are evaluated at compile time and their values are inlined wherever they are used: -.. raw:: html - - - .. code:: rust const DIGEST_SIZE: usize = 3; @@ -47,14 +43,9 @@ Only functions marked :rust:`const` can be called at compile time to generate :rust:`const` values. :rust:`const` functions can however be called at runtime. -.. raw:: html - --------- Details --------- - Mention that :rust:`const` behaves semantically similar to C++'s :rust:`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 index d18f2c537..137916adb 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 @@ -25,8 +25,6 @@ lives through the entirety of the program execution. When a globally-scoped value does not have a reason to need object identity, :rust:`const` is generally preferred. -.. raw:: html - --------- Details --------- @@ -53,6 +51,3 @@ initialization on first use. :rust:`OnceCell` is not :rust:`Sync` and thus cannot be used in this context. Thread-local data can be created with the macro :rust:`std::thread_local`. - -.. raw:: html - diff --git a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst index eafa897cf..48dbd6e69 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst @@ -32,8 +32,6 @@ 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 --------- @@ -85,6 +83,3 @@ More To Explore :rust:`Some(inner)` expression, so it contains the full :rust:`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 index 3342d8ea2..7f6ba5a8a 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst @@ -12,8 +12,6 @@ Like tuples, Struct can also be destructured by matching: {{#include ../../third_party/rust-by-example/destructuring-structs.rs}} -.. raw:: html - --------- Details --------- @@ -25,6 +23,3 @@ Details hard to spot. Try changing the :rust:`2` in the second arm to a variable, and see that it subtly doesn't work. Change it to a :rust:`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 index 326252068..29d0ba1b9 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst @@ -39,8 +39,6 @@ Here we have used the arms to *destructure* the :rust:`Result` value. In the first arm, :rust:`half` is bound to the value inside the :rust:`Ok` variant. In the second arm, :rust:`msg` is bound to the error message. -.. raw:: html - --------- Details --------- @@ -56,6 +54,3 @@ Details - 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 index bebb69bb9..51b8d2ff5 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 @@ -83,10 +83,6 @@ Like with :rust:`if let`, there is a `while let `__ variant which repeatedly tests a value against a pattern: -.. raw:: html - - - .. code:: rust fn main() { @@ -103,8 +99,6 @@ returns :rust:`Some(c)` until the string is empty, after which it will return :rust:`None`. The :rust:`while let` lets us keep iterating through all items. -.. raw:: html - --------- Details --------- @@ -162,6 +156,3 @@ while-let if statement that breaks when there is no value to unwrap for :rust:`name.pop()`. The :rust:`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 index 7585e9f7e..bc2c3fe0b 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst @@ -11,10 +11,6 @@ Let's write a simple recursive evaluator for arithmetic expressions. An example of a small arithmetic expression could be :rust:`10 + 20`, which evaluates to :rust:`30`. We can represent the expression as a tree: -.. raw:: html - - - .. code:: bob .-------. @@ -29,10 +25,6 @@ A bigger and more complex expression would be :rust:`(10 * 9) + ((3 - 4) * 5)`, which evaluate to :rust:`85`. We represent this as a much bigger tree: -.. raw:: html - - - .. code:: bob .-----. 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 72a04db1e..6c6e7e9ad 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 @@ -70,8 +70,6 @@ acts on. There are several common receivers for a method: - No receiver: this becomes a static method on the struct. Typically used to create constructors which are called :rust:`new` by convention. -.. raw:: html - --------- Details --------- @@ -101,6 +99,3 @@ Key Points: - Beyond variants on :rust:`self`, there are also `special wrapper types `__ allowed to be receiver types, such as :rust:`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 index 4ca3f1d7c..e677fe279 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst @@ -19,8 +19,6 @@ interfaces: fn greet(&self); } -.. raw:: html - --------- Details --------- @@ -30,6 +28,3 @@ Details - 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 index 4b62ac2cc..10a5b472f 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst @@ -26,8 +26,6 @@ as follows: println!("{p1:?} vs. {p2:?}"); } -.. raw:: html - --------- Details --------- @@ -36,6 +34,3 @@ Derivation is implemented with macros, and many crates provide useful derive macros to add useful functionality. For example, :rust:`serde` can derive serialization support for a struct using :rust:`#[derive(Serialize)]`. - -.. raw:: html - diff --git a/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst index d8af0e06e..127ae8ad3 100644 --- a/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst +++ b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst @@ -26,8 +26,6 @@ stored. println!("picked a string: {:?}", pick(28, "dog", "cat")); } -.. raw:: html - --------- Details --------- @@ -57,6 +55,3 @@ Details - 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 index 283394a08..d34b387aa 100644 --- a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst +++ b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst @@ -33,8 +33,6 @@ You can use generics to abstract over the concrete field type: println!("coords: {:?}", integer.coords()); } -.. raw:: html - --------- Details --------- @@ -54,6 +52,3 @@ Details - Try declaring a new variable :rust:`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., :rust:`T` and :rust:`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 index 0d5b122dc..032d8fd9c 100644 --- a/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst +++ b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst @@ -32,8 +32,6 @@ parameters get concrete types when it is used. println!("{from_int:?}, {from_bool:?}"); } -.. raw:: html - --------- Details --------- @@ -55,6 +53,3 @@ Details 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 index 47b6eda3d..724dfe5f2 100644 --- a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst +++ b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst @@ -25,8 +25,6 @@ You can do this with :rust:`T: Trait`: println!("{pair:?}"); } -.. raw:: html - --------- Details --------- @@ -56,6 +54,3 @@ Details - Note that Rust does not (yet) support specialization. For example, given the original :rust:`duplicate`, it is invalid to add a specialized :rust:`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 index db1761b6b..13f314608 100644 --- a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst @@ -30,8 +30,6 @@ function arguments and return values: println!("debuggable: {debuggable:?}"); } -.. raw:: html - --------- Details --------- @@ -57,6 +55,3 @@ meaning of :rust:`impl Trait` is a bit different in the different positions. What is the type of :rust:`debuggable`? Try :rust:`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 index 76d0e199d..e9da311c1 100644 --- a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst @@ -56,8 +56,6 @@ supports using them for type-erased, dynamic dispatch via trait objects: dynamic(&dog); } -.. raw:: html - --------- Details --------- @@ -90,6 +88,3 @@ Details - A :rust:`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 index 03ce2ee2d..cf22b0a7a 100644 --- a/courses/comprehensive_rust_training/100_generics/07_exercise.rst +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -19,8 +19,6 @@ trait. {{#include exercise.rs:main}} -.. raw:: html - --------- Details --------- @@ -30,6 +28,3 @@ Details trait and `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 08b49a9a9..a21b034e8 100644 --- a/courses/comprehensive_rust_training/110_std_types/02_docs.rst +++ b/courses/comprehensive_rust_training/110_std_types/02_docs.rst @@ -46,14 +46,9 @@ To document an item from inside the item (such as inside a module), use //! This module contains functionality relating to divisibility of integers. -.. raw:: html - --------- Details --------- - Show students the generated docs for the :rust:`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 index bfdee318d..a8e584b17 100644 --- a/courses/comprehensive_rust_training/110_std_types/03_option.rst +++ b/courses/comprehensive_rust_training/110_std_types/03_option.rst @@ -23,8 +23,6 @@ returns an :rust:`Option`. assert_eq!(position.expect("Character not found"), 0); } -.. raw:: html - --------- Details --------- @@ -44,6 +42,3 @@ Details a valid value of T. For example, a reference cannot be NULL, so :rust:`Option<&T>` automatically uses NULL to represent the :rust:`None` variant, and thus can be stored in the same memory as :rust:`&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 index 89ab12959..4046cd4fc 100644 --- a/courses/comprehensive_rust_training/110_std_types/04_result.rst +++ b/courses/comprehensive_rust_training/110_std_types/04_result.rst @@ -33,8 +33,6 @@ generic: :rust:`Result` where :rust:`T` is used in the :rust:`Ok` variant } } -.. raw:: html - --------- Details --------- @@ -49,6 +47,3 @@ Details methods and functions that help functional-style programming. - :rust:`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 index 65ca41ef3..c55f5ea31 100644 --- a/courses/comprehensive_rust_training/110_std_types/05_string.rst +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -29,8 +29,6 @@ is a growable UTF-8 encoded string: `Deref `__, which means that you can call all :rust:`str` methods on a :rust:`String`. -.. raw:: html - --------- Details --------- @@ -71,6 +69,3 @@ Details method. This trait is automatically implemented for all types that implement :rust:`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 index f68fccbda..fbdaed864 100644 --- a/courses/comprehensive_rust_training/110_std_types/06_vec.rst +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -37,8 +37,6 @@ standard resizable heap-allocated buffer: `Deref `__, which means that you can call slice methods on a :rust:`Vec`. -.. raw:: html - --------- Details --------- @@ -58,6 +56,3 @@ Details - Slices are covered on day 3. For now, students only need to know that a value of type :rust:`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 index 8b79cd83c..a9a3a16df 100644 --- a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -41,8 +41,6 @@ Standard hash map with protection against HashDoS attacks: println!("{page_counts:#?}"); } -.. raw:: html - --------- Details --------- @@ -86,6 +84,3 @@ Details :rust:`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 :rust:`keys` method. - -.. raw:: html - diff --git a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst index 9b22ab1db..1a256a3dd 100644 --- a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst +++ b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst @@ -60,8 +60,6 @@ operators. :rust:`Ord` is a total ordering, with :rust:`cmp` returning :rust:`Ordering`. -.. raw:: html - --------- Details --------- @@ -83,6 +81,3 @@ cannot, because it is reflexive: 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 index 256c64fcd..53c974308 100644 --- a/courses/comprehensive_rust_training/120_std_traits/02_operators.rst +++ b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst @@ -31,8 +31,6 @@ Operator overloading is implemented via traits in println!("{p1:?} + {p2:?} = {:?}", p1 + p2); } -.. raw:: html - --------- Details --------- @@ -61,6 +59,3 @@ The :rust:`Not` trait (:rust:`!` 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: :rust:`!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 index 7fef0b9c8..818dff8a9 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 @@ -37,8 +37,6 @@ implemented: println!("{s}, {addr}, {one}, {bigger}"); } -.. raw:: html - --------- Details --------- @@ -49,6 +47,3 @@ Details be converted into a :rust:`String`", the rule is opposite, you should use :rust:`Into`. Your function will accept types that implement :rust:`From` and those that *only* implement :rust:`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 index 5a5541468..aaa76e273 100644 --- a/courses/comprehensive_rust_training/120_std_traits/04_casting.rst +++ b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst @@ -34,8 +34,6 @@ For infallible casts (e.g. :rust:`u32` to :rust:`u64`), prefer using :rust:`From fallible casts, :rust:`TryFrom` and :rust:`TryInto` are available when you want to handle casts that fit differently from those that don't. -.. raw:: html - --------- Details --------- @@ -47,6 +45,3 @@ data might be lost is generally discouraged, or at least deserves an explanatory comment. This is common in casting integers to :rust:`usize` for use as an index. - -.. raw:: html - 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 a59eacda0..c9ae152ec 100644 --- a/courses/comprehensive_rust_training/120_std_traits/06_default.rst +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -39,8 +39,6 @@ trait produces a default value for a type. println!("{:#?}", nothing.unwrap_or_default()); } -.. raw:: html - --------- Details --------- @@ -59,6 +57,3 @@ Details :rust:`Default` and provides convenience methods that use it. - The :rust:`..` 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 441c33e8d..8e77d95fb 100644 --- a/courses/comprehensive_rust_training/120_std_traits/07_closures.rst +++ b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst @@ -37,8 +37,6 @@ traits: apply_and_log(multiply_sum, "multiply_sum", 3); } -.. raw:: html - --------- Details --------- @@ -87,6 +85,3 @@ forces capture by value. let hi = make_greeter("Hi".to_string()); hi("Greg"); } - -.. raw:: html - 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 ad97d8b5e..d8928edb0 100644 --- a/courses/comprehensive_rust_training/130_memory_management/01_review.rst +++ b/courses/comprehensive_rust_training/130_memory_management/01_review.rst @@ -48,8 +48,6 @@ dynamically sized data, the actual string, on the heap: : : : : `- - - - - - - - - - - - - -' `- - - - - - - - - - - - - - - -' -.. raw:: html - --------- Details --------- @@ -85,6 +83,3 @@ should point out that this is rightfully unsafe! 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 index 67a2db382..9ddeede66 100644 --- a/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst +++ b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst @@ -30,8 +30,6 @@ Rust offers a new mix: It does this with an explicit ownership concept. -.. raw:: html - --------- Details --------- @@ -61,6 +59,3 @@ 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). - -.. 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 index 7cbe61e8d..976548aff 100644 --- a/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst +++ b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst @@ -9,10 +9,6 @@ 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 struct Point(i32, i32); @@ -31,8 +27,6 @@ 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 --------- @@ -40,6 +34,3 @@ 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 index 938abd984..cd4e23f98 100644 --- a/courses/comprehensive_rust_training/130_memory_management/04_move.rst +++ b/courses/comprehensive_rust_training/130_memory_management/04_move.rst @@ -77,8 +77,6 @@ function parameter. This transfers ownership: // say_hello(name); } -.. raw:: html - --------- Details --------- @@ -190,6 +188,3 @@ Key points: - Unlike Rust, :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 index c70daa943..ca61caddf 100644 --- a/courses/comprehensive_rust_training/130_memory_management/05_clone.rst +++ b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst @@ -21,8 +21,6 @@ accomplishes this. say_hello(name); } -.. raw:: html - --------- Details --------- @@ -40,6 +38,3 @@ Details - The behavior for :rust:`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 index 72bef3e0e..724509a97 100644 --- a/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst +++ b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst @@ -9,10 +9,6 @@ Copy Types While move semantics are the default, certain types are copied by default: -.. raw:: html - - - .. code:: rust fn main() { @@ -26,10 +22,6 @@ These types implement the :rust:`Copy` trait. You can opt-in your own types to use copy semantics: -.. raw:: html - - - .. code:: rust #[derive(Copy, Clone, Debug)] @@ -45,8 +37,6 @@ You can opt-in your own types to use copy semantics: - After the assignment, both :rust:`p1` and :rust:`p2` own their own data. - We can also use :rust:`p1.clone()` to explicitly copy the data. -.. raw:: html - --------- Details --------- @@ -81,6 +71,3 @@ More to Explore 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 index 2ce173cdb..89f84c85c 100644 --- a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst +++ b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst @@ -37,8 +37,6 @@ specify code to run when they go out of scope: println!("Exiting main"); } -.. raw:: html - --------- Details --------- @@ -68,6 +66,3 @@ Discussion points: a stack overflow! - Try replacing :rust:`drop(a)` with :rust:`a.drop()`. - -.. raw:: html - 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 9376026e5..0461ec8e3 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -65,8 +65,6 @@ indirection: : : : : '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' -.. raw:: html - --------- Details --------- @@ -100,6 +98,3 @@ Details empty/null. This makes :rust:`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 index 16418bfba..f07203fda 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -29,8 +29,6 @@ same data from multiple places: `Weak `__ pointer to create cycles that will get dropped. -.. raw:: html - --------- Details --------- @@ -47,6 +45,3 @@ Details - :rust:`Rc::downgrade` gives you a *weakly reference-counted* object to create cycles that will be dropped properly (likely in combination with :rust:`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 index bf645384e..a1c418b07 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 @@ -80,8 +80,6 @@ Memory layout after allocating :rust:`pets`: : : '- - - - - - - - - - - - - - - - - - - - - - -' -.. raw:: html - --------- Details --------- @@ -111,6 +109,3 @@ Details 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/150_borrowing/01_shared.rst b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst index dc837429f..abe9682c1 100644 --- a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst +++ b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst @@ -9,10 +9,6 @@ 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 #[derive(Debug)] @@ -32,8 +28,6 @@ function, you can let a function *borrow* the value: - The :rust:`add` function *borrows* two points and returns a new point. - The caller retains ownership of the inputs. -.. raw:: html - --------- Details --------- @@ -60,10 +54,6 @@ Notes on stack returns and inlining: In the "DEBUG" optimization level, the addresses should change, while they stay the same when changing to the "RELEASE" setting: - .. raw:: html - - - .. code:: rust #[derive(Debug)] @@ -91,6 +81,3 @@ Notes on stack returns and inlining: 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 index 2b0dd20e3..ff1526a51 100644 --- a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst +++ b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst @@ -10,10 +10,6 @@ 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 fn main() { @@ -30,10 +26,6 @@ There's also a second main rule that the borrow checker enforces: The - 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 fn main() { @@ -49,8 +41,6 @@ There's also a second main rule that the borrow checker enforces: The println!("b: {b}"); } -.. raw:: html - --------- Details --------- @@ -78,6 +68,3 @@ Details 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 index 027e33027..a5f0944d3 100644 --- a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst +++ b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst @@ -30,8 +30,6 @@ Similarly, consider the case of iterator invalidation: } } -.. raw:: html - --------- Details --------- @@ -39,6 +37,3 @@ 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 index 16c1e2002..77216e70f 100644 --- a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst +++ b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst @@ -15,8 +15,6 @@ 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 --------- @@ -25,6 +23,3 @@ 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/160_lifetimes/01_lifetime_annotations.rst b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst index 9a5e0ba5d..585645bc0 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst @@ -23,11 +23,6 @@ solution. Lifetimes become more complicated when considering passing values to and returning values from functions. -.. raw:: html - - - .. code:: rust #[derive(Debug)] @@ -48,8 +43,6 @@ returning values from functions. println!("p3: {p3:?}"); } -.. raw:: html - --------- Details --------- @@ -71,6 +64,3 @@ lives for at least :rust:`'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 index 0ba0010a6..9b7807547 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst @@ -46,8 +46,6 @@ is not inference - it is just a syntactic shorthand. println!("{:?}", nearest(points, &Point(0, 2))); } -.. raw:: html - --------- Details --------- @@ -73,6 +71,3 @@ 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 index 8deaa6ed2..c0e4b68d6 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst @@ -27,8 +27,6 @@ lifetime: println!("{dog:?}"); } -.. raw:: html - --------- Details --------- @@ -47,6 +45,3 @@ Details 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 index 0880cad3f..47c14c1fe 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst @@ -78,10 +78,6 @@ those callbacks. What remains for you is to implement the :rust:`parse_field` function and the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. -.. raw:: html - - - .. code:: rust {{#include exercise.rs:preliminaries }} @@ -103,8 +99,6 @@ the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. {{#include exercise.rs:main }} -.. raw:: html - --------- Details --------- @@ -115,6 +109,3 @@ Details with the :rust:`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/01_motivation.rst b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst index 55ea95cb1..1facf9371 100644 --- a/courses/comprehensive_rust_training/170_iterators/01_motivation.rst +++ b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst @@ -26,8 +26,6 @@ In a C-style for loop you declare these things directly: In Rust we bundle this state and logic together into an object known as an "iterator". -.. raw:: html - --------- Details --------- @@ -68,6 +66,3 @@ end. 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 index 320465675..8156fd3c2 100644 --- a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst @@ -41,8 +41,6 @@ elements of a slice it might look something like this: } } -.. raw:: html - --------- Details --------- @@ -73,6 +71,3 @@ More to Explore - You can also demonstrate adding a generic parameter to :rust:`SliceIter` to allow it to work with any kind of slice (not just :rust:`&[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 index 36055d910..5180ef5f8 100644 --- a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst +++ b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst @@ -19,8 +19,6 @@ build customized iterators. println!("The sum of squares of even numbers from 1 to 10 is: {}", result); -.. raw:: html - --------- Details --------- @@ -47,6 +45,3 @@ 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 index 2a5011a27..d398fad34 100644 --- a/courses/comprehensive_rust_training/170_iterators/04_collect.rst +++ b/courses/comprehensive_rust_training/170_iterators/04_collect.rst @@ -19,8 +19,6 @@ method lets you build a collection from an println!("prime_squares: {prime_squares:?}"); } -.. raw:: html - --------- Details --------- @@ -56,6 +54,3 @@ More to Explore - The reason type annotations are often needed with :rust:`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 index ef2154de3..2e98cb53e 100644 --- a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst @@ -57,8 +57,6 @@ by the :rust:`for` loop. } } -.. raw:: html - --------- Details --------- @@ -92,6 +90,3 @@ The same problem can occur for standard library types: iterate over owned elements from that vector. Use :rust:`for e in &some_vector` instead, to iterate over references to elements of :rust:`some_vector`. - -.. raw:: html - diff --git a/courses/comprehensive_rust_training/180_modules/01_modules.rst b/courses/comprehensive_rust_training/180_modules/01_modules.rst index 8e0acda4e..6cef97d19 100644 --- a/courses/comprehensive_rust_training/180_modules/01_modules.rst +++ b/courses/comprehensive_rust_training/180_modules/01_modules.rst @@ -29,8 +29,6 @@ Similarly, :rust:`mod` lets us namespace types and functions: bar::do_something(); } -.. raw:: html - --------- Details --------- @@ -41,6 +39,3 @@ Details 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 index 734559852..f0627aabf 100644 --- a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst +++ b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst @@ -45,8 +45,6 @@ module. todo!() } -.. raw:: html - --------- Details --------- @@ -80,6 +78,3 @@ Details This is useful, for example, if you would like to place tests for a module in a file named :rust:`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 index 3a2ec9606..3d565c962 100644 --- a/courses/comprehensive_rust_training/180_modules/03_visibility.rst +++ b/courses/comprehensive_rust_training/180_modules/03_visibility.rst @@ -40,8 +40,6 @@ Modules are a privacy boundary: outer::public(); } -.. raw:: html - --------- Details --------- @@ -57,6 +55,3 @@ scope of public visibility. - 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 index 3725852b6..6fbfd3821 100644 --- a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst +++ b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst @@ -46,8 +46,6 @@ externally. // println!("Is {} big? {}", foo.val, foo.is_big); } -.. raw:: html - --------- Details --------- @@ -85,6 +83,3 @@ More to Explore - Module privacy still applies when there are :rust:`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 index 365068f91..3a26f2594 100644 --- a/courses/comprehensive_rust_training/180_modules/05_paths.rst +++ b/courses/comprehensive_rust_training/180_modules/05_paths.rst @@ -30,8 +30,6 @@ Paths are resolved as follows: - :rust:`crate::foo` refers to :rust:`foo` in the root of the current crate, - :rust:`bar::foo` refers to :rust:`foo` in the :rust:`bar` crate. -.. raw:: html - --------- Details --------- @@ -58,6 +56,3 @@ Details - The :rust:`use` statement can have a wildcard: :rust:`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 index 78e9e1b94..b7727aa8a 100644 --- a/courses/comprehensive_rust_training/180_modules/06_exercise.rst +++ b/courses/comprehensive_rust_training/180_modules/06_exercise.rst @@ -39,8 +39,6 @@ Here's the single-module implementation of the GUI library: {{#include exercise.rs:single-module}} -.. raw:: html - --------- Details --------- @@ -48,6 +46,3 @@ Details Encourage students to divide the code in a way that feels natural for them, and get accustomed to the required :rust:`mod`, :rust:`use`, and :rust:`pub` declarations. Afterward, discuss what organizations are most idiomatic. - -.. raw:: html - diff --git a/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst index 340e87592..742279e56 100644 --- a/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst +++ b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst @@ -44,13 +44,8 @@ tests. - The :rust:`#[cfg(test)]` attribute is only active when you run :rust:`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/03_lints.rst b/courses/comprehensive_rust_training/190_testing/03_lints.rst index aa08344c6..6955b590d 100644 --- a/courses/comprehensive_rust_training/190_testing/03_lints.rst +++ b/courses/comprehensive_rust_training/190_testing/03_lints.rst @@ -21,8 +21,6 @@ even more lints, organized into groups that can be enabled per-project. println!("X probably fits in a u16, right? {}", x as u16); } -.. raw:: html - --------- Details --------- @@ -34,6 +32,3 @@ default-deny lints) all the time. Note that errors or warnings with :rust:`help: ...` can be fixed with :rust:`cargo fix` or via your editor. - -.. raw:: html - diff --git a/courses/comprehensive_rust_training/200_error_handling/01_panics.rst b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst index 3164ade61..a1d0dc7d1 100644 --- a/courses/comprehensive_rust_training/200_error_handling/01_panics.rst +++ b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst @@ -29,8 +29,6 @@ Rust will trigger a panic if a fatal error happens at runtime: - Use non-panicking APIs (such as :rust:`Vec::get`) if crashing is not acceptable. -.. raw:: html - --------- Details --------- @@ -58,6 +56,3 @@ caught: single request crashes. - This does not work if :rust:`panic = 'abort'` is set in your :rust:`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 index 8a6bf907f..e6526b109 100644 --- a/courses/comprehensive_rust_training/200_error_handling/02_result.rst +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -32,8 +32,6 @@ enum, which we briefly saw when discussing standard library types. } } -.. raw:: html - --------- Details --------- @@ -91,6 +89,3 @@ Error Numbers - 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 index 81c16c7b0..615c73710 100644 --- a/courses/comprehensive_rust_training/200_error_handling/03_try.rst +++ b/courses/comprehensive_rust_training/200_error_handling/03_try.rst @@ -51,8 +51,6 @@ We can use this to simplify our error handling code: println!("username or error: {username:?}"); } -.. raw:: html - --------- Details --------- @@ -69,6 +67,3 @@ Key points: implements :rust:`std::process::Termination`. In practice, this means that :rust:`E` implements :rust:`Debug`. The executable will print the :rust:`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 index 2b8dc3bbf..cdba7b705 100644 --- a/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst +++ b/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst @@ -74,8 +74,6 @@ Example println!("username or error: {username:?}"); } -.. raw:: html - --------- Details --------- @@ -98,6 +96,3 @@ arbitrary :rust:`T` and :rust:`U` types. A function that returns :rust:`Result` cannot use :rust:`?` on :rust:`Option` and vice versa. However, :rust:`Option::ok_or` converts :rust:`Option` to :rust:`Result` whereas :rust:`Result::ok` turns :rust:`Result` into :rust:`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 index 334051b20..7efb00f66 100644 --- a/courses/comprehensive_rust_training/200_error_handling/05_error.rst +++ b/courses/comprehensive_rust_training/200_error_handling/05_error.rst @@ -32,8 +32,6 @@ can contain any error. } } -.. raw:: html - --------- Details --------- @@ -49,6 +47,3 @@ display the error message somewhere. Make sure to implement the :rust:`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 index d415ab728..2442edd57 100644 --- a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -42,8 +42,6 @@ macros that assist in implementing :rust:`From`, :rust:`Display`, and the } } -.. raw:: html - --------- Details --------- @@ -55,6 +53,3 @@ Details - Note that the (:rust:`thiserror::`)\ :rust:`Error` derive macro, while it has the effect of implementing the (:rust:`std::error::`)\ :rust:`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 index 415c78c2b..bb1535578 100644 --- a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -46,8 +46,6 @@ trait impls explicitly for custom error types. } } -.. raw:: html - --------- Details --------- @@ -78,6 +76,3 @@ More to Explore :rust:`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 index 36ef2c0d5..00b0abac8 100644 --- a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst +++ b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst @@ -25,6 +25,3 @@ for :rust:`eval`. 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/01_unsafe.rst b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst index f90f05c7a..4c8b58885 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst @@ -31,8 +31,6 @@ see `Chapter 19.1 in the Rust Book `__ and the `Rustonomicon `__. -.. raw:: html - --------- Details --------- @@ -41,6 +39,3 @@ 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 index 43cf8c156..5958a9f45 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst @@ -35,8 +35,6 @@ Creating pointers is safe, but dereferencing them requires :rust:`unsafe`: */ } -.. raw:: html - --------- Details --------- @@ -65,6 +63,3 @@ The "NOT SAFE" section gives an example of a common kind of UB bug: :rust:`*r1` has the :rust:`'static` lifetime, so :rust:`r3` has type :rust:`&'static String`, and thus outlives :rust:`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 index 1688e1246..5b7f2376f 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst @@ -39,8 +39,6 @@ mutable static variables: } } -.. raw:: html - --------- Details --------- @@ -53,6 +51,3 @@ Details - Using a mutable static is generally a bad idea, but there are some cases where it might make sense in low-level :rust:`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 index a1a9c0e27..b18d3de2a 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst @@ -22,8 +22,6 @@ Unions are like enums, but you need to track the active field yourself: println!("bool: {}", unsafe { u.b }); // Undefined behavior! } -.. raw:: html - --------- Details --------- @@ -36,6 +34,3 @@ 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 index 6d948d793..8b397c416 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 @@ -80,8 +80,6 @@ conditions to avoid undefined behaviour. println!("a = {}, b = {}", a, b); } -.. raw:: html - --------- Details --------- @@ -116,6 +114,3 @@ Note that unsafe code is allowed within an unsafe function without an :rust:`unsafe` block. We can prohibit this with :rust:`#[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 index c6d038ac1..75e90d129 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 @@ -31,8 +31,6 @@ For example, the :rust:`zerocopy` crate has an unsafe trait that looks // SAFETY: `u32` has a defined representation and no padding. unsafe impl IntoBytes for u32 {} -.. raw:: html - --------- Details --------- @@ -44,6 +42,3 @@ The actual safety section for :rust:`IntoBytes` is rather longer and more complicated. The built-in :rust:`Send` and :rust:`Sync` traits are unsafe. - -.. raw:: html - From 5cdb5595e8745a42952b4f3325dacc9a2e689030 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:56:16 -0500 Subject: [PATCH 03/55] Fix rust role in module/slide titles --- .../040_control_flow_basics/01_if.rst | 4 ++-- .../040_control_flow_basics/02_match.rst | 4 ++-- .../040_control_flow_basics/03_loops.rst | 2 +- .../040_control_flow_basics/04_break_continue.rst | 4 ++-- .../070_user_defined_types/05_const.rst | 4 ++-- .../070_user_defined_types/06_static.rst | 4 ++-- .../080_pattern_matching/04_let_control_flow.rst | 8 ++++---- .../100_generics/05_impl_trait.rst | 4 ++-- .../100_generics/06_dyn_trait.rst | 4 ++-- .../comprehensive_rust_training/110_std_types/06_vec.rst | 4 ++-- .../110_std_types/07_hashmap.rst | 4 ++-- .../120_std_traits/01_comparisons.rst | 4 ++-- .../120_std_traits/03_from_and_into.rst | 4 ++-- .../120_std_traits/05_read_and_write.rst | 4 ++-- .../140_smart_pointers/01_box.rst | 4 ++-- .../140_smart_pointers/02_rc.rst | 4 ++-- .../170_iterators/02_iterator.rst | 4 ++-- .../170_iterators/03_helpers.rst | 4 ++-- .../170_iterators/04_collect.rst | 4 ++-- .../170_iterators/05_intoiterator.rst | 4 ++-- .../200_error_handling/02_result.rst | 4 ++-- .../200_error_handling/06_thiserror.rst | 4 ++-- .../200_error_handling/07_anyhow.rst | 4 ++-- 23 files changed, 47 insertions(+), 47 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 ea5d9637d..25677defc 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 @@ -1,9 +1,9 @@ ==================== -:rust:`if` expressions +"if" expressions ==================== -------------------- -:rust:`if` expressions +"if" expressions -------------------- You use 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 index 5f31ff379..2846f0a82 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst @@ -1,9 +1,9 @@ ======================= -:rust:`match` Expressions +"match" Expressions ======================= ----------------------- -:rust:`match` Expressions +"match" Expressions ----------------------- :rust:`match` can be used to check a value against one or more options: 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 3aea2fd16..3a5baef45 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 @@ -10,7 +10,7 @@ There are three looping keywords in Rust: :rust:`while`, :rust:`loop`, and :rust:`for`: ----------- -:rust:`while` +"while" ----------- The 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 7f2d53816..09308020c 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 @@ -1,9 +1,9 @@ ============================ -:rust:`break` and :rust:`continue` +"break" and "continue" ============================ ---------------------------- -:rust:`break` and :rust:`continue` +"break" and "continue" ---------------------------- If you want to immediately start the next iteration use 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 d4d143949..6b516a0a2 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 @@ -1,9 +1,9 @@ =========== -:rust:`const` +"const" =========== ----------- -:rust:`const` +"const" ----------- Constants are evaluated at compile time and their values are inlined 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 137916adb..7275548b4 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 @@ -1,9 +1,9 @@ ============ -:rust:`static` +"static" ============ ------------ -:rust:`static` +"static" ------------ Static variables will live during the whole execution of the program, 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 51b8d2ff5..8471459ea 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 @@ -14,11 +14,11 @@ languages. They are used for pattern matching: - :rust:`while let` expressions ======================== -:rust:`if let` expressions +"if let" expressions ======================== ------------------------ -:rust:`if let` expressions +"if let" expressions ------------------------ The @@ -43,11 +43,11 @@ pattern: } ========================== -:rust:`let else` expressions +"let else" expressions ========================== -------------------------- -:rust:`let else` expressions +"let else" expressions -------------------------- For the common case of matching a pattern and returning from the diff --git a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst index 13f314608..ad19c6c54 100644 --- a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst @@ -1,9 +1,9 @@ ================ -:rust:`impl Trait` +"impl Trait" ================ ---------------- -:rust:`impl Trait` +"impl Trait" ---------------- Similar to trait bounds, an :rust:`impl Trait` syntax can be used in 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 e9da311c1..b12fe8402 100644 --- a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst @@ -1,9 +1,9 @@ =============== -:rust:`dyn Trait` +"dyn Trait" =============== --------------- -:rust:`dyn Trait` +"dyn Trait" --------------- In addition to using traits for static dispatch via generics, Rust also 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 fbdaed864..53423bbfd 100644 --- a/courses/comprehensive_rust_training/110_std_types/06_vec.rst +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -1,9 +1,9 @@ ========= -:rust:`Vec` +"Vec" ========= --------- -:rust:`Vec` +"Vec" --------- `Vec `__ is the 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 a9a3a16df..3cf77dba9 100644 --- a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -1,9 +1,9 @@ ============= -:rust:`HashMap` +"HashMap" ============= ------------- -:rust:`HashMap` +"HashMap" ------------- Standard hash map with protection against HashDoS attacks: diff --git a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst index 1a256a3dd..0354ac9c0 100644 --- a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst +++ b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst @@ -10,7 +10,7 @@ These traits support comparisons between values. All traits can be derived for types containing fields that implement these traits. -------------------------- -:rust:`PartialEq` and :rust:`Eq` +"PartialEq" and "Eq" -------------------------- :rust:`PartialEq` is a partial equivalence relation, with required method @@ -34,7 +34,7 @@ transitive) and implies :rust:`PartialEq`. Functions that require full equivalence will use :rust:`Eq` as a trait bound. ---------------------------- -:rust:`PartialOrd` and :rust:`Ord` +"PartialOrd" and "Ord" ---------------------------- :rust:`PartialOrd` defines a partial ordering, with a :rust:`partial_cmp` 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 818dff8a9..f11728265 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 @@ -1,9 +1,9 @@ ======================= -:rust:`From` and :rust:`Into` +"From" and "Into" ======================= ----------------------- -:rust:`From` and :rust:`Into` +"From" and "Into" ----------------------- Types implement 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 6cefe66d2..c39d2b4ca 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 @@ -1,9 +1,9 @@ ======================== -:rust:`Read` and :rust:`Write` +"Read" and "Write" ======================== ------------------------ -:rust:`Read` and :rust:`Write` +"Read" and "Write" ------------------------ Using `Read `__ 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 0461ec8e3..6fbc7740d 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -1,9 +1,9 @@ ============ -:rust:`Box` +"Box" ============ ------------ -:rust:`Box` +"Box" ------------ `Box `__ is an 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 f07203fda..162fe6c48 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -1,9 +1,9 @@ ======== -:rust:`Rc` +"Rc" ======== -------- -:rust:`Rc` +"Rc" -------- `Rc `__ is a diff --git a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst index 8156fd3c2..d2020da18 100644 --- a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst @@ -1,9 +1,9 @@ ==================== -:rust:`Iterator` Trait +"Iterator" Trait ==================== -------------------- -:rust:`Iterator` Trait +"Iterator" Trait -------------------- The diff --git a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst index 5180ef5f8..b57fe1c1b 100644 --- a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst +++ b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst @@ -1,9 +1,9 @@ ============================= -:rust:`Iterator` Helper Methods +"Iterator" Helper Methods ============================= ----------------------------- -:rust:`Iterator` Helper Methods +"Iterator" Helper Methods ----------------------------- In addition to the :rust:`next` method that defines how an iterator behaves, diff --git a/courses/comprehensive_rust_training/170_iterators/04_collect.rst b/courses/comprehensive_rust_training/170_iterators/04_collect.rst index d398fad34..2c7f26747 100644 --- a/courses/comprehensive_rust_training/170_iterators/04_collect.rst +++ b/courses/comprehensive_rust_training/170_iterators/04_collect.rst @@ -1,9 +1,9 @@ ============= -:rust:`collect` +"collect" ============= ------------- -:rust:`collect` +"collect" ------------- The diff --git a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst index 2e98cb53e..15838928f 100644 --- a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst @@ -1,9 +1,9 @@ ================== -:rust:`IntoIterator` +"IntoIterator" ================== ------------------ -:rust:`IntoIterator` +"IntoIterator" ------------------ The :rust:`Iterator` trait tells you how to *iterate* once you have created 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 e6526b109..b7ee3428c 100644 --- a/courses/comprehensive_rust_training/200_error_handling/02_result.rst +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -1,9 +1,9 @@ ============ -:rust:`Result` +"Result" ============ ------------ -:rust:`Result` +"Result" ------------ Our primary mechanism for error handling in Rust is the 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 2442edd57..57faa3115 100644 --- a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -1,9 +1,9 @@ =============== -:rust:`thiserror` +"thiserror" =============== --------------- -:rust:`thiserror` +"thiserror" --------------- The `thiserror `__ crate provides macros 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 bb1535578..8e947656d 100644 --- a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -1,9 +1,9 @@ ============ -:rust:`anyhow` +"anyhow" ============ ------------ -:rust:`anyhow` +"anyhow" ------------ The `anyhow `__ crate provides a rich error From 57ec8af7d6d74ee659e23ba32b70e58fb6165305 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:01:59 -0500 Subject: [PATCH 04/55] Fix table --- .../030_types_and_values/03_values.rst | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 3c41227b9..1a865d0ac 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 @@ -12,22 +12,22 @@ each type. +---------------+-------------------------------+---------------------+ | | Types | Literals | +===============+===============================+=====================+ -| Signed | :rust:`i8`, :rust:`i16`, :rust:`i32`, | :rust:`-10`, :rust:`0`, | -| integers | :rust:`i64`, :rust:`i128`, :rust:`isize` | :rust:`1_000`, | -| | | :rust:`123_i64` | +| Signed | ``i8``, ``i16``, ``i32``, | ``-10``, ``0``, | +| integers | ``i64``, ``i128``, ``isize`` | ``1_000``, | +| | | ``123_i64`` | +---------------+-------------------------------+---------------------+ -| Unsigned | :rust:`u8`, :rust:`u16`, :rust:`u32`, | :rust:`0`, :rust:`123`, | -| integers | :rust:`u64`, :rust:`u128`, :rust:`usize` | :rust:`10_u16` | +| Unsigned | ``u8``, ``u16``, ``u32``, | ``0``, ``123``, | +| integers | ``u64``, ``u128``, ``usize`` | ``10_u16`` | +---------------+-------------------------------+---------------------+ -| Floating | :rust:`f32`, :rust:`f64` | :rust:`3.14`, | -| point numbers | | :rust:`-10.0e20`, | -| | | :rust:`2_f32` | +| Floating | ``f32``, ``f64`` | ``3.14``, | +| point numbers | | ``-10.0e20``, | +| | | ``2_f32`` | +---------------+-------------------------------+---------------------+ -| Unicode | :rust:`char` | :rust:`'a'`, | +| Unicode | ``char`` | ``'a'``, | | scalar values | | ':math:`\alpha`', | | | | ':math:`\infty`' | +---------------+-------------------------------+---------------------+ -| Booleans | :rust:`bool` | :rust:`true`, :rust:`false` | +| Booleans | ``bool`` | ``true``, ``false`` | +---------------+-------------------------------+---------------------+ The types have widths as follows: From 58ffdc069290fc4a10f84002cdb3bb0caa933c22 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:04:41 -0500 Subject: [PATCH 05/55] Fixe more titles --- .../comprehensive_rust_training/100_generics/07_exercise.rst | 4 ++-- .../comprehensive_rust_training/120_std_traits/06_default.rst | 4 ++-- .../130_memory_management/07_drop.rst | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/courses/comprehensive_rust_training/100_generics/07_exercise.rst b/courses/comprehensive_rust_training/100_generics/07_exercise.rst index cf22b0a7a..4febe5162 100644 --- a/courses/comprehensive_rust_training/100_generics/07_exercise.rst +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -1,9 +1,9 @@ =========================== -Exercise: Generic :rust:`min` +Exercise: Generic "min" =========================== --------------------------- -Exercise: Generic :rust:`min` +Exercise: Generic "min" --------------------------- In this short exercise, you will implement a generic :rust:`min` function 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 c9ae152ec..5c9cffed4 100644 --- a/courses/comprehensive_rust_training/120_std_traits/06_default.rst +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -1,9 +1,9 @@ ======================= -The :rust:`Default` Trait +The "Default" Trait ======================= ----------------------- -The :rust:`Default` Trait +The "Default" Trait ----------------------- `Default `__ 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 89f84c85c..40e58fc60 100644 --- a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst +++ b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst @@ -1,9 +1,9 @@ ==================== -The :rust:`Drop` Trait +The "Drop" Trait ==================== -------------------- -The :rust:`Drop` Trait +The "Drop" Trait -------------------- Values which implement From 85f97e6f5634b8137732ce101785ad8c1771a1c9 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:40:53 -0500 Subject: [PATCH 06/55] Add missing sub-modules --- .../040_control_flow_basics/03_loops.rst | 57 ++++++++ .../04_break_continue.rst | 38 +++++ .../04_let_control_flow.rst | 16 -- .../090_methods_and_traits/02_traits.rst | 137 +++++++++++++++++- .../150_borrowing/04_interior_mutability.rst | 88 +++++++++++ .../210_unsafe_rust/05_unsafe_functions.rst | 4 - 6 files changed, 317 insertions(+), 23 deletions(-) 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 3a5baef45..4d5513434 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 @@ -27,3 +27,60 @@ the condition is true. } println!("Final x: {x}"); } + +------------- +"for" +------------- + +The :rust:`for` `loop `__ +iterates over ranges of values or the items in a collection: + +.. code:: rust + + fn main() { + for x in 1..5 { + println!("x: {x}"); + } + + for elem in [1, 2, 3, 4, 5] { + println!("elem: {elem}"); + } + } + +--------------- +"for" Details +--------------- + +- Under the hood :rust:`for` loops use a concept called ?iterators? to + handle iterating over different kinds of ranges/collections. + Iterators will be discussed in more detail later. +- Note that the first :rust:`for` loop only iterates to :rust:`4`. Show the + :rust:`1..=5` syntax for an inclusive range. + +-------------- +"loop" +-------------- + +The :rust:`loop` +`statement `__ just +loops forever, until a :rust:`break`. + +.. code:: rust + + fn main() { + let mut i = 0; + loop { + i += 1; + println!("{i}"); + if i > 100 { + break; + } + } + } + +---------------- +"loop" Details +---------------- + +- The :rust:`loop` statement works like a :rust:`while true` loop. Use it for + things like servers which will serve connections forever. 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 09308020c..adebca184 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 @@ -38,3 +38,41 @@ Note that :rust:`loop` is the only looping construct which can return a non-trivial value. This is because it's guaranteed to only return at a :rust:`break` statement (unlike :rust:`while` and :rust:`for` loops, which can also return when the condition fails). + +-------- +Labels +-------- + +Both :rust:`continue` and :rust:`break` can optionally take a label argument +which is used to break out of nested loops: + +.. code:: rust + + fn main() { + let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]]; + let mut elements_searched = 0; + let target_value = 10; + 'outer: for i in 0..=2 { + for j in 0..=2 { + elements_searched += 1; + if s[i][j] == target_value { + break 'outer; + } + } + } + print!("elements searched: {elements_searched}"); + } + +--------- +Details +--------- + +- Labeled break also works on arbitrary blocks, e.g. + + .. code:: rust + + 'label: { + break 'label; + println!("This line gets skipped"); + } + 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 8471459ea..93c5bb92b 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 @@ -13,10 +13,6 @@ languages. They are used for pattern matching: - :rust:`let else` expressions - :rust:`while let` expressions -======================== -"if let" expressions -======================== - ------------------------ "if let" expressions ------------------------ @@ -42,10 +38,6 @@ pattern: sleep_for(0.8); } -========================== -"let else" expressions -========================== - -------------------------- "let else" expressions -------------------------- @@ -99,10 +91,6 @@ returns :rust:`Some(c)` until the string is empty, after which it will return :rust:`None`. The :rust:`while let` lets us keep iterating through all items. ---------- -Details ---------- - -------- if-let -------- @@ -142,10 +130,6 @@ The rewritten version is: return Ok(digit); } -=========== -while-let -=========== - ----------- while-let ----------- 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 index e677fe279..ac09b597e 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst @@ -19,12 +19,143 @@ interfaces: fn greet(&self); } ---------- -Details ---------- +---------------- +Traits 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. + +--------------------- +Implementing Traits +--------------------- + +.. code:: rust + + trait Pet { + fn talk(&self) -> String; + + fn greet(&self) { + println!("Oh you're a cutie! What's your name? {}", self.talk()); + } + } + + struct Dog { + name: String, + age: i8, + } + + impl Pet for Dog { + fn talk(&self) -> String { + format!("Woof, my name is {}!", self.name) + } + } + + fn main() { + let fido = Dog { name: String::from("Fido"), age: 5 }; + fido.greet(); + } + +---------------------- +Implementing Details +---------------------- + +- To implement :rust:`Trait` for :rust:`Type`, you use an + :rust:`impl Trait for Type { .. }` block. + +- Unlike Go interfaces, just having matching methods is not enough: a + :rust:`Cat` type with a :rust:`talk()` method would not automatically satisfy + :rust:`Pet` unless it is in an :rust:`impl Pet` block. + +- Traits may provide default implementations of some methods. Default + implementations can rely on all the methods of the trait. In this + case, :rust:`greet` is provided, and relies on :rust:`talk`. + +------------- +Supertraits +------------- + +A trait can require that types implementing it also implement other +traits, called *supertraits*. Here, any type implementing :rust:`Pet` must +implement :rust:`Animal`. + +.. code:: rust + + trait Animal { + fn leg_count(&self) -> u32; + } + + trait Pet: Animal { + fn name(&self) -> String; + } + + struct Dog(String); + + impl Animal for Dog { + fn leg_count(&self) -> u32 { + 4 + } + } + + impl Pet for Dog { + fn name(&self) -> String { + self.0.clone() + } + } + + fn main() { + let puppy = Dog(String::from("Rex")); + println!("{} has {} legs", puppy.name(), puppy.leg_count()); + } + +--------------------- +Supertraits Details +--------------------- + +This is sometimes called *trait inheritance* but students should not +expect this to behave like OO inheritance. It just specifies an +additional requirement on implementations of a trait. + +------------------ +Associated Types +------------------ + +Associated types are placeholder types which are supplied by the trait +implementation. + +.. code:: rust + + #[derive(Debug)] + struct Meters(i32); + #[derive(Debug)] + struct MetersSquared(i32); + + trait Multiply { + type Output; + fn multiply(&self, other: &Self) -> Self::Output; + } + + impl Multiply for Meters { + type Output = MetersSquared; + fn multiply(&self, other: &Self) -> Self::Output { + MetersSquared(self.0 * other.0) + } + } + + fn main() { + println!("{:?}", Meters(10).multiply(&Meters(20))); + } + +--------- +Details +--------- + +- Associated types are sometimes also called *output types*. The key + observation is that the implementer, not the caller, chooses this + type. + +- Many standard library traits have associated types, including + arithmetic operators and :rust:`Iterator`. diff --git a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst index 77216e70f..3d32ca8a7 100644 --- a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst +++ b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst @@ -23,3 +23,91 @@ 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. + +-------------- +"Cell" +-------------- + +:rust:`Cell` wraps a value and allows getting or setting the value using +only a shared reference to the :rust:`Cell`. However, it does not allow any +references to the inner value. Since there are no references, borrowing +rules cannot be broken. + +.. code:: rust + + use std::cell::Cell; + + fn main() { + // Note that `cell` is NOT declared as mutable. + let cell = Cell::new(5); + + cell.set(123); + println!("{}", cell.get()); + } + +---------------- +"Cell" Details +---------------- + +- :rust:`Cell` is a simple means to ensure safety: it has a :rust:`set` method + that takes :rust:`&self`. This needs no runtime check, but requires + moving values, which can have its own cost. + +----------------- +"RefCell" +----------------- + +:rust:`RefCell` allows accessing and mutating a wrapped value by providing +alternative types :rust:`Ref` and :rust:`RefMut` that emulate :rust:`&T`/:rust:`&mut T` +without actually being Rust references. + +These types perform dynamic checks using a counter in the :rust:`RefCell` to +prevent existence of a :rust:`RefMut` alongside another :rust:`Ref`/:rust:`RefMut`. + +By implementing :rust:`Deref` (and :rust:`DerefMut` for :rust:`RefMut`), these types +allow calling methods on the inner value without allowing references to +escape. + +.. code:: rust + + use std::cell::RefCell; + + fn main() { + // Note that `cell` is NOT declared as mutable. + let cell = RefCell::new(5); + + { + let mut cell_ref = cell.borrow_mut(); + *cell_ref = 123; + + // This triggers an error at runtime. + // let other = cell.borrow(); + // println!("{}", *other); + } + + println!("{cell:?}"); + } + +.. raw:: html + +------------------- +"RefCell" Details +------------------- + +- :rust:`RefCell` enforces Rust?s usual borrowing rules (either multiple + shared references or a single exclusive reference) with a runtime + check. In this case, all borrows are very short and never overlap, so + the checks always succeed. + +- The extra block in the example is to end the borrow created by the + call to :rust:`borrow_mut` before we print the cell. Trying to print a + borrowed :rust:`RefCell` just shows the message :rust:`"{borrowed}"`. + +----------------- +More to Explore +----------------- + +There are also :rust:`OnceCell` and :rust:`OnceLock`, which allow initialization +on first use. Making these useful requires some more knowledge than +students have at this time. + 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 8b397c416..6e67211af 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 @@ -2,10 +2,6 @@ Unsafe Functions ================== ------------------- -Unsafe Functions ------------------- - -------------------------- Calling Unsafe Functions -------------------------- From d5c8441f786d34efc4acc085b197f8703cc82879 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:08:09 -0500 Subject: [PATCH 07/55] Fix formatting issues --- .../040_control_flow_basics/03_loops.rst | 2 +- .../040_control_flow_basics/05_blocks_and_scopes.rst | 4 ---- .../070_user_defined_types/06_static.rst | 4 ---- .../080_pattern_matching/01_match.rst | 4 ---- .../090_methods_and_traits/01_methods.rst | 2 +- .../120_std_traits/06_default.rst | 2 +- .../130_memory_management/04_move.rst | 8 -------- .../130_memory_management/06_copy_types.rst | 4 ---- .../150_borrowing/01_shared.rst | 4 ---- .../150_borrowing/04_interior_mutability.rst | 2 +- .../200_error_handling/02_result.rst | 4 ---- .../200_error_handling/07_anyhow.rst | 4 ---- .../210_unsafe_rust/05_unsafe_functions.rst | 6 ------ 13 files changed, 4 insertions(+), 46 deletions(-) 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 4d5513434..bc6c872ea 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 @@ -51,7 +51,7 @@ iterates over ranges of values or the items in a collection: "for" Details --------------- -- Under the hood :rust:`for` loops use a concept called ?iterators? to +- Under the hood :rust:`for` loops use a concept called :dfn:`iterators` to handle iterating over different kinds of ranges/collections. Iterators will be discussed in more detail later. - Note that the first :rust:`for` loop only iterates to :rust:`4`. Show the 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 index f5450196d..96923496d 100644 --- 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 @@ -2,10 +2,6 @@ Blocks and Scopes =================== -------------------- -Blocks and Scopes -------------------- - -------- Blocks -------- 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 7275548b4..3c12488eb 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 @@ -33,10 +33,6 @@ Details - :rust:`static` provides object identity: an address in memory and state as required by types with interior mutability such as :rust:`Mutex`. -================= -More to Explore -================= - ----------------- More to Explore ----------------- diff --git a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst index 48dbd6e69..c146670be 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst @@ -57,10 +57,6 @@ Key Points: - The condition defined in the guard applies to every expression in a pattern with an :rust:`|`. -================= -More To Explore -================= - ----------------- More To Explore ----------------- 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 6c6e7e9ad..d32de0718 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 @@ -97,5 +97,5 @@ Key Points: - This might be a good time to demonstrate how the :rust:`&self` differs from :rust:`self` by trying to run :rust:`finish` twice. - Beyond variants on :rust:`self`, there are also - `special wrapper types `__ + `special wrapper types `__ allowed to be receiver types, such as :rust:`Box`. 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 5c9cffed4..520647705 100644 --- a/courses/comprehensive_rust_training/120_std_traits/06_default.rst +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -56,4 +56,4 @@ Details - The Rust standard library is aware that types can implement :rust:`Default` and provides convenience methods that use it. - The :rust:`..` syntax is called - `struct update syntax `__. + `struct update syntax `__. 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 cd4e23f98..8e20b7a5a 100644 --- a/courses/comprehensive_rust_training/130_memory_management/04_move.rst +++ b/courses/comprehensive_rust_training/130_memory_management/04_move.rst @@ -109,14 +109,6 @@ In the :rust:`say_hello` example: making move semantics the default, and by forcing programmers to make clones explicit. -================= -More to Explore -================= - ------------------ -More to Explore ------------------ - -------------------------------- Defensive Copies in Modern C++ -------------------------------- 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 index 724509a97..18c45f14e 100644 --- a/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst +++ b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst @@ -59,10 +59,6 @@ In the above example, try the following: now in the :rust:`println!` for :rust:`p1`. - Show that it works if you clone :rust:`p1` instead. -================= -More to Explore -================= - ----------------- More to Explore ----------------- diff --git a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst index abe9682c1..ecff35252 100644 --- a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst +++ b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst @@ -35,10 +35,6 @@ 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 ----------------- diff --git a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst index 3d32ca8a7..07a237814 100644 --- a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst +++ b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst @@ -94,7 +94,7 @@ escape. "RefCell" Details ------------------- -- :rust:`RefCell` enforces Rust?s usual borrowing rules (either multiple +- :rust:`RefCell` enforces Rust's usual borrowing rules (either multiple shared references or a single exclusive reference) with a runtime check. In this case, all borrows are very short and never overlap, so the checks always succeed. 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 b7ee3428c..6e85049e1 100644 --- a/courses/comprehensive_rust_training/200_error_handling/02_result.rst +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -51,10 +51,6 @@ Details always see in your source code where proper error handling is being skipped. -================= -More to Explore -================= - ----------------- More to Explore ----------------- 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 8e947656d..d4e1c8f26 100644 --- a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -64,10 +64,6 @@ Details :rust:`Result` and :rust:`Option` types. :rust:`use anyhow::Context` is necessary to enable :rust:`.context()` and :rust:`.with_context()` on those types. -================= -More to Explore -================= - ----------------- More to Explore ----------------- 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 6e67211af..953c045b4 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 @@ -76,12 +76,6 @@ conditions to avoid undefined behaviour. println!("a = {}, b = {}", a, b); } ---------- -Details ---------- - -.. _calling-unsafe-functions-1: - -------------------------- Calling Unsafe Functions -------------------------- From 2283e9c10bf72494051540a45587eb04730257d4 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:35:41 -0500 Subject: [PATCH 08/55] Fix build errors --- .../140_smart_pointers/04_exercise.rst | 6 +++--- .../200_error_handling/08_exercise.rst | 6 +++--- .../210_unsafe_rust/07_exercise.rst | 12 +++++------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst index 1b3934b7b..83a9778bc 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst @@ -19,8 +19,8 @@ values in order. .. code:: rust - {{#include exercise.rs:types}} + include exercise.rs:types - // Implement `new`, `insert`, `len`, and `has` for `Subtree`. + Implement new, insert, len, and has for Subtree. - {{#include exercise.rs:tests}} + include exercise.rs:tests diff --git a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst index 00b0abac8..8a93881d0 100644 --- a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst +++ b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst @@ -15,11 +15,11 @@ for :rust:`eval`. .. code:: rust - {{#include exercise.rs:types}} + #include exercise.rs:types - {{#include exercise.rs:eval}} + #include exercise.rs:eval - {{#include exercise.rs:tests}} + #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 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 69b9082e4..233bf5e67 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst @@ -67,21 +67,19 @@ missing functions and methods: // TODO: remove this when you're done with your implementation. #![allow(unused_imports, unused_variables, dead_code)] - {{#include exercise.rs:ffi}} + #include exercise.rs:ffi}} - {{#include exercise.rs:DirectoryIterator}} + #include exercise.rs:DirectoryIterator}} unimplemented!() - } - } - {{#include exercise.rs:Iterator}} + #include exercise.rs:Iterator unimplemented!() } } - {{#include exercise.rs:Drop}} + #include exercise.rs:Drop unimplemented!() } } - {{#include exercise.rs:main}} + #include exercise.rs:main From 3f08997cfe99a71a9da79a51705610d912ad40ec Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Fri, 7 Feb 2025 18:29:17 -0500 Subject: [PATCH 09/55] Trying to fix build failures --- .../030_types_and_values/06_exercise.rst | 2 +- .../040_control_flow_basics/08_exercise.rst | 2 +- .../050_tuples_and_arrays/05_exercise.rst | 2 +- .../060_references/06_exercise.rst | 2 +- .../070_user_defined_types/07_exercise.rst | 2 +- .../080_pattern_matching/05_exercise.rst | 4 ++-- .../090_methods_and_traits/04_exercise.rst | 2 +- .../comprehensive_rust_training/100_generics/07_exercise.rst | 2 +- .../comprehensive_rust_training/110_std_types/08_exercise.rst | 2 +- .../120_std_traits/08_exercise.rst | 2 +- .../130_memory_management/08_exercise.rst | 2 +- .../140_smart_pointers/04_exercise.rst | 2 +- .../comprehensive_rust_training/150_borrowing/05_exercise.rst | 2 +- .../comprehensive_rust_training/160_lifetimes/04_exercise.rst | 2 +- .../comprehensive_rust_training/170_iterators/06_exercise.rst | 2 +- .../comprehensive_rust_training/180_modules/06_exercise.rst | 2 +- .../comprehensive_rust_training/190_testing/04_exercise.rst | 2 +- .../200_error_handling/08_exercise.rst | 2 +- .../210_unsafe_rust/07_exercise.rst | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) 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 index 64669332e..6db2faa8a 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst @@ -13,7 +13,7 @@ n-2'th Fibonacci numbers. Write a function :rust:`fib(n)` that calculates the n'th Fibonacci number. When will this function panic? -.. code:: rust +:: {{#include exercise.rs:fib}} if n < 2 { 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 2825f2e69..039581bac 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 @@ -28,7 +28,7 @@ For example, beginning with *n1* = 3: Write a function to calculate the length of the collatz sequence for a given initial :rust:`n`. -.. code:: rust +:: {{#include exercise.rs:collatz_length}} todo!("Implement this") 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 8db643227..4208e9a4f 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 @@ -40,7 +40,7 @@ into Copy the code below to https://play.rust-lang.org/ and implement the function. This function only operates on 3x3 matrices. -.. code:: rust +:: // TODO: remove this when you're done with your implementation. #![allow(unused_variables, dead_code)] diff --git a/courses/comprehensive_rust_training/060_references/06_exercise.rst b/courses/comprehensive_rust_training/060_references/06_exercise.rst index faf9f1b5c..2e5a800c5 100644 --- a/courses/comprehensive_rust_training/060_references/06_exercise.rst +++ b/courses/comprehensive_rust_training/060_references/06_exercise.rst @@ -10,7 +10,7 @@ We will create a few utility functions for 3-dimensional geometry, representing a point as :rust:`[f64;3]`. It is up to you to determine the function signatures. -.. code:: rust +:: // 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 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 index bbb5c104e..2dd9d48e3 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst @@ -15,7 +15,7 @@ This exercise only requires creating and populating data structures so that :rust:`main` runs without errors. The next part of the course will cover getting data out of these structures. -.. code:: rust +:: {{#include exercise.rs:event}} // TODO: add required variants diff --git a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst index bc2c3fe0b..536f6be95 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst @@ -45,7 +45,7 @@ this as a much bigger tree: In code, we will represent the tree with two types: -.. code:: rust +:: {{#include exercise.rs:Operation}} @@ -67,7 +67,7 @@ a test temporarily with :rust:`#[ignore]`: #[ignore] fn test_value() { .. } -.. code:: rust +:: {{#include exercise.rs:Operation}} 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 index b33499f1e..41900af46 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst @@ -20,7 +20,7 @@ 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 +:: {{#include exercise.rs:setup}} diff --git a/courses/comprehensive_rust_training/100_generics/07_exercise.rst b/courses/comprehensive_rust_training/100_generics/07_exercise.rst index 4febe5162..e0887ca0e 100644 --- a/courses/comprehensive_rust_training/100_generics/07_exercise.rst +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -11,7 +11,7 @@ that determines the minimum of two values, using the `Ord `__ trait. -.. code:: rust +:: use std::cmp::Ordering; 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 0539903d2..bfcd1142c 100644 --- a/courses/comprehensive_rust_training/110_std_types/08_exercise.rst +++ b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst @@ -21,7 +21,7 @@ If you finish early, try using the method to halve the number of hash lookups required to implement the :rust:`count` method. -.. code:: rust +:: use std::collections::HashMap; 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 f1a432cd0..81e247ea5 100644 --- a/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst +++ b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst @@ -11,7 +11,7 @@ In this example, you will implement the classic playground, and implement the missing bits. Only rotate ASCII alphabetic characters, to ensure the result is still valid UTF-8. -.. code:: rust +:: {{#include exercise.rs:head }} diff --git a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst index 86d9a9ca7..0c2838624 100644 --- a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst +++ b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst @@ -12,7 +12,7 @@ value piece-by-piece, using convenience functions. Fill in the missing pieces. -.. code:: rust +:: {{#include exercise.rs:Package}} {{#include exercise.rs:as_dependency}} diff --git a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst index 83a9778bc..b9fbe4f1f 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst @@ -17,7 +17,7 @@ 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 +:: include exercise.rs:types diff --git a/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst index bae5268bd..9a2417914 100644 --- a/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst +++ b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst @@ -11,7 +11,7 @@ Exercise: Health Statistics 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)] diff --git a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst index 47c14c1fe..f8c8cf75f 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst @@ -78,7 +78,7 @@ those callbacks. What remains for you is to implement the :rust:`parse_field` function and the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. -.. code:: rust +:: {{#include exercise.rs:preliminaries }} diff --git a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst index fc7c432bc..887994697 100644 --- a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst +++ b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst @@ -15,7 +15,7 @@ Copy the following code to https://play.rust-lang.org/ and make the tests pass. Use an iterator expression and :rust:`collect` the result to construct the return value. -.. code:: rust +:: {{#include exercise.rs:offset_differences}} unimplemented!() diff --git a/courses/comprehensive_rust_training/180_modules/06_exercise.rst b/courses/comprehensive_rust_training/180_modules/06_exercise.rst index b7727aa8a..627e41f1c 100644 --- a/courses/comprehensive_rust_training/180_modules/06_exercise.rst +++ b/courses/comprehensive_rust_training/180_modules/06_exercise.rst @@ -35,7 +35,7 @@ Source Here's the single-module implementation of the GUI library: -.. code:: rust +:: {{#include exercise.rs:single-module}} diff --git a/courses/comprehensive_rust_training/190_testing/04_exercise.rst b/courses/comprehensive_rust_training/190_testing/04_exercise.rst index c8243b27f..582ec127b 100644 --- a/courses/comprehensive_rust_training/190_testing/04_exercise.rst +++ b/courses/comprehensive_rust_training/190_testing/04_exercise.rst @@ -31,7 +31,7 @@ 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}} diff --git a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst index 8a93881d0..aa20c3f2c 100644 --- a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst +++ b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst @@ -13,7 +13,7 @@ handling to handle this error case and return an error when it occurs. We provide a simple :rust:`DivideByZeroError` type to use as the error type for :rust:`eval`. -.. code:: rust +:: #include exercise.rs:types 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 233bf5e67..ab42b5405 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst @@ -62,7 +62,7 @@ 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 +:: // TODO: remove this when you're done with your implementation. #![allow(unused_imports, unused_variables, dead_code)] From 02c1b31975985dda8a02524fe80009062fff19bc Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Fri, 7 Feb 2025 21:20:45 -0500 Subject: [PATCH 10/55] Switch to unix formatting --- .../010_introduction.rst | 88 ++--- .../010_introduction/01-about_adacore.rst | 42 +- .../02-about_this_training.rst | 166 ++++---- .../020_hello_world.rst | 82 ++-- .../020_hello_world/01_what_is_rust.rst | 82 ++-- .../020_hello_world/02_benefits.rst | 120 +++--- .../020_hello_world/03_playground.rst | 64 +-- .../030_types_and_values.rst | 88 ++--- .../030_types_and_values/01_hello_world.rst | 112 +++--- .../030_types_and_values/02_variables.rst | 70 ++-- .../030_types_and_values/03_values.rst | 96 ++--- .../030_types_and_values/04_arithmetic.rst | 80 ++-- .../030_types_and_values/05_inference.rst | 108 +++--- .../030_types_and_values/06_exercise.rst | 56 +-- .../040_control_flow_basics.rst | 92 ++--- .../040_control_flow_basics/01_if.rst | 96 ++--- .../040_control_flow_basics/02_match.rst | 160 ++++---- .../040_control_flow_basics/03_loops.rst | 172 ++++----- .../04_break_continue.rst | 156 ++++---- .../05_blocks_and_scopes.rst | 68 ++-- .../040_control_flow_basics/06_functions.rst | 84 ++-- .../040_control_flow_basics/07_macros.rst | 104 ++--- .../040_control_flow_basics/08_exercise.rst | 78 ++-- .../050_tuples_and_arrays.rst | 86 ++--- .../050_tuples_and_arrays/01_arrays.rst | 80 ++-- .../050_tuples_and_arrays/02_tuples.rst | 60 +-- .../050_tuples_and_arrays/03_iteration.rst | 62 +-- .../04_destructuring.rst | 90 ++--- .../050_tuples_and_arrays/05_exercise.rst | 108 +++--- .../060_references.rst | 88 ++--- .../060_references/01_shared.rst | 132 +++---- .../060_references/02_exclusive.rst | 72 ++-- .../060_references/03_slices.rst | 92 ++--- .../060_references/04_strings.rst | 166 ++++---- .../060_references/05_dangling.rst | 84 ++-- .../060_references/06_exercise.rst | 68 ++-- .../070_user_defined_types.rst | 90 ++--- .../01_named_structs.rst | 126 +++--- .../02_tuple_structs.rst | 124 +++--- .../070_user_defined_types/03_enums.rst | 258 ++++++------- .../070_user_defined_types/04_aliases.rst | 68 ++-- .../070_user_defined_types/05_const.rst | 102 ++--- .../070_user_defined_types/06_static.rst | 98 ++--- .../070_user_defined_types/07_exercise.rst | 92 ++--- .../080_pattern_matching.rst | 86 ++--- .../080_pattern_matching/01_match.rst | 162 ++++---- .../02_destructuring_structs.rst | 50 +-- .../03_destructuring_enums.rst | 112 +++--- .../04_let_control_flow.rst | 284 +++++++------- .../080_pattern_matching/05_exercise.rst | 160 ++++---- .../090_methods_and_traits.rst | 84 ++-- .../090_methods_and_traits/01_methods.rst | 202 +++++----- .../090_methods_and_traits/02_traits.rst | 322 ++++++++-------- .../090_methods_and_traits/03_deriving.rst | 72 ++-- .../090_methods_and_traits/04_exercise.rst | 58 +-- .../100_generics.rst | 90 ++--- .../100_generics/01_generic_functions.rst | 114 +++--- .../100_generics/02_generic_data.rst | 108 +++--- .../100_generics/03_generic_traits.rst | 110 +++--- .../100_generics/04_trait_bounds.rst | 112 +++--- .../100_generics/05_impl_trait.rst | 114 +++--- .../100_generics/06_dyn_trait.rst | 180 ++++----- .../100_generics/07_exercise.rst | 60 +-- .../110_std_types.rst | 92 ++--- .../110_std_types/01_std.rst | 44 +-- .../110_std_types/02_docs.rst | 108 +++--- .../110_std_types/03_option.rst | 88 ++--- .../110_std_types/04_result.rst | 98 ++--- .../110_std_types/05_string.rst | 142 +++---- .../110_std_types/06_vec.rst | 116 +++--- .../110_std_types/07_hashmap.rst | 172 ++++----- .../110_std_types/08_exercise.rst | 112 +++--- .../120_std_traits.rst | 92 ++--- .../120_std_traits/01_comparisons.rst | 166 ++++---- .../120_std_traits/02_operators.rst | 122 +++--- .../120_std_traits/03_from_and_into.rst | 98 ++--- .../120_std_traits/04_casting.rst | 94 ++--- .../120_std_traits/05_read_and_write.rst | 102 ++--- .../120_std_traits/06_default.rst | 118 +++--- .../120_std_traits/07_closures.rst | 174 ++++----- .../120_std_traits/08_exercise.rst | 46 +-- .../130_memory_management.rst | 92 ++--- .../130_memory_management/01_review.rst | 170 ++++---- .../130_memory_management/02_approaches.rst | 122 +++--- .../130_memory_management/03_ownership.rst | 72 ++-- .../130_memory_management/04_move.rst | 364 +++++++++--------- .../130_memory_management/05_clone.rst | 80 ++-- .../130_memory_management/06_copy_types.rst | 138 +++---- .../130_memory_management/07_drop.rst | 136 +++---- .../130_memory_management/08_exercise.rst | 88 ++--- .../140_smart_pointers.rst | 84 ++-- .../140_smart_pointers/01_box.rst | 200 +++++----- .../140_smart_pointers/02_rc.rst | 94 ++--- .../140_smart_pointers/03_trait_objects.rst | 222 +++++------ .../140_smart_pointers/04_exercise.rst | 52 +-- .../150_borrowing.rst | 86 ++--- .../150_borrowing/01_shared.rst | 158 ++++---- .../150_borrowing/02_borrowck.rst | 140 +++---- .../150_borrowing/03_examples.rst | 78 ++-- .../150_borrowing/04_interior_mutability.rst | 226 +++++------ .../150_borrowing/05_exercise.rst | 56 +-- .../160_lifetimes.rst | 84 ++-- .../160_lifetimes/01_lifetime_annotations.rst | 132 +++---- .../160_lifetimes/02_lifetime_elision.rst | 146 +++---- .../160_lifetimes/03_struct_lifetimes.rst | 94 ++--- .../160_lifetimes/04_exercise.rst | 222 +++++------ .../170_iterators.rst | 88 ++--- .../170_iterators/01_motivation.rst | 136 +++---- .../170_iterators/02_iterator.rst | 146 +++---- .../170_iterators/03_helpers.rst | 94 ++--- .../170_iterators/04_collect.rst | 112 +++--- .../170_iterators/05_intoiterator.rst | 184 ++++----- .../170_iterators/06_exercise.rst | 48 +-- .../180_modules.rst | 88 ++--- .../180_modules/01_modules.rst | 82 ++-- .../180_modules/02_filesystem.rst | 160 ++++---- .../180_modules/03_visibility.rst | 114 +++--- .../180_modules/04_encapsulation.rst | 170 ++++---- .../180_modules/05_paths.rst | 116 +++--- .../180_modules/06_exercise.rst | 96 ++--- .../190_testing.rst | 84 ++-- .../190_testing/01_unit_tests.rst | 102 ++--- .../190_testing/02_other.rst | 106 ++--- .../190_testing/03_lints.rst | 68 ++-- .../190_testing/04_exercise.rst | 78 ++-- .../200_error_handling.rst | 92 ++--- .../200_error_handling/01_panics.rst | 116 +++--- .../200_error_handling/02_result.rst | 174 ++++----- .../200_error_handling/03_try.rst | 138 +++---- .../200_error_handling/04_try_conversions.rst | 196 +++++----- .../200_error_handling/05_error.rst | 98 ++--- .../200_error_handling/06_thiserror.rst | 110 +++--- .../200_error_handling/07_anyhow.rst | 148 +++---- .../200_error_handling/08_exercise.rst | 54 +-- .../210_unsafe_rust.rst | 90 ++--- .../210_unsafe_rust/01_unsafe.rst | 82 ++-- .../210_unsafe_rust/02_dereferencing.rst | 130 +++---- .../210_unsafe_rust/03_mutable_static.rst | 106 ++--- .../210_unsafe_rust/04_unions.rst | 72 ++-- .../210_unsafe_rust/05_unsafe_functions.rst | 212 +++++----- .../210_unsafe_rust/06_unsafe_traits.rst | 88 ++--- .../210_unsafe_rust/07_exercise.rst | 170 ++++---- 142 files changed, 8179 insertions(+), 8179 deletions(-) diff --git a/courses/comprehensive_rust_training/010_introduction.rst b/courses/comprehensive_rust_training/010_introduction.rst index 871d7e9d0..83f5ebfdf 100644 --- a/courses/comprehensive_rust_training/010_introduction.rst +++ b/courses/comprehensive_rust_training/010_introduction.rst @@ -1,44 +1,44 @@ -************ -Introduction -************ - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************ +Introduction +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 24d52ae24..58c6a6afe 100644 --- a/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst +++ b/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst @@ -1,21 +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) - +============= +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 index 81b578774..85b97e022 100644 --- a/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst +++ b/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst @@ -1,83 +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 +=================== +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/020_hello_world.rst b/courses/comprehensive_rust_training/020_hello_world.rst index dfebc51e3..d90348eb2 100644 --- a/courses/comprehensive_rust_training/020_hello_world.rst +++ b/courses/comprehensive_rust_training/020_hello_world.rst @@ -1,41 +1,41 @@ -************* -Hello World -************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************* +Hello World +************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 7ba69ab11..00677b9ae 100644 --- 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 @@ -1,41 +1,41 @@ -=============== -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++ - - - :rust:`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. - ---------- -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. +=============== +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++ + + - :rust:`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. + +--------- +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. diff --git a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst index 5203a9644..14bd1c3e1 100644 --- a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst +++ b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst @@ -1,60 +1,60 @@ -================== -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 :rust:`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. - ---------- -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). +================== +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 :rust:`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. + +--------- +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). diff --git a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst index af0099428..023e8f603 100644 --- a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst +++ b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst @@ -1,32 +1,32 @@ -============ -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 :rust:`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. - ---------- -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. +============ +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 :rust:`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. + +--------- +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. diff --git a/courses/comprehensive_rust_training/030_types_and_values.rst b/courses/comprehensive_rust_training/030_types_and_values.rst index 4444b8c0b..c45f40579 100644 --- a/courses/comprehensive_rust_training/030_types_and_values.rst +++ b/courses/comprehensive_rust_training/030_types_and_values.rst @@ -1,44 +1,44 @@ -****************** -Types And Values -****************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +****************** +Types And Values +****************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 6661e6cc8..a1ea568d3 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 @@ -1,56 +1,56 @@ -============== -Hello, World -============== - --------------- -Hello, World --------------- - -Let us jump into the simplest possible Rust program, a classic Hello -World program: - -.. code:: rust - - fn main() { - println!("Hello World!"); - } - -What you see: - -- Functions are introduced with :rust:`fn`. -- Blocks are delimited by curly braces like in C and C++. -- The :rust:`main` function is the entry point of the program. -- Rust has hygienic macros, :rust:`println!` is an example of this. -- Rust strings are UTF-8 encoded and can contain any Unicode character. - ---------- -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 `__. +============== +Hello, World +============== + +-------------- +Hello, World +-------------- + +Let us jump into the simplest possible Rust program, a classic Hello +World program: + +.. code:: rust + + fn main() { + println!("Hello World!"); + } + +What you see: + +- Functions are introduced with :rust:`fn`. +- Blocks are delimited by curly braces like in C and C++. +- The :rust:`main` function is the entry point of the program. +- Rust has hygienic macros, :rust:`println!` is an example of this. +- Rust strings are UTF-8 encoded and can contain any Unicode character. + +--------- +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 `__. 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 index f1f361c19..ebce89d7e 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst @@ -1,35 +1,35 @@ -=========== -Variables -=========== - ------------ -Variables ------------ - -Rust provides type safety via static typing. Variable bindings are made -with :rust:`let`: - -.. code:: rust - - fn main() { - let x: i32 = 10; - println!("x: {x}"); - // x = 20; - // println!("x: {x}"); - } - ---------- -Details ---------- - -- Uncomment the :rust:`x = 20` to demonstrate that variables are immutable - by default. Add the :rust:`mut` keyword to allow changes. - -- Warnings are enabled for this slide, such as for unused variables or - unnecessary :rust:`mut`. These are omitted in most slides to avoid - distracting warnings. Try removing the mutation but leaving the - :rust:`mut` keyword in place. - -- The :rust:`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. +=========== +Variables +=========== + +----------- +Variables +----------- + +Rust provides type safety via static typing. Variable bindings are made +with :rust:`let`: + +.. code:: rust + + fn main() { + let x: i32 = 10; + println!("x: {x}"); + // x = 20; + // println!("x: {x}"); + } + +--------- +Details +--------- + +- Uncomment the :rust:`x = 20` to demonstrate that variables are immutable + by default. Add the :rust:`mut` keyword to allow changes. + +- Warnings are enabled for this slide, such as for unused variables or + unnecessary :rust:`mut`. These are omitted in most slides to avoid + distracting warnings. Try removing the mutation but leaving the + :rust:`mut` keyword in place. + +- The :rust:`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. 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 1a865d0ac..691522290 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 @@ -1,48 +1,48 @@ -======== -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'``, | -| scalar values | | ':math:`\alpha`', | -| | | ':math:`\infty`' | -+---------------+-------------------------------+---------------------+ -| Booleans | ``bool`` | ``true``, ``false`` | -+---------------+-------------------------------+---------------------+ - -The types have widths as follows: - -- :rust:`iN`, :rust:`uN`, and :rust:`fN` are *N* bits wide, -- :rust:`isize` and :rust:`usize` are the width of a pointer, -- :rust:`char` is 32 bits wide, -- :rust:`bool` is 8 bits wide. - ---------- -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 :rust:`1_000` can be written as :rust:`1000` (or :rust:`10_00`), and - :rust:`123_i64` can be written as :rust:`123i64`. +======== +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'``, | +| scalar values | | ':math:`\alpha`', | +| | | ':math:`\infty`' | ++---------------+-------------------------------+---------------------+ +| Booleans | ``bool`` | ``true``, ``false`` | ++---------------+-------------------------------+---------------------+ + +The types have widths as follows: + +- :rust:`iN`, :rust:`uN`, and :rust:`fN` are *N* bits wide, +- :rust:`isize` and :rust:`usize` are the width of a pointer, +- :rust:`char` is 32 bits wide, +- :rust:`bool` is 8 bits wide. + +--------- +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 :rust:`1_000` can be written as :rust:`1000` (or :rust:`10_00`), and + :rust:`123_i64` can be written as :rust:`123i64`. 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 index b5f85ce7c..91f88dcbf 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst @@ -1,40 +1,40 @@ -============ -Arithmetic -============ - ------------- -Arithmetic ------------- - -.. code:: rust - - fn interproduct(a: i32, b: i32, c: i32) -> i32 { - return a * b + b * c + c * a; - } - - fn main() { - println!("result: {}", interproduct(120, 100, 248)); - } - ---------- -Details ---------- - -This is the first time we've seen a function other than :rust:`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 :rust:`i32`\ 's to :rust:`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., -:rust:`(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. +============ +Arithmetic +============ + +------------ +Arithmetic +------------ + +.. code:: rust + + fn interproduct(a: i32, b: i32, c: i32) -> i32 { + return a * b + b * c + c * a; + } + + fn main() { + println!("result: {}", interproduct(120, 100, 248)); + } + +--------- +Details +--------- + +This is the first time we've seen a function other than :rust:`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 :rust:`i32`\ 's to :rust:`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., +:rust:`(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. 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 index c555187b1..8e09f3fd7 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst @@ -1,54 +1,54 @@ -================ -Type Inference -================ - ----------------- -Type Inference ----------------- - -Rust will look at how the variable is *used* to determine the type: - -.. code:: rust - - 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); - } - ---------- -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 -:rust:`i32`. This sometimes appears as :rust:`{integer}` in error messages. -Similarly, floating-point literals default to :rust:`f64`. - -.. code:: rust - - fn main() { - let x = 3.14; - let y = 20; - assert_eq!(x, y); - // ERROR: no implementation for `{float} == {integer}` - } +================ +Type Inference +================ + +---------------- +Type Inference +---------------- + +Rust will look at how the variable is *used* to determine the type: + +.. code:: rust + + 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); + } + +--------- +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 +:rust:`i32`. This sometimes appears as :rust:`{integer}` in error messages. +Similarly, floating-point literals default to :rust:`f64`. + +.. code:: rust + + fn main() { + let x = 3.14; + let y = 20; + assert_eq!(x, y); + // ERROR: no implementation for `{float} == {integer}` + } 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 index 6db2faa8a..728a04e3a 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst @@ -1,28 +1,28 @@ -===================== -Exercise: Fibonacci -===================== - ---------------------- -Exercise: Fibonacci ---------------------- - -The Fibonacci sequence begins with :rust:`[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 :rust:`fib(n)` that calculates the n'th Fibonacci number. -When will this function 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}} +===================== +Exercise: Fibonacci +===================== + +--------------------- +Exercise: Fibonacci +--------------------- + +The Fibonacci sequence begins with :rust:`[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 :rust:`fib(n)` that calculates the n'th Fibonacci number. +When will this function 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 index 856f38bac..3caa1bd06 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics.rst @@ -1,46 +1,46 @@ -********************* -Control Flow Basics -********************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +********************* +Control Flow Basics +********************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 25677defc..dcb2cf5fd 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 @@ -1,48 +1,48 @@ -==================== -"if" expressions -==================== - --------------------- -"if" expressions --------------------- - -You use -`if expressions `__ -exactly like :rust:`if` statements in other languages: - -.. code:: rust - - fn main() { - let x = 10; - if x == 0 { - println!("zero!"); - } else if x < 100 { - println!("biggish"); - } else { - println!("huge"); - } - } - -In addition, you can use :rust:`if` as an expression. The last expression of -each block becomes the value of the :rust:`if` expression: - -.. code:: rust - - fn main() { - let x = 10; - let size = if x < 20 { "small" } else { "large" }; - println!("number size: {}", size); - } - ---------- -Details ---------- - -Because :rust:`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 -:rust:`;` after :rust:`"small"` in the second example. - -An :rust:`if` expression should be used in the same way as the other -expressions. For example, when it is used in a :rust:`let` statement, the -statement must be terminated with a :rust:`;` as well. Remove the :rust:`;` -before :rust:`println!` to see the compiler error. +==================== +"if" expressions +==================== + +-------------------- +"if" expressions +-------------------- + +You use +`if expressions `__ +exactly like :rust:`if` statements in other languages: + +.. code:: rust + + fn main() { + let x = 10; + if x == 0 { + println!("zero!"); + } else if x < 100 { + println!("biggish"); + } else { + println!("huge"); + } + } + +In addition, you can use :rust:`if` as an expression. The last expression of +each block becomes the value of the :rust:`if` expression: + +.. code:: rust + + fn main() { + let x = 10; + let size = if x < 20 { "small" } else { "large" }; + println!("number size: {}", size); + } + +--------- +Details +--------- + +Because :rust:`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 +:rust:`;` after :rust:`"small"` in the second example. + +An :rust:`if` expression should be used in the same way as the other +expressions. For example, when it is used in a :rust:`let` statement, the +statement must be terminated with a :rust:`;` as well. Remove the :rust:`;` +before :rust:`println!` to see the compiler error. 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 index 2846f0a82..5bef5cb4a 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst @@ -1,80 +1,80 @@ -======================= -"match" Expressions -======================= - ------------------------ -"match" Expressions ------------------------ - -:rust:`match` can be used to check a value against one or more options: - -.. code:: rust - - fn main() { - let val = 1; - match val { - 1 => println!("one"), - 10 => println!("ten"), - 100 => println!("one hundred"), - _ => { - println!("something else"); - } - } - } - -Like :rust:`if` expressions, :rust:`match` can also return a value; - -.. code:: rust - - fn main() { - let flag = true; - let val = match flag { - true => 1, - false => 0, - }; - println!("The value of {flag} is {val}"); - } - ---------- -Details ---------- - -- :rust:`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 :rust:`switch` works - in other languages. - -- The body of a :rust:`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. - -- :rust:`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 :rust:`_`. Exhaustiveness is easiest to demonstrate with enums, but - enums haven't been introduced yet. Instead we demonstrate matching on - a :rust:`bool`, which is the simplest primitive type. - -- This slide introduces :rust:`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 :rust:`match`, you can compare the - examples to their equivalents written with :rust:`if`. In the second case - matching on a :rust:`bool` an :rust:`if {} else {}` block is pretty similar. - But in the first example that checks multiple cases, a :rust:`match` - expression can be more concise than - :rust:`if {} else if {} else if {} else`. - -- :rust:`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. +======================= +"match" Expressions +======================= + +----------------------- +"match" Expressions +----------------------- + +:rust:`match` can be used to check a value against one or more options: + +.. code:: rust + + fn main() { + let val = 1; + match val { + 1 => println!("one"), + 10 => println!("ten"), + 100 => println!("one hundred"), + _ => { + println!("something else"); + } + } + } + +Like :rust:`if` expressions, :rust:`match` can also return a value; + +.. code:: rust + + fn main() { + let flag = true; + let val = match flag { + true => 1, + false => 0, + }; + println!("The value of {flag} is {val}"); + } + +--------- +Details +--------- + +- :rust:`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 :rust:`switch` works + in other languages. + +- The body of a :rust:`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. + +- :rust:`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 :rust:`_`. Exhaustiveness is easiest to demonstrate with enums, but + enums haven't been introduced yet. Instead we demonstrate matching on + a :rust:`bool`, which is the simplest primitive type. + +- This slide introduces :rust:`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 :rust:`match`, you can compare the + examples to their equivalents written with :rust:`if`. In the second case + matching on a :rust:`bool` an :rust:`if {} else {}` block is pretty similar. + But in the first example that checks multiple cases, a :rust:`match` + expression can be more concise than + :rust:`if {} else if {} else if {} else`. + +- :rust:`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. 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 bc6c872ea..16047d2f2 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 @@ -1,86 +1,86 @@ -======= -Loops -======= - -------- -Loops -------- - -There are three looping keywords in Rust: :rust:`while`, :rust:`loop`, and -:rust:`for`: - ------------ -"while" ------------ - -The -`while keyword `__ -works much like in other languages, executing the loop body as long as -the condition is true. - -.. code:: rust - - fn main() { - let mut x = 200; - while x >= 10 { - x = x / 2; - } - println!("Final x: {x}"); - } - -------------- -"for" -------------- - -The :rust:`for` `loop `__ -iterates over ranges of values or the items in a collection: - -.. code:: rust - - fn main() { - for x in 1..5 { - println!("x: {x}"); - } - - for elem in [1, 2, 3, 4, 5] { - println!("elem: {elem}"); - } - } - ---------------- -"for" Details ---------------- - -- Under the hood :rust:`for` loops use a concept called :dfn:`iterators` to - handle iterating over different kinds of ranges/collections. - Iterators will be discussed in more detail later. -- Note that the first :rust:`for` loop only iterates to :rust:`4`. Show the - :rust:`1..=5` syntax for an inclusive range. - --------------- -"loop" --------------- - -The :rust:`loop` -`statement `__ just -loops forever, until a :rust:`break`. - -.. code:: rust - - fn main() { - let mut i = 0; - loop { - i += 1; - println!("{i}"); - if i > 100 { - break; - } - } - } - ----------------- -"loop" Details ----------------- - -- The :rust:`loop` statement works like a :rust:`while true` loop. Use it for - things like servers which will serve connections forever. +======= +Loops +======= + +------- +Loops +------- + +There are three looping keywords in Rust: :rust:`while`, :rust:`loop`, and +:rust:`for`: + +----------- +"while" +----------- + +The +`while keyword `__ +works much like in other languages, executing the loop body as long as +the condition is true. + +.. code:: rust + + fn main() { + let mut x = 200; + while x >= 10 { + x = x / 2; + } + println!("Final x: {x}"); + } + +------------- +"for" +------------- + +The :rust:`for` `loop `__ +iterates over ranges of values or the items in a collection: + +.. code:: rust + + fn main() { + for x in 1..5 { + println!("x: {x}"); + } + + for elem in [1, 2, 3, 4, 5] { + println!("elem: {elem}"); + } + } + +--------------- +"for" Details +--------------- + +- Under the hood :rust:`for` loops use a concept called :dfn:`iterators` to + handle iterating over different kinds of ranges/collections. + Iterators will be discussed in more detail later. +- Note that the first :rust:`for` loop only iterates to :rust:`4`. Show the + :rust:`1..=5` syntax for an inclusive range. + +-------------- +"loop" +-------------- + +The :rust:`loop` +`statement `__ just +loops forever, until a :rust:`break`. + +.. code:: rust + + fn main() { + let mut i = 0; + loop { + i += 1; + println!("{i}"); + if i > 100 { + break; + } + } + } + +---------------- +"loop" Details +---------------- + +- The :rust:`loop` statement works like a :rust:`while true` loop. Use it for + things like servers which will serve connections forever. 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 adebca184..18fe5696b 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 @@ -1,78 +1,78 @@ -============================ -"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 :rust:`loop`, this can take an optional expression that becomes the -value of the :rust:`loop` expression. - -.. code:: rust - - fn main() { - let mut i = 0; - loop { - i += 1; - if i > 5 { - break; - } - if i % 2 == 0 { - continue; - } - println!("{}", i); - } - } - ---------- -Details ---------- - -Note that :rust:`loop` is the only looping construct which can return a -non-trivial value. This is because it's guaranteed to only return at a -:rust:`break` statement (unlike :rust:`while` and :rust:`for` loops, which can also -return when the condition fails). - --------- -Labels --------- - -Both :rust:`continue` and :rust:`break` can optionally take a label argument -which is used to break out of nested loops: - -.. code:: rust - - fn main() { - let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]]; - let mut elements_searched = 0; - let target_value = 10; - 'outer: for i in 0..=2 { - for j in 0..=2 { - elements_searched += 1; - if s[i][j] == target_value { - break 'outer; - } - } - } - print!("elements searched: {elements_searched}"); - } - ---------- -Details ---------- - -- Labeled break also works on arbitrary blocks, e.g. - - .. code:: rust - - 'label: { - break 'label; - println!("This line gets skipped"); - } - +============================ +"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 :rust:`loop`, this can take an optional expression that becomes the +value of the :rust:`loop` expression. + +.. code:: rust + + fn main() { + let mut i = 0; + loop { + i += 1; + if i > 5 { + break; + } + if i % 2 == 0 { + continue; + } + println!("{}", i); + } + } + +--------- +Details +--------- + +Note that :rust:`loop` is the only looping construct which can return a +non-trivial value. This is because it's guaranteed to only return at a +:rust:`break` statement (unlike :rust:`while` and :rust:`for` loops, which can also +return when the condition fails). + +-------- +Labels +-------- + +Both :rust:`continue` and :rust:`break` can optionally take a label argument +which is used to break out of nested loops: + +.. code:: rust + + fn main() { + let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]]; + let mut elements_searched = 0; + let target_value = 10; + 'outer: for i in 0..=2 { + for j in 0..=2 { + elements_searched += 1; + if s[i][j] == target_value { + break 'outer; + } + } + } + print!("elements searched: {elements_searched}"); + } + +--------- +Details +--------- + +- Labeled break also works on arbitrary blocks, e.g. + + .. code:: rust + + 'label: { + break 'label; + println!("This line gets skipped"); + } + 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 index 96923496d..ca55f7e3e 100644 --- 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 @@ -1,34 +1,34 @@ -=================== -Blocks and Scopes -=================== - --------- -Blocks --------- - -A block in Rust contains a sequence of expressions, enclosed by braces -:rust:`{}`. Each block has a value and a type, which are those of the last -expression of the block: - -.. code:: rust - - fn main() { - let z = 13; - let x = { - let y = 10; - println!("y: {y}"); - z - y - }; - println!("x: {x}"); - } - -If the last expression ends with :rust:`;`, then the resulting value and -type is :rust:`()`. - ---------- -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 :rust:`return`. +=================== +Blocks and Scopes +=================== + +-------- +Blocks +-------- + +A block in Rust contains a sequence of expressions, enclosed by braces +:rust:`{}`. Each block has a value and a type, which are those of the last +expression of the block: + +.. code:: rust + + fn main() { + let z = 13; + let x = { + let y = 10; + println!("y: {y}"); + z - y + }; + println!("x: {x}"); + } + +If the last expression ends with :rust:`;`, then the resulting value and +type is :rust:`()`. + +--------- +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 :rust:`return`. 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 979f3b4b3..4a189ae45 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 @@ -1,42 +1,42 @@ -=========== -Functions -=========== - ------------ -Functions ------------ - -.. code:: rust - - fn gcd(a: u32, b: u32) -> u32 { - if b > 0 { - gcd(b, a % b) - } else { - a - } - } - - fn main() { - println!("gcd: {}", gcd(143, 52)); - } - ---------- -Details ---------- - -- Declaration parameters are followed by a type (the reverse of some - programming languages), then a return type. -- The last expression in a function body (or any block) becomes the - return value. Simply omit the :rust:`;` at the end of the expression. The - :rust:`return` keyword can be used for early return, but the "bare value" - form is idiomatic at the end of a function (refactor :rust:`gcd` to use a - :rust:`return`). -- Some functions have no return value, and return the 'unit type', - :rust:`()`. The compiler will infer this if the return type is omitted. -- Overloading is not supported - each function has a single - implementation. - - - Always takes a fixed number of parameters. Default arguments are - not supported. Macros can be used to support variadic functions. - - Always takes a single set of parameter types. These types can be - generic, which will be covered later. +=========== +Functions +=========== + +----------- +Functions +----------- + +.. code:: rust + + fn gcd(a: u32, b: u32) -> u32 { + if b > 0 { + gcd(b, a % b) + } else { + a + } + } + + fn main() { + println!("gcd: {}", gcd(143, 52)); + } + +--------- +Details +--------- + +- Declaration parameters are followed by a type (the reverse of some + programming languages), then a return type. +- The last expression in a function body (or any block) becomes the + return value. Simply omit the :rust:`;` at the end of the expression. The + :rust:`return` keyword can be used for early return, but the "bare value" + form is idiomatic at the end of a function (refactor :rust:`gcd` to use a + :rust:`return`). +- Some functions have no return value, and return the 'unit type', + :rust:`()`. The compiler will infer this if the return type is omitted. +- Overloading is not supported - each function has a single + implementation. + + - Always takes a fixed number of parameters. Default arguments are + not supported. Macros can be used to support variadic functions. + - Always takes a single set of parameter types. These types can be + generic, which will be covered later. 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 7cb3bf031..9b5381baa 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 @@ -1,52 +1,52 @@ -======== -Macros -======== - --------- -Macros --------- - -Macros are expanded into Rust code during compilation, and can take a -variable number of arguments. They are distinguished by a :rust:`!` at the -end. The Rust standard library includes an assortment of useful macros. - -- :rust:`println!(format, ..)` prints a line to standard output, applying - formatting described in - `std::fmt `__. -- :rust:`format!(format, ..)` works just like :rust:`println!` but returns the - result as a string. -- :rust:`dbg!(expression)` logs the value of the expression and returns it. -- :rust:`todo!()` marks a bit of code as not-yet-implemented. If executed, - it will panic. -- :rust:`unreachable!()` marks a bit of code as unreachable. If executed, - it will panic. - -.. code:: rust - - 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)); - } - ---------- -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. +======== +Macros +======== + +-------- +Macros +-------- + +Macros are expanded into Rust code during compilation, and can take a +variable number of arguments. They are distinguished by a :rust:`!` at the +end. The Rust standard library includes an assortment of useful macros. + +- :rust:`println!(format, ..)` prints a line to standard output, applying + formatting described in + `std::fmt `__. +- :rust:`format!(format, ..)` works just like :rust:`println!` but returns the + result as a string. +- :rust:`dbg!(expression)` logs the value of the expression and returns it. +- :rust:`todo!()` marks a bit of code as not-yet-implemented. If executed, + it will panic. +- :rust:`unreachable!()` marks a bit of code as unreachable. If executed, + it will panic. + +.. code:: rust + + 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)); + } + +--------- +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. 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 039581bac..ff786cc87 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 @@ -1,39 +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 :rust:`n`. - -:: - - {{#include exercise.rs:collatz_length}} - todo!("Implement this") - } - - {{#include exercise.rs:tests}} - - {{#include exercise.rs:main}} +============================ +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 :rust:`n`. + +:: + + {{#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 index 526591d33..1d337ef40 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays.rst @@ -1,43 +1,43 @@ -******************* -Tuples And Arrays -******************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +******************* +Tuples And Arrays +******************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 8103f0766..37d310929 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst @@ -1,40 +1,40 @@ -======== -Arrays -======== - --------- -Arrays --------- - -.. code:: rust - - fn main() { - let mut a: [i8; 10] = [42; 10]; - a[5] = 0; - println!("a: {a:?}"); - } - ---------- -Details ---------- - -- A value of the array type :rust:`[T; N]` holds :rust:`N` (a compile-time - constant) elements of the same type :rust:`T`. Note that the length of - the array is *part of its type*, which means that :rust:`[u8; 3]` and - :rust:`[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 :rust:`println!` macro asks for the debug implementation with the - :rust:`?` format parameter: :rust:`{}` gives the default output, :rust:`{:?}` - 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 :rust:`#`, eg :rust:`{a:#?}`, invokes a "pretty printing" format, - which can be easier to read. +======== +Arrays +======== + +-------- +Arrays +-------- + +.. code:: rust + + fn main() { + let mut a: [i8; 10] = [42; 10]; + a[5] = 0; + println!("a: {a:?}"); + } + +--------- +Details +--------- + +- A value of the array type :rust:`[T; N]` holds :rust:`N` (a compile-time + constant) elements of the same type :rust:`T`. Note that the length of + the array is *part of its type*, which means that :rust:`[u8; 3]` and + :rust:`[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 :rust:`println!` macro asks for the debug implementation with the + :rust:`?` format parameter: :rust:`{}` gives the default output, :rust:`{:?}` + 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 :rust:`#`, eg :rust:`{a:#?}`, invokes a "pretty printing" format, + which can be easier to read. 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 index 7fcb167fb..652eecb60 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst @@ -1,30 +1,30 @@ -======== -Tuples -======== - --------- -Tuples --------- - -.. code:: rust - - fn main() { - let t: (i8, bool) = (7, true); - println!("t.0: {}", t.0); - println!("t.1: {}", t.1); - } - ---------- -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. :rust:`t.0`, :rust:`t.1`. - -- The empty tuple :rust:`()` is referred to as the "unit type" and - signifies absence of a return value, akin to :rust:`void` in other - languages. +======== +Tuples +======== + +-------- +Tuples +-------- + +.. code:: rust + + fn main() { + let t: (i8, bool) = (7, true); + println!("t.0: {}", t.0); + println!("t.1: {}", t.1); + } + +--------- +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. :rust:`t.0`, :rust:`t.1`. + +- The empty tuple :rust:`()` is referred to as the "unit type" and + signifies absence of a return value, akin to :rust:`void` in other + languages. 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 index 740ba9c71..589b76e7f 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst @@ -1,31 +1,31 @@ -================= -Array Iteration -================= - ------------------ -Array Iteration ------------------ - -The :rust:`for` statement supports iterating over arrays (but not tuples). - -.. code:: rust - - fn main() { - let primes = [2, 3, 5, 7, 11, 13, 17, 19]; - for prime in primes { - for i in 2..prime { - assert_ne!(prime % i, 0); - } - } - } - ---------- -Details ---------- - -This functionality uses the :rust:`IntoIterator` trait, but we haven't -covered that yet. - -The :rust:`assert_ne!` macro is new here. There are also :rust:`assert_eq!` and -:rust:`assert!` macros. These are always checked, while debug-only variants -like :rust:`debug_assert!` compile to nothing in release builds. +================= +Array Iteration +================= + +----------------- +Array Iteration +----------------- + +The :rust:`for` statement supports iterating over arrays (but not tuples). + +.. code:: rust + + fn main() { + let primes = [2, 3, 5, 7, 11, 13, 17, 19]; + for prime in primes { + for i in 2..prime { + assert_ne!(prime % i, 0); + } + } + } + +--------- +Details +--------- + +This functionality uses the :rust:`IntoIterator` trait, but we haven't +covered that yet. + +The :rust:`assert_ne!` macro is new here. There are also :rust:`assert_eq!` and +:rust:`assert!` macros. These are always checked, while debug-only variants +like :rust:`debug_assert!` compile to nothing in release builds. 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 index 21ff863f5..8b1132c42 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst @@ -1,45 +1,45 @@ -============================ -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 - - 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 - - fn print_tuple(tuple: (i32, i32)) { - let (left, right) = tuple; - println!("left: {left}, right: {right}"); - } - ---------- -Details ---------- - -- The patterns used here are "irrefutable", meaning that the compiler - can statically verify that the value on the right of :rust:`=` 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 :rust:`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. +============================ +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 + + 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 + + fn print_tuple(tuple: (i32, i32)) { + let (left, right) = tuple; + println!("left: {left}, right: {right}"); + } + +--------- +Details +--------- + +- The patterns used here are "irrefutable", meaning that the compiler + can statically verify that the value on the right of :rust:`=` 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 :rust:`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. 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 4208e9a4f..a456dcf07 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 @@ -1,54 +1,54 @@ -========================= -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 :rust:`transpose` which -will transpose a matrix (turn rows into columns): - -Transpose - -.. math:: - - \begin{bmatrix} - 1 & 2 & 3 \\ - 4 & 5 & 6 \\ - 7 & 8 & 9 - \end{bmatrix} - -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. - -:: - - // 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}} +========================= +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 :rust:`transpose` which +will transpose a matrix (turn rows into columns): + +Transpose + +.. math:: + + \begin{bmatrix} + 1 & 2 & 3 \\ + 4 & 5 & 6 \\ + 7 & 8 & 9 + \end{bmatrix} + +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. + +:: + + // 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 index cf81e3e22..515bf14ae 100644 --- a/courses/comprehensive_rust_training/060_references.rst +++ b/courses/comprehensive_rust_training/060_references.rst @@ -1,44 +1,44 @@ -************ -References -************ - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************ +References +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index fca3aa92e..95aa18859 100644 --- a/courses/comprehensive_rust_training/060_references/01_shared.rst +++ b/courses/comprehensive_rust_training/060_references/01_shared.rst @@ -1,66 +1,66 @@ -=================== -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. - -.. code:: rust - - 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 :rust:`T` has type :rust:`&T`. A reference value is -made with the :rust:`&` operator. The :rust:`*` operator "dereferences" a -reference, yielding its value. - ---------- -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 :rust:`&` is - always required. - -- Rust will auto-dereference in some cases, in particular when invoking - methods (try :rust:`r.is_ascii()`). There is no need for an :rust:`->` - operator like in C++. - -- In this example, :rust:`r` is mutable so that it can be reassigned - (:rust:`r = &b`). Note that this re-binds :rust:`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 :rust:`*r = 'X'`. - -- Rust is tracking the lifetimes of all references to ensure they live - long enough. Dangling references cannot occur in safe Rust. - :rust:`x_axis` would return a reference to :rust:`point`, but :rust:`point` will - be deallocated when the function returns, so this will not compile. - -- We will talk more about borrowing when we get to ownership. +=================== +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. + +.. code:: rust + + 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 :rust:`T` has type :rust:`&T`. A reference value is +made with the :rust:`&` operator. The :rust:`*` operator "dereferences" a +reference, yielding its value. + +--------- +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 :rust:`&` is + always required. + +- Rust will auto-dereference in some cases, in particular when invoking + methods (try :rust:`r.is_ascii()`). There is no need for an :rust:`->` + operator like in C++. + +- In this example, :rust:`r` is mutable so that it can be reassigned + (:rust:`r = &b`). Note that this re-binds :rust:`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 :rust:`*r = 'X'`. + +- Rust is tracking the lifetimes of all references to ensure they live + long enough. Dangling references cannot occur in safe Rust. + :rust:`x_axis` would return a reference to :rust:`point`, but :rust:`point` will + be deallocated when the function returns, so this will not compile. + +- We will talk more about borrowing when we get to ownership. diff --git a/courses/comprehensive_rust_training/060_references/02_exclusive.rst b/courses/comprehensive_rust_training/060_references/02_exclusive.rst index a8b8c6b50..e54fa7fac 100644 --- a/courses/comprehensive_rust_training/060_references/02_exclusive.rst +++ b/courses/comprehensive_rust_training/060_references/02_exclusive.rst @@ -1,36 +1,36 @@ -====================== -Exclusive References -====================== - ----------------------- -Exclusive References ----------------------- - -Exclusive references, also known as mutable references, allow changing -the value they refer to. They have type :rust:`&mut T`. - -.. code:: rust - - fn main() { - let mut point = (1, 2); - let x_coord = &mut point.0; - *x_coord = 20; - println!("point: {point:?}"); - } - ---------- -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 :rust:`&point.0` or changing - :rust:`point.0` while :rust:`x_coord` is alive. - -- Be sure to note the difference between :rust:`let mut x_coord: &i32` and - :rust:`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. +====================== +Exclusive References +====================== + +---------------------- +Exclusive References +---------------------- + +Exclusive references, also known as mutable references, allow changing +the value they refer to. They have type :rust:`&mut T`. + +.. code:: rust + + fn main() { + let mut point = (1, 2); + let x_coord = &mut point.0; + *x_coord = 20; + println!("point: {point:?}"); + } + +--------- +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 :rust:`&point.0` or changing + :rust:`point.0` while :rust:`x_coord` is alive. + +- Be sure to note the difference between :rust:`let mut x_coord: &i32` and + :rust:`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. diff --git a/courses/comprehensive_rust_training/060_references/03_slices.rst b/courses/comprehensive_rust_training/060_references/03_slices.rst index 85d05f2d1..1221a346f 100644 --- a/courses/comprehensive_rust_training/060_references/03_slices.rst +++ b/courses/comprehensive_rust_training/060_references/03_slices.rst @@ -1,46 +1,46 @@ -======== -Slices -======== - --------- -Slices --------- - -A slice gives you a view into a larger collection: - -.. code:: rust - - 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. - ---------- -Details ---------- - -- We create a slice by borrowing :rust:`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 :rust:`&a[0..a.len()]` and - :rust:`&a[..a.len()]` are identical. - -- The same is true for the last index, so :rust:`&a[2..a.len()]` and - :rust:`&a[2..]` are identical. - -- To easily create a slice of the full array, we can therefore use - :rust:`&a[..]`. - -- :rust:`s` is a reference to a slice of :rust:`i32`\ s. Notice that the type - of :rust:`s` (:rust:`&[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, :rust:`a` has - to remain 'alive' (in scope) for at least as long as our slice. +======== +Slices +======== + +-------- +Slices +-------- + +A slice gives you a view into a larger collection: + +.. code:: rust + + 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. + +--------- +Details +--------- + +- We create a slice by borrowing :rust:`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 :rust:`&a[0..a.len()]` and + :rust:`&a[..a.len()]` are identical. + +- The same is true for the last index, so :rust:`&a[2..a.len()]` and + :rust:`&a[2..]` are identical. + +- To easily create a slice of the full array, we can therefore use + :rust:`&a[..]`. + +- :rust:`s` is a reference to a slice of :rust:`i32`\ s. Notice that the type + of :rust:`s` (:rust:`&[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, :rust:`a` has + to remain 'alive' (in scope) for at least as long as our slice. diff --git a/courses/comprehensive_rust_training/060_references/04_strings.rst b/courses/comprehensive_rust_training/060_references/04_strings.rst index fd7e446ff..3ef3a65d2 100644 --- a/courses/comprehensive_rust_training/060_references/04_strings.rst +++ b/courses/comprehensive_rust_training/060_references/04_strings.rst @@ -1,83 +1,83 @@ -========= -Strings -========= - ---------- -Strings ---------- - -We can now understand the two string types in Rust: - -- :rust:`&str` is a slice of UTF-8 encoded bytes, similar to :rust:`&[u8]`. -- :rust:`String` is an owned buffer of UTF-8 encoded bytes, similar to - :rust:`Vec`. - - - -.. code:: rust - - 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}"); - } - ---------- -Details ---------- - -- :rust:`&str` introduces a string slice, which is an immutable reference - to UTF-8 encoded string data stored in a block of memory. String - literals (:rust:`"Hello"`), are stored in the program's binary. - -- Rust's :rust:`String` type is a wrapper around a vector of bytes. As with - a :rust:`Vec`, it is owned. - -- As with many other types :rust:`String::from()` creates a string from a - string literal; :rust:`String::new()` creates a new empty string, to - which string data can be added using the :rust:`push()` and - :rust:`push_str()` methods. - -- The :rust:`format!()` macro is a convenient way to generate an owned - string from dynamic values. It accepts the same format specification - as :rust:`println!()`. - -- You can borrow :rust:`&str` slices from :rust:`String` via :rust:`&` and - optionally range selection. If you select a byte range that is not - aligned to character boundaries, the expression will panic. The - :rust:`chars` iterator iterates over characters and is preferred over - trying to get character boundaries right. - -- For C++ programmers: think of :rust:`&str` as :rust:`std::string_view` from - C++, but the one that always points to a valid string in memory. Rust - :rust:`String` is a rough equivalent of :rust:`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 :rust:`&[u8]` value directly: - - .. code:: rust - - fn main() { - println!("{:?}", b"abc"); - println!("{:?}", &[97, 98, 99]); - } - -- Raw strings allow you to create a :rust:`&str` value with escapes - disabled: :rust:`r"\n" == "\\n"`. You can embed double-quotes by using an - equal amount of :rust:`#` on either side of the quotes: - - .. code:: rust - - fn main() { - println!(r#"link"#); - println!("link"); - } +========= +Strings +========= + +--------- +Strings +--------- + +We can now understand the two string types in Rust: + +- :rust:`&str` is a slice of UTF-8 encoded bytes, similar to :rust:`&[u8]`. +- :rust:`String` is an owned buffer of UTF-8 encoded bytes, similar to + :rust:`Vec`. + + + +.. code:: rust + + 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}"); + } + +--------- +Details +--------- + +- :rust:`&str` introduces a string slice, which is an immutable reference + to UTF-8 encoded string data stored in a block of memory. String + literals (:rust:`"Hello"`), are stored in the program's binary. + +- Rust's :rust:`String` type is a wrapper around a vector of bytes. As with + a :rust:`Vec`, it is owned. + +- As with many other types :rust:`String::from()` creates a string from a + string literal; :rust:`String::new()` creates a new empty string, to + which string data can be added using the :rust:`push()` and + :rust:`push_str()` methods. + +- The :rust:`format!()` macro is a convenient way to generate an owned + string from dynamic values. It accepts the same format specification + as :rust:`println!()`. + +- You can borrow :rust:`&str` slices from :rust:`String` via :rust:`&` and + optionally range selection. If you select a byte range that is not + aligned to character boundaries, the expression will panic. The + :rust:`chars` iterator iterates over characters and is preferred over + trying to get character boundaries right. + +- For C++ programmers: think of :rust:`&str` as :rust:`std::string_view` from + C++, but the one that always points to a valid string in memory. Rust + :rust:`String` is a rough equivalent of :rust:`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 :rust:`&[u8]` value directly: + + .. code:: rust + + fn main() { + println!("{:?}", b"abc"); + println!("{:?}", &[97, 98, 99]); + } + +- Raw strings allow you to create a :rust:`&str` value with escapes + disabled: :rust:`r"\n" == "\\n"`. You can embed double-quotes by using an + equal amount of :rust:`#` on either side of the quotes: + + .. code:: rust + + fn main() { + println!(r#"link"#); + println!("link"); + } diff --git a/courses/comprehensive_rust_training/060_references/05_dangling.rst b/courses/comprehensive_rust_training/060_references/05_dangling.rst index de790861f..b052f9aab 100644 --- a/courses/comprehensive_rust_training/060_references/05_dangling.rst +++ b/courses/comprehensive_rust_training/060_references/05_dangling.rst @@ -1,42 +1,42 @@ -==================== -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 :rust:`null`, making -them safe to use without :rust:`null` checks. The other rule we'll look at -for now is that references can't *outlive* the data they point to. - -.. code:: rust - - fn main() { - let x_ref = { - let x = 10; - &x - }; - println!("x: {x_ref}"); - } - ---------- -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 :rust:`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. +==================== +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 :rust:`null`, making +them safe to use without :rust:`null` checks. The other rule we'll look at +for now is that references can't *outlive* the data they point to. + +.. code:: rust + + fn main() { + let x_ref = { + let x = 10; + &x + }; + println!("x: {x_ref}"); + } + +--------- +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 :rust:`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. diff --git a/courses/comprehensive_rust_training/060_references/06_exercise.rst b/courses/comprehensive_rust_training/060_references/06_exercise.rst index 2e5a800c5..8dc796e15 100644 --- a/courses/comprehensive_rust_training/060_references/06_exercise.rst +++ b/courses/comprehensive_rust_training/060_references/06_exercise.rst @@ -1,34 +1,34 @@ -==================== -Exercise: Geometry -==================== - --------------------- -Exercise: Geometry --------------------- - -We will create a few utility functions for 3-dimensional geometry, -representing a point as :rust:`[f64;3]`. It is up to you to determine the -function signatures. - -:: - - // 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}} +==================== +Exercise: Geometry +==================== + +-------------------- +Exercise: Geometry +-------------------- + +We will create a few utility functions for 3-dimensional geometry, +representing a point as :rust:`[f64;3]`. It is up to you to determine the +function signatures. + +:: + + // 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 index 2823130b1..83aad69ac 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types.rst @@ -1,45 +1,45 @@ -******************** -User Defined Types -******************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +******************** +User Defined Types +******************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 2d0a1fe06..c4011c8e7 100644 --- 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 @@ -1,63 +1,63 @@ -=============== -Named Structs -=============== - ---------------- -Named Structs ---------------- - -Like C and C++, Rust has support for custom structs: - -.. code:: rust - - 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); - } - ---------- -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. :rust:`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 :rust:`..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. +=============== +Named Structs +=============== + +--------------- +Named Structs +--------------- + +Like C and C++, Rust has support for custom structs: + +.. code:: rust + + 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); + } + +--------- +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. :rust:`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 :rust:`..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. 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 e0722fa1a..a168ab387 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 @@ -1,62 +1,62 @@ -=============== -Tuple Structs -=============== - ---------------- -Tuple Structs ---------------- - -If the field names are unimportant, you can use a tuple struct: - -.. code:: rust - - 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 - - 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); - } - ---------- -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: :rust:`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: - :rust:`PhoneNumber(String)` or :rust:`OddNumber(u32)`. - -- Demonstrate how to add a :rust:`f64` value to a :rust:`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. +=============== +Tuple Structs +=============== + +--------------- +Tuple Structs +--------------- + +If the field names are unimportant, you can use a tuple struct: + +.. code:: rust + + 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 + + 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); + } + +--------- +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: :rust:`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: + :rust:`PhoneNumber(String)` or :rust:`OddNumber(u32)`. + +- Demonstrate how to add a :rust:`f64` value to a :rust:`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. 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 073166f40..fec50750c 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 @@ -1,129 +1,129 @@ -======= -Enums -======= - -------- -Enums -------- - -The :rust:`enum` keyword allows the creation of a type which has a few -different variants: - -.. code:: rust - - #[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:?}"); - } - ---------- -Details ---------- - -Key Points: - -- Enumerations allow you to collect a set of values under one type. -- :rust:`Direction` is a type with variants. There are two values of - :rust:`Direction`: :rust:`Direction::Left` and :rust:`Direction::Right`. -- :rust:`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 :rust:`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, :rust:`Option<&u8>` stores either - a pointer to an integer or :rust:`NULL` for the :rust:`None` variant. - - - You can control the discriminant if needed (e.g., for - compatibility with C): - - .. code:: rust - - #[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 :rust:`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 :rust:`size_of::()` equals :rust:`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. - - .. code:: rust - - 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); - } - } +======= +Enums +======= + +------- +Enums +------- + +The :rust:`enum` keyword allows the creation of a type which has a few +different variants: + +.. code:: rust + + #[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:?}"); + } + +--------- +Details +--------- + +Key Points: + +- Enumerations allow you to collect a set of values under one type. +- :rust:`Direction` is a type with variants. There are two values of + :rust:`Direction`: :rust:`Direction::Left` and :rust:`Direction::Right`. +- :rust:`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 :rust:`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, :rust:`Option<&u8>` stores either + a pointer to an integer or :rust:`NULL` for the :rust:`None` variant. + + - You can control the discriminant if needed (e.g., for + compatibility with C): + + .. code:: rust + + #[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 :rust:`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 :rust:`size_of::()` equals :rust:`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. + + .. code:: rust + + 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); + } + } 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 index c479b7821..b194dad8a 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst @@ -1,34 +1,34 @@ -============== -Type Aliases -============== - --------------- -Type Aliases --------------- - -A type alias creates a name for another type. The two types can be used -interchangeably. - -.. code:: rust - - 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>>>; - ---------- -Details ---------- - -- A `newtype `__ is often a better alternative - since it creates a distinct type. Prefer - :rust:`struct InventoryCount(usize)` to :rust:`type InventoryCount = usize`. - -- C programmers will recognize this as similar to a :rust:`typedef`. +============== +Type Aliases +============== + +-------------- +Type Aliases +-------------- + +A type alias creates a name for another type. The two types can be used +interchangeably. + +.. code:: rust + + 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>>>; + +--------- +Details +--------- + +- A `newtype `__ is often a better alternative + since it creates a distinct type. Prefer + :rust:`struct InventoryCount(usize)` to :rust:`type InventoryCount = usize`. + +- C programmers will recognize this as similar to a :rust:`typedef`. 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 6b516a0a2..b34847954 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 @@ -1,51 +1,51 @@ -=========== -"const" -=========== - ------------ -"const" ------------ - -Constants are evaluated at compile time and their values are inlined -wherever they are used: - -.. code:: rust - - 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 :rust:`const` can be called at compile time to -generate :rust:`const` values. :rust:`const` functions can however be called at -runtime. - ---------- -Details ---------- - -- Mention that :rust:`const` behaves semantically similar to C++'s - :rust:`constexpr` +=========== +"const" +=========== + +----------- +"const" +----------- + +Constants are evaluated at compile time and their values are inlined +wherever they are used: + +.. code:: rust + + 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 :rust:`const` can be called at compile time to +generate :rust:`const` values. :rust:`const` functions can however be called at +runtime. + +--------- +Details +--------- + +- Mention that :rust:`const` behaves semantically similar to C++'s + :rust:`constexpr` 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 3c12488eb..ea7f2efac 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 @@ -1,49 +1,49 @@ -============ -"static" -============ - ------------- -"static" ------------- - -Static variables will live during the whole execution of the program, -and therefore will not move: - -.. code:: rust - - 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, -:rust:`const` is generally preferred. - ---------- -Details ---------- - -- :rust:`static` is similar to mutable global variables in C++. -- :rust:`static` provides object identity: an address in memory and state - as required by types with interior mutability such as :rust:`Mutex`. - ------------------ -More to Explore ------------------ - -Because :rust:`static` variables are accessible from any thread, they must -be :rust:`Sync`. Interior mutability is possible through a -`Mutex `__, -atomic or similar. - -It is common to use :rust:`OnceLock` in a static as a way to support -initialization on first use. :rust:`OnceCell` is not :rust:`Sync` and thus -cannot be used in this context. - -Thread-local data can be created with the macro :rust:`std::thread_local`. +============ +"static" +============ + +------------ +"static" +------------ + +Static variables will live during the whole execution of the program, +and therefore will not move: + +.. code:: rust + + 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, +:rust:`const` is generally preferred. + +--------- +Details +--------- + +- :rust:`static` is similar to mutable global variables in C++. +- :rust:`static` provides object identity: an address in memory and state + as required by types with interior mutability such as :rust:`Mutex`. + +----------------- +More to Explore +----------------- + +Because :rust:`static` variables are accessible from any thread, they must +be :rust:`Sync`. Interior mutability is possible through a +`Mutex `__, +atomic or similar. + +It is common to use :rust:`OnceLock` in a static as a way to support +initialization on first use. :rust:`OnceCell` is not :rust:`Sync` and thus +cannot be used in this context. + +Thread-local data can be created with the macro :rust:`std::thread_local`. 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 index 2dd9d48e3..0f8759018 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst @@ -1,46 +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 :rust:`#[derive(Debug)]` to allow the types to -be formatted with :rust:`{:?}`. - -This exercise only requires creating and populating data structures so -that :rust:`main` runs without errors. The next part of the course will -cover getting data out of these structures. - -:: - - {{#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}} +=========================== +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 :rust:`#[derive(Debug)]` to allow the types to +be formatted with :rust:`{:?}`. + +This exercise only requires creating and populating data structures so +that :rust:`main` runs without errors. The next part of the course will +cover getting data out of these structures. + +:: + + {{#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 index 6104446e0..f2268f09f 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching.rst @@ -1,43 +1,43 @@ -****************** -Pattern Matching -****************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +****************** +Pattern Matching +****************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index c146670be..383c2ddbe 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst @@ -1,81 +1,81 @@ -================= -Matching Values -================= - ------------------ -Matching Values ------------------ - -The :rust:`match` keyword lets you match a value against one or more -*patterns*. The patterns can be simple values, similarly to :rust:`switch` -in C and C++, but they can also be used to express more complex -conditions: - -.. code:: rust - - #[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 (:rust:`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. - ---------- -Details ---------- - -Key Points: - -- You might point out how some specific characters are being used when - in a pattern - - - :rust:`|` as an :rust:`or` - - :rust:`..` can expand as much as it needs to be - - :rust:`1..=5` represents an inclusive range - - :rust:`_` 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 :rust:`if` expression inside of the - match arm. An :rust:`if` expression inside of the branch block (after - :rust:`=>`) happens after the match arm is selected. Failing the :rust:`if` - condition inside of that block won't result in other arms of the - original :rust:`match` expression being considered. -- The condition defined in the guard applies to every expression in a - pattern with an :rust:`|`. - ------------------ -More To Explore ------------------ - -- Another piece of pattern syntax you can show students is the :rust:`@` - 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 :rust:`inner` has the value 123 which it pulled from the - :rust:`Option` via destructuring, :rust:`outer` captures the entire - :rust:`Some(inner)` expression, so it contains the full - :rust:`Option::Some(123)`. This is rarely used but can be useful in more - complex patterns. +================= +Matching Values +================= + +----------------- +Matching Values +----------------- + +The :rust:`match` keyword lets you match a value against one or more +*patterns*. The patterns can be simple values, similarly to :rust:`switch` +in C and C++, but they can also be used to express more complex +conditions: + +.. code:: rust + + #[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 (:rust:`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. + +--------- +Details +--------- + +Key Points: + +- You might point out how some specific characters are being used when + in a pattern + + - :rust:`|` as an :rust:`or` + - :rust:`..` can expand as much as it needs to be + - :rust:`1..=5` represents an inclusive range + - :rust:`_` 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 :rust:`if` expression inside of the + match arm. An :rust:`if` expression inside of the branch block (after + :rust:`=>`) happens after the match arm is selected. Failing the :rust:`if` + condition inside of that block won't result in other arms of the + original :rust:`match` expression being considered. +- The condition defined in the guard applies to every expression in a + pattern with an :rust:`|`. + +----------------- +More To Explore +----------------- + +- Another piece of pattern syntax you can show students is the :rust:`@` + 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 :rust:`inner` has the value 123 which it pulled from the + :rust:`Option` via destructuring, :rust:`outer` captures the entire + :rust:`Some(inner)` expression, so it contains the full + :rust:`Option::Some(123)`. This is rarely used but can be useful in more + complex patterns. 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 index 7f6ba5a8a..2708b9f76 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst @@ -1,25 +1,25 @@ -========= -Structs -========= - ---------- -Structs ---------- - -Like tuples, Struct can also be destructured by matching: - -.. code:: rust - - {{#include ../../third_party/rust-by-example/destructuring-structs.rs}} - ---------- -Details ---------- - -- Change the literal values in :rust:`foo` to match with the other - patterns. -- Add a new field to :rust:`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 :rust:`2` in the second arm to a variable, - and see that it subtly doesn't work. Change it to a :rust:`const` and see - it working again. +========= +Structs +========= + +--------- +Structs +--------- + +Like tuples, Struct can also be destructured by matching: + +.. code:: rust + + {{#include ../../third_party/rust-by-example/destructuring-structs.rs}} + +--------- +Details +--------- + +- Change the literal values in :rust:`foo` to match with the other + patterns. +- Add a new field to :rust:`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 :rust:`2` in the second arm to a variable, + and see that it subtly doesn't work. Change it to a :rust:`const` and see + it working again. 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 index 29d0ba1b9..9278b54ba 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst @@ -1,56 +1,56 @@ -======= -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 :rust:`enum` type: - -.. code:: rust - - 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 :rust:`Result` value. In the -first arm, :rust:`half` is bound to the value inside the :rust:`Ok` variant. In -the second arm, :rust:`msg` is bound to the error message. - ---------- -Details ---------- - -- The :rust:`if`/:rust:`else` expression is returning an enum that is later - unpacked with a :rust:`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. +======= +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 :rust:`enum` type: + +.. code:: rust + + 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 :rust:`Result` value. In the +first arm, :rust:`half` is bound to the value inside the :rust:`Ok` variant. In +the second arm, :rust:`msg` is bound to the error message. + +--------- +Details +--------- + +- The :rust:`if`/:rust:`else` expression is returning an enum that is later + unpacked with a :rust:`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. 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 93c5bb92b..737d310d7 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 @@ -1,142 +1,142 @@ -================== -Let Control Flow -================== - ------------------- -Let Control Flow ------------------- - -Rust has a few control flow constructs which differ from other -languages. They are used for pattern matching: - -- :rust:`if let` expressions -- :rust:`let else` expressions -- :rust:`while let` expressions - ------------------------- -"if let" expressions ------------------------- - -The -`if let expression `__ -lets you execute different code depending on whether a value matches a -pattern: - -.. code:: rust - - 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 --------------------------- - -For the common case of matching a pattern and returning from the -function, use -`let else `__. -The "else" case must diverge (:rust:`return`, :rust:`break`, or panic - anything -but falling off the end of the block). - -.. code:: rust - - 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 :rust:`if let`, there is a -`while let `__ -variant which repeatedly tests a value against a pattern: - -.. code:: rust - - fn main() { - let mut name = String::from("Comprehensive Rust"); - while let Some(c) = name.pop() { - println!("character: {c}"); - } - // (There are more efficient ways to reverse a string!) - } - -Here -`String::pop `__ -returns :rust:`Some(c)` until the string is empty, after which it will -return :rust:`None`. The :rust:`while let` lets us keep iterating through all -items. - --------- -if-let --------- - -- Unlike :rust:`match`, :rust:`if let` does not have to cover all branches. - This can make it more concise than :rust:`match`. -- A common usage is handling :rust:`Some` values when working with - :rust:`Option`. -- Unlike :rust:`match`, :rust:`if let` does not support guard clauses for - pattern matching. - ----------- -let-else ----------- - -:rust:`if-let`\ s can pile up, as shown. The :rust:`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 ------------ - -- Point out that the :rust:`while let` loop will keep going as long as the - value matches the pattern. -- You could rewrite the :rust:`while let` loop as an infinite loop with an - if statement that breaks when there is no value to unwrap for - :rust:`name.pop()`. The :rust:`while let` provides syntactic sugar for the - above scenario. +================== +Let Control Flow +================== + +------------------ +Let Control Flow +------------------ + +Rust has a few control flow constructs which differ from other +languages. They are used for pattern matching: + +- :rust:`if let` expressions +- :rust:`let else` expressions +- :rust:`while let` expressions + +------------------------ +"if let" expressions +------------------------ + +The +`if let expression `__ +lets you execute different code depending on whether a value matches a +pattern: + +.. code:: rust + + 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 +-------------------------- + +For the common case of matching a pattern and returning from the +function, use +`let else `__. +The "else" case must diverge (:rust:`return`, :rust:`break`, or panic - anything +but falling off the end of the block). + +.. code:: rust + + 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 :rust:`if let`, there is a +`while let `__ +variant which repeatedly tests a value against a pattern: + +.. code:: rust + + fn main() { + let mut name = String::from("Comprehensive Rust"); + while let Some(c) = name.pop() { + println!("character: {c}"); + } + // (There are more efficient ways to reverse a string!) + } + +Here +`String::pop `__ +returns :rust:`Some(c)` until the string is empty, after which it will +return :rust:`None`. The :rust:`while let` lets us keep iterating through all +items. + +-------- +if-let +-------- + +- Unlike :rust:`match`, :rust:`if let` does not have to cover all branches. + This can make it more concise than :rust:`match`. +- A common usage is handling :rust:`Some` values when working with + :rust:`Option`. +- Unlike :rust:`match`, :rust:`if let` does not support guard clauses for + pattern matching. + +---------- +let-else +---------- + +:rust:`if-let`\ s can pile up, as shown. The :rust:`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 +----------- + +- Point out that the :rust:`while let` loop will keep going as long as the + value matches the pattern. +- You could rewrite the :rust:`while let` loop as an infinite loop with an + if statement that breaks when there is no value to unwrap for + :rust:`name.pop()`. The :rust:`while let` provides syntactic sugar for the + above scenario. diff --git a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst index 536f6be95..c8ce0a4c0 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst @@ -1,80 +1,80 @@ -================================= -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 :rust:`10 + 20`, which -evaluates to :rust:`30`. We can represent the expression as a tree: - -.. code:: bob - - .-------. - .------ | + | ------. - | '-------' | - v v - .--------. .--------. - | 10 | | 20 | - '--------' '--------' - -A bigger and more complex expression would be -:rust:`(10 * 9) + ((3 - 4) * 5)`, which evaluate to :rust:`85`. We represent -this as a much bigger tree: - -.. 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: - -:: - - {{#include exercise.rs:Operation}} - - {{#include exercise.rs:Expression}} - -The :rust:`Box` type here is a smart pointer, and will be covered in detail -later in the course. An expression can be "boxed" with :rust:`Box::new` as -seen in the tests. To evaluate a boxed expression, use the deref -operator (:rust:`*`) to "unbox" it: :rust:`eval(*boxed_expr)`. - -Copy and paste the code into the Rust playground, and begin implementing -:rust:`eval`. The final product should pass the tests. It may be helpful to -use :rust:`todo!()` and get the tests to pass one-by-one. You can also skip -a test temporarily with :rust:`#[ignore]`: - -.. code:: none - - #[test] - #[ignore] - fn test_value() { .. } - -:: - - {{#include exercise.rs:Operation}} - - {{#include exercise.rs:Expression}} - - {{#include exercise.rs:eval}} - todo!() - } - - {{#include exercise.rs:tests}} +================================= +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 :rust:`10 + 20`, which +evaluates to :rust:`30`. We can represent the expression as a tree: + +.. code:: bob + + .-------. + .------ | + | ------. + | '-------' | + v v + .--------. .--------. + | 10 | | 20 | + '--------' '--------' + +A bigger and more complex expression would be +:rust:`(10 * 9) + ((3 - 4) * 5)`, which evaluate to :rust:`85`. We represent +this as a much bigger tree: + +.. 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: + +:: + + {{#include exercise.rs:Operation}} + + {{#include exercise.rs:Expression}} + +The :rust:`Box` type here is a smart pointer, and will be covered in detail +later in the course. An expression can be "boxed" with :rust:`Box::new` as +seen in the tests. To evaluate a boxed expression, use the deref +operator (:rust:`*`) to "unbox" it: :rust:`eval(*boxed_expr)`. + +Copy and paste the code into the Rust playground, and begin implementing +:rust:`eval`. The final product should pass the tests. It may be helpful to +use :rust:`todo!()` and get the tests to pass one-by-one. You can also skip +a test temporarily with :rust:`#[ignore]`: + +.. code:: none + + #[test] + #[ignore] + fn test_value() { .. } + +:: + + {{#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 index d2ae4c12f..3c5496e3e 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits.rst @@ -1,42 +1,42 @@ -******************** -Methods And Traits -******************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +******************** +Methods And Traits +******************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index d32de0718..05c2875da 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 @@ -1,101 +1,101 @@ -========= -Methods -========= - ---------- -Methods ---------- - -Rust allows you to associate functions with your new types. You do this -with an :rust:`impl` block: - -.. code:: rust - - #[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 :rust:`self` arguments specify the "receiver" - the object the method -acts on. There are several common receivers for a method: - -- :rust:`&self`: borrows the object from the caller using a shared and - immutable reference. The object can be used again afterwards. -- :rust:`&mut self`: borrows the object from the caller using a unique and - mutable reference. The object can be used again afterwards. -- :rust:`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. -- :rust:`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 :rust:`new` by convention. - ---------- -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 :rust:`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 :rust:`self`, a method receiver. - - - Show that it is an abbreviated term for :rust:`self: Self` and perhaps - show how the struct name could also be used. - - Explain that :rust:`Self` is a type alias for the type the :rust:`impl` - block is in and can be used elsewhere in the block. - - Note how :rust:`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 :rust:`&self` differs - from :rust:`self` by trying to run :rust:`finish` twice. - - Beyond variants on :rust:`self`, there are also - `special wrapper types `__ - allowed to be receiver types, such as :rust:`Box`. +========= +Methods +========= + +--------- +Methods +--------- + +Rust allows you to associate functions with your new types. You do this +with an :rust:`impl` block: + +.. code:: rust + + #[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 :rust:`self` arguments specify the "receiver" - the object the method +acts on. There are several common receivers for a method: + +- :rust:`&self`: borrows the object from the caller using a shared and + immutable reference. The object can be used again afterwards. +- :rust:`&mut self`: borrows the object from the caller using a unique and + mutable reference. The object can be used again afterwards. +- :rust:`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. +- :rust:`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 :rust:`new` by convention. + +--------- +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 :rust:`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 :rust:`self`, a method receiver. + + - Show that it is an abbreviated term for :rust:`self: Self` and perhaps + show how the struct name could also be used. + - Explain that :rust:`Self` is a type alias for the type the :rust:`impl` + block is in and can be used elsewhere in the block. + - Note how :rust:`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 :rust:`&self` differs + from :rust:`self` by trying to run :rust:`finish` twice. + - Beyond variants on :rust:`self`, there are also + `special wrapper types `__ + allowed to be receiver types, such as :rust:`Box`. 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 index ac09b597e..adbd03e96 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst @@ -1,161 +1,161 @@ -======== -Traits -======== - --------- -Traits --------- - -Rust lets you abstract over types with traits. They're similar to -interfaces: - -.. code:: rust - - trait Pet { - /// Return a sentence from this pet. - fn talk(&self) -> String; - - /// Print a string to the terminal greeting this pet. - fn greet(&self); - } - ----------------- -Traits 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. - ---------------------- -Implementing Traits ---------------------- - -.. code:: rust - - trait Pet { - fn talk(&self) -> String; - - fn greet(&self) { - println!("Oh you're a cutie! What's your name? {}", self.talk()); - } - } - - struct Dog { - name: String, - age: i8, - } - - impl Pet for Dog { - fn talk(&self) -> String { - format!("Woof, my name is {}!", self.name) - } - } - - fn main() { - let fido = Dog { name: String::from("Fido"), age: 5 }; - fido.greet(); - } - ----------------------- -Implementing Details ----------------------- - -- To implement :rust:`Trait` for :rust:`Type`, you use an - :rust:`impl Trait for Type { .. }` block. - -- Unlike Go interfaces, just having matching methods is not enough: a - :rust:`Cat` type with a :rust:`talk()` method would not automatically satisfy - :rust:`Pet` unless it is in an :rust:`impl Pet` block. - -- Traits may provide default implementations of some methods. Default - implementations can rely on all the methods of the trait. In this - case, :rust:`greet` is provided, and relies on :rust:`talk`. - -------------- -Supertraits -------------- - -A trait can require that types implementing it also implement other -traits, called *supertraits*. Here, any type implementing :rust:`Pet` must -implement :rust:`Animal`. - -.. code:: rust - - trait Animal { - fn leg_count(&self) -> u32; - } - - trait Pet: Animal { - fn name(&self) -> String; - } - - struct Dog(String); - - impl Animal for Dog { - fn leg_count(&self) -> u32 { - 4 - } - } - - impl Pet for Dog { - fn name(&self) -> String { - self.0.clone() - } - } - - fn main() { - let puppy = Dog(String::from("Rex")); - println!("{} has {} legs", puppy.name(), puppy.leg_count()); - } - ---------------------- -Supertraits Details ---------------------- - -This is sometimes called *trait inheritance* but students should not -expect this to behave like OO inheritance. It just specifies an -additional requirement on implementations of a trait. - ------------------- -Associated Types ------------------- - -Associated types are placeholder types which are supplied by the trait -implementation. - -.. code:: rust - - #[derive(Debug)] - struct Meters(i32); - #[derive(Debug)] - struct MetersSquared(i32); - - trait Multiply { - type Output; - fn multiply(&self, other: &Self) -> Self::Output; - } - - impl Multiply for Meters { - type Output = MetersSquared; - fn multiply(&self, other: &Self) -> Self::Output { - MetersSquared(self.0 * other.0) - } - } - - fn main() { - println!("{:?}", Meters(10).multiply(&Meters(20))); - } - ---------- -Details ---------- - -- Associated types are sometimes also called *output types*. The key - observation is that the implementer, not the caller, chooses this - type. - -- Many standard library traits have associated types, including - arithmetic operators and :rust:`Iterator`. +======== +Traits +======== + +-------- +Traits +-------- + +Rust lets you abstract over types with traits. They're similar to +interfaces: + +.. code:: rust + + trait Pet { + /// Return a sentence from this pet. + fn talk(&self) -> String; + + /// Print a string to the terminal greeting this pet. + fn greet(&self); + } + +---------------- +Traits 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. + +--------------------- +Implementing Traits +--------------------- + +.. code:: rust + + trait Pet { + fn talk(&self) -> String; + + fn greet(&self) { + println!("Oh you're a cutie! What's your name? {}", self.talk()); + } + } + + struct Dog { + name: String, + age: i8, + } + + impl Pet for Dog { + fn talk(&self) -> String { + format!("Woof, my name is {}!", self.name) + } + } + + fn main() { + let fido = Dog { name: String::from("Fido"), age: 5 }; + fido.greet(); + } + +---------------------- +Implementing Details +---------------------- + +- To implement :rust:`Trait` for :rust:`Type`, you use an + :rust:`impl Trait for Type { .. }` block. + +- Unlike Go interfaces, just having matching methods is not enough: a + :rust:`Cat` type with a :rust:`talk()` method would not automatically satisfy + :rust:`Pet` unless it is in an :rust:`impl Pet` block. + +- Traits may provide default implementations of some methods. Default + implementations can rely on all the methods of the trait. In this + case, :rust:`greet` is provided, and relies on :rust:`talk`. + +------------- +Supertraits +------------- + +A trait can require that types implementing it also implement other +traits, called *supertraits*. Here, any type implementing :rust:`Pet` must +implement :rust:`Animal`. + +.. code:: rust + + trait Animal { + fn leg_count(&self) -> u32; + } + + trait Pet: Animal { + fn name(&self) -> String; + } + + struct Dog(String); + + impl Animal for Dog { + fn leg_count(&self) -> u32 { + 4 + } + } + + impl Pet for Dog { + fn name(&self) -> String { + self.0.clone() + } + } + + fn main() { + let puppy = Dog(String::from("Rex")); + println!("{} has {} legs", puppy.name(), puppy.leg_count()); + } + +--------------------- +Supertraits Details +--------------------- + +This is sometimes called *trait inheritance* but students should not +expect this to behave like OO inheritance. It just specifies an +additional requirement on implementations of a trait. + +------------------ +Associated Types +------------------ + +Associated types are placeholder types which are supplied by the trait +implementation. + +.. code:: rust + + #[derive(Debug)] + struct Meters(i32); + #[derive(Debug)] + struct MetersSquared(i32); + + trait Multiply { + type Output; + fn multiply(&self, other: &Self) -> Self::Output; + } + + impl Multiply for Meters { + type Output = MetersSquared; + fn multiply(&self, other: &Self) -> Self::Output { + MetersSquared(self.0 * other.0) + } + } + + fn main() { + println!("{:?}", Meters(10).multiply(&Meters(20))); + } + +--------- +Details +--------- + +- Associated types are sometimes also called *output types*. The key + observation is that the implementer, not the caller, chooses this + type. + +- Many standard library traits have associated types, including + arithmetic operators and :rust:`Iterator`. 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 index 10a5b472f..d34dcbb57 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst @@ -1,36 +1,36 @@ -========== -Deriving -========== - ----------- -Deriving ----------- - -Supported traits can be automatically implemented for your custom types, -as follows: - -.. code:: rust - - #[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:?}"); - } - ---------- -Details ---------- - -Derivation is implemented with macros, and many crates provide useful -derive macros to add useful functionality. For example, :rust:`serde` can -derive serialization support for a struct using -:rust:`#[derive(Serialize)]`. +========== +Deriving +========== + +---------- +Deriving +---------- + +Supported traits can be automatically implemented for your custom types, +as follows: + +.. code:: rust + + #[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:?}"); + } + +--------- +Details +--------- + +Derivation is implemented with macros, and many crates provide useful +derive macros to add useful functionality. For example, :rust:`serde` can +derive serialization support for a struct using +:rust:`#[derive(Serialize)]`. 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 index 41900af46..d9eeda122 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst @@ -1,29 +1,29 @@ -======================== -Exercise: Logger Trait -======================== - ------------------------- -Exercise: Logger Trait ------------------------- - -Let's design a simple logging utility, using a trait :rust:`Logger` with a -:rust:`log` method. Code which might log its progress can then take an -:rust:`&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 :rust:`StderrLogger` given below logs all messages, regardless -of verbosity. Your task is to write a :rust:`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? - -:: - - {{#include exercise.rs:setup}} - - // TODO: Define and implement `VerbosityFilter`. - - {{#include exercise.rs:main}} +======================== +Exercise: Logger Trait +======================== + +------------------------ +Exercise: Logger Trait +------------------------ + +Let's design a simple logging utility, using a trait :rust:`Logger` with a +:rust:`log` method. Code which might log its progress can then take an +:rust:`&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 :rust:`StderrLogger` given below logs all messages, regardless +of verbosity. Your task is to write a :rust:`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? + +:: + + {{#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 index a08b63e3f..2b6d6b838 100644 --- a/courses/comprehensive_rust_training/100_generics.rst +++ b/courses/comprehensive_rust_training/100_generics.rst @@ -1,45 +1,45 @@ -********** -Generics -********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +********** +Generics +********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 127ae8ad3..fa999d5dc 100644 --- a/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst +++ b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst @@ -1,57 +1,57 @@ -=================== -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 - - /// 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")); - } - ---------- -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 :rust:`i32` and :rust:`&str` - for :rust:`T`, but we can use any type here, including user-defined - types: - - .. code:: rust - - 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 :rust:`pick` - to return :rust:`even + odd` if :rust:`n == 0`. Even if only the :rust:`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. +=================== +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 + + /// 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")); + } + +--------- +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 :rust:`i32` and :rust:`&str` + for :rust:`T`, but we can use any type here, including user-defined + types: + + .. code:: rust + + 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 :rust:`pick` + to return :rust:`even + odd` if :rust:`n == 0`. Even if only the :rust:`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. diff --git a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst index d34b387aa..fe6147c06 100644 --- a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst +++ b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst @@ -1,54 +1,54 @@ -==================== -Generic Data Types -==================== - --------------------- -Generic Data Types --------------------- - -You can use generics to abstract over the concrete field type: - -.. code:: rust - - #[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()); - } - ---------- -Details ---------- - -- *Q:* Why :rust:`T` is specified twice in :rust:`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 :rust:`T`. - - It is possible to write :rust:`impl Point { .. }`. - - - :rust:`Point` is still generic and you can use :rust:`Point`, but - methods in this block will only be available for - :rust:`Point`. - -- Try declaring a new variable :rust:`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., :rust:`T` and :rust:`U`. +==================== +Generic Data Types +==================== + +-------------------- +Generic Data Types +-------------------- + +You can use generics to abstract over the concrete field type: + +.. code:: rust + + #[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()); + } + +--------- +Details +--------- + +- *Q:* Why :rust:`T` is specified twice in :rust:`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 :rust:`T`. + - It is possible to write :rust:`impl Point { .. }`. + + - :rust:`Point` is still generic and you can use :rust:`Point`, but + methods in this block will only be available for + :rust:`Point`. + +- Try declaring a new variable :rust:`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., :rust:`T` and :rust:`U`. 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 032d8fd9c..c7381fd1b 100644 --- a/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst +++ b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst @@ -1,55 +1,55 @@ -================ -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 - - #[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:?}"); - } - ---------- -Details ---------- - -- The :rust:`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, :rust:`Foo::from("hello")` would not compile because - there is no :rust:`From<&str>` implementation for :rust:`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 `__. +================ +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 + + #[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:?}"); + } + +--------- +Details +--------- + +- The :rust:`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, :rust:`Foo::from("hello")` would not compile because + there is no :rust:`From<&str>` implementation for :rust:`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 `__. diff --git a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst index 724dfe5f2..5b11e69f4 100644 --- a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst +++ b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst @@ -1,56 +1,56 @@ -============== -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 :rust:`T: Trait`: - -.. code:: rust - - 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:?}"); - } - ---------- -Details ---------- - -- Try making a :rust:`NonCloneable` and passing it to :rust:`duplicate`. - -- When multiple traits are necessary, use :rust:`+` to join them. - -- Show a :rust:`where` clause, students will encounter it when reading - code. - - .. code:: rust - - 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 :rust:`Option`. - -- Note that Rust does not (yet) support specialization. For example, - given the original :rust:`duplicate`, it is invalid to add a specialized - :rust:`duplicate(a: u32)`. +============== +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 :rust:`T: Trait`: + +.. code:: rust + + 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:?}"); + } + +--------- +Details +--------- + +- Try making a :rust:`NonCloneable` and passing it to :rust:`duplicate`. + +- When multiple traits are necessary, use :rust:`+` to join them. + +- Show a :rust:`where` clause, students will encounter it when reading + code. + + .. code:: rust + + 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 :rust:`Option`. + +- Note that Rust does not (yet) support specialization. For example, + given the original :rust:`duplicate`, it is invalid to add a specialized + :rust:`duplicate(a: u32)`. diff --git a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst index ad19c6c54..a0c547eae 100644 --- a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst @@ -1,57 +1,57 @@ -================ -"impl Trait" -================ - ----------------- -"impl Trait" ----------------- - -Similar to trait bounds, an :rust:`impl Trait` syntax can be used in -function arguments and return values: - -.. code:: rust - - // 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:?}"); - } - ---------- -Details ---------- - -:rust:`impl Trait` allows you to work with types which you cannot name. The -meaning of :rust:`impl Trait` is a bit different in the different positions. - -- For a parameter, :rust:`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 - :rust:`impl Foo` picks the concrete type it returns, without writing it - out in the source. A function returning a generic type like - :rust:`collect() -> B` can return any type satisfying :rust:`B`, and the - caller may need to choose one, such as with - :rust:`let x: Vec<_> = foo.collect()` or with the turbofish, - :rust:`foo.collect::>()`. - -What is the type of :rust:`debuggable`? Try :rust:`let debuggable: () = ..` to -see what the error message shows. +================ +"impl Trait" +================ + +---------------- +"impl Trait" +---------------- + +Similar to trait bounds, an :rust:`impl Trait` syntax can be used in +function arguments and return values: + +.. code:: rust + + // 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:?}"); + } + +--------- +Details +--------- + +:rust:`impl Trait` allows you to work with types which you cannot name. The +meaning of :rust:`impl Trait` is a bit different in the different positions. + +- For a parameter, :rust:`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 + :rust:`impl Foo` picks the concrete type it returns, without writing it + out in the source. A function returning a generic type like + :rust:`collect() -> B` can return any type satisfying :rust:`B`, and the + caller may need to choose one, such as with + :rust:`let x: Vec<_> = foo.collect()` or with the turbofish, + :rust:`foo.collect::>()`. + +What is the type of :rust:`debuggable`? Try :rust:`let debuggable: () = ..` to +see what the error message shows. 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 b12fe8402..4964817b9 100644 --- a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst @@ -1,90 +1,90 @@ -=============== -"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 - - 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); - } - ---------- -Details ---------- - -- Generics, including :rust:`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 :rust:`dyn Trait`, it instead uses dynamic dispatch through a - `virtual method table `__ - (vtable). This means that there's a single version of :rust:`fn dynamic` - that is used regardless of what type of :rust:`Pet` is passed in. - -- When using :rust:`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 :rust:`Box` can also be used (this will be - demonstrated on day 3). - -- At runtime, a :rust:`&dyn Pet` is represented as a "fat pointer", i.e. a - pair of two pointers: One pointer points to the concrete object that - implements :rust:`Pet`, and the other points to the vtable for the trait - implementation for that type. When calling the :rust:`talk` method on - :rust:`&dyn Pet` the compiler looks up the function pointer for :rust:`talk` - in the vtable and then invokes the function, passing the pointer to - the :rust:`Dog` or :rust:`Cat` into that function. The compiler doesn't need - to know the concrete type of the :rust:`Pet` in order to do this. - -- A :rust:`dyn Trait` is considered to be "type-erased", because we no - longer have compile-time knowledge of what the concrete type is. +=============== +"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 + + 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); + } + +--------- +Details +--------- + +- Generics, including :rust:`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 :rust:`dyn Trait`, it instead uses dynamic dispatch through a + `virtual method table `__ + (vtable). This means that there's a single version of :rust:`fn dynamic` + that is used regardless of what type of :rust:`Pet` is passed in. + +- When using :rust:`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 :rust:`Box` can also be used (this will be + demonstrated on day 3). + +- At runtime, a :rust:`&dyn Pet` is represented as a "fat pointer", i.e. a + pair of two pointers: One pointer points to the concrete object that + implements :rust:`Pet`, and the other points to the vtable for the trait + implementation for that type. When calling the :rust:`talk` method on + :rust:`&dyn Pet` the compiler looks up the function pointer for :rust:`talk` + in the vtable and then invokes the function, passing the pointer to + the :rust:`Dog` or :rust:`Cat` into that function. The compiler doesn't need + to know the concrete type of the :rust:`Pet` in order to do this. + +- A :rust:`dyn Trait` is considered to be "type-erased", because we no + longer have compile-time knowledge of what the concrete type is. diff --git a/courses/comprehensive_rust_training/100_generics/07_exercise.rst b/courses/comprehensive_rust_training/100_generics/07_exercise.rst index e0887ca0e..37c889566 100644 --- a/courses/comprehensive_rust_training/100_generics/07_exercise.rst +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -1,30 +1,30 @@ -=========================== -Exercise: Generic "min" -=========================== - ---------------------------- -Exercise: Generic "min" ---------------------------- - -In this short exercise, you will implement a generic :rust:`min` function -that determines the minimum of two values, using the -`Ord `__ -trait. - -:: - - use std::cmp::Ordering; - - // TODO: implement the `min` function used in `main`. - - {{#include exercise.rs:main}} - ---------- -Details ---------- - -- Show students the - `Ord `__ - trait and - `Ordering `__ - enum. +=========================== +Exercise: Generic "min" +=========================== + +--------------------------- +Exercise: Generic "min" +--------------------------- + +In this short exercise, you will implement a generic :rust:`min` function +that determines the minimum of two values, using the +`Ord `__ +trait. + +:: + + use std::cmp::Ordering; + + // TODO: implement the `min` function used in `main`. + + {{#include exercise.rs:main}} + +--------- +Details +--------- + +- Show students the + `Ord `__ + trait and + `Ordering `__ + enum. diff --git a/courses/comprehensive_rust_training/110_std_types.rst b/courses/comprehensive_rust_training/110_std_types.rst index 56e4b1186..71b29dae5 100644 --- a/courses/comprehensive_rust_training/110_std_types.rst +++ b/courses/comprehensive_rust_training/110_std_types.rst @@ -1,46 +1,46 @@ -*********** -Std Types -*********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +*********** +Std Types +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 5901ea3ae..5bdc4c2ed 100644 --- a/courses/comprehensive_rust_training/110_std_types/01_std.rst +++ b/courses/comprehensive_rust_training/110_std_types/01_std.rst @@ -1,22 +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 :rust:`String` type. - -In fact, Rust contains several layers of the Standard Library: :rust:`core`, -:rust:`alloc` and :rust:`std`. - -- :rust:`core` includes the most basic types and functions that don't - depend on :rust:`libc`, allocator or even the presence of an operating - system. -- :rust:`alloc` includes types which require a global heap allocator, such - as :rust:`Vec`, :rust:`Box` and :rust:`Arc`. -- Embedded Rust applications often only use :rust:`core`, and sometimes - :rust:`alloc`. +================== +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 :rust:`String` type. + +In fact, Rust contains several layers of the Standard Library: :rust:`core`, +:rust:`alloc` and :rust:`std`. + +- :rust:`core` includes the most basic types and functions that don't + depend on :rust:`libc`, allocator or even the presence of an operating + system. +- :rust:`alloc` includes types which require a global heap allocator, such + as :rust:`Vec`, :rust:`Box` and :rust:`Arc`. +- Embedded Rust applications often only use :rust:`core`, and sometimes + :rust:`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 index a21b034e8..a39a35247 100644 --- a/courses/comprehensive_rust_training/110_std_types/02_docs.rst +++ b/courses/comprehensive_rust_training/110_std_types/02_docs.rst @@ -1,54 +1,54 @@ -=============== -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 :rust:`rustup doc --std` or https://std.rs to view the documentation. - -In fact, you can document your own code: - -.. code:: rust - - /// 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 -:rust:`//!` or :rust:`/*! .. */`, called "inner doc comments": - -.. code:: rust - - //! This module contains functionality relating to divisibility of integers. - ---------- -Details ---------- - -- Show students the generated docs for the :rust:`rand` crate at - https://docs.rs/rand. +=============== +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 :rust:`rustup doc --std` or https://std.rs to view the documentation. + +In fact, you can document your own code: + +.. code:: rust + + /// 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 +:rust:`//!` or :rust:`/*! .. */`, called "inner doc comments": + +.. code:: rust + + //! This module contains functionality relating to divisibility of integers. + +--------- +Details +--------- + +- Show students the generated docs for the :rust:`rand` crate at + https://docs.rs/rand. 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 a8e584b17..e2705a0fe 100644 --- a/courses/comprehensive_rust_training/110_std_types/03_option.rst +++ b/courses/comprehensive_rust_training/110_std_types/03_option.rst @@ -1,44 +1,44 @@ -======== -Option -======== - --------- -Option --------- - -We have already seen some use of :rust:`Option`. It stores either a value -of type :rust:`T` or nothing. For example, -`String::find `__ -returns an :rust:`Option`. - -.. code:: rust - - fn main() { - 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'); - println!("find returned {position:?}"); - assert_eq!(position.expect("Character not found"), 0); - } - ---------- -Details ---------- - -- :rust:`Option` is widely used, not just in the standard library. -- :rust:`unwrap` will return the value in an :rust:`Option`, or panic. - :rust:`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 :rust:`unwrap`/:rust:`expect` all over the place when - hacking something together, but production code typically handles - :rust:`None` in a nicer fashion. - -- The "niche optimization" means that :rust:`Option` often has the same - size in memory as :rust:`T`, if there is some representation that is not - a valid value of T. For example, a reference cannot be NULL, so - :rust:`Option<&T>` automatically uses NULL to represent the :rust:`None` - variant, and thus can be stored in the same memory as :rust:`&T`. +======== +Option +======== + +-------- +Option +-------- + +We have already seen some use of :rust:`Option`. It stores either a value +of type :rust:`T` or nothing. For example, +`String::find `__ +returns an :rust:`Option`. + +.. code:: rust + + fn main() { + 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'); + println!("find returned {position:?}"); + assert_eq!(position.expect("Character not found"), 0); + } + +--------- +Details +--------- + +- :rust:`Option` is widely used, not just in the standard library. +- :rust:`unwrap` will return the value in an :rust:`Option`, or panic. + :rust:`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 :rust:`unwrap`/:rust:`expect` all over the place when + hacking something together, but production code typically handles + :rust:`None` in a nicer fashion. + +- The "niche optimization" means that :rust:`Option` often has the same + size in memory as :rust:`T`, if there is some representation that is not + a valid value of T. For example, a reference cannot be NULL, so + :rust:`Option<&T>` automatically uses NULL to represent the :rust:`None` + variant, and thus can be stored in the same memory as :rust:`&T`. diff --git a/courses/comprehensive_rust_training/110_std_types/04_result.rst b/courses/comprehensive_rust_training/110_std_types/04_result.rst index 4046cd4fc..a8206af2c 100644 --- a/courses/comprehensive_rust_training/110_std_types/04_result.rst +++ b/courses/comprehensive_rust_training/110_std_types/04_result.rst @@ -1,49 +1,49 @@ -======== -Result -======== - --------- -Result --------- - -:rust:`Result` is similar to :rust:`Option`, but indicates the success or -failure of an operation, each with a different enum variant. It is -generic: :rust:`Result` where :rust:`T` is used in the :rust:`Ok` variant and -:rust:`E` appears in the :rust:`Err` variant. - -.. code:: rust - - 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}"); - } - } - } - ---------- -Details ---------- - -- As with :rust:`Option`, the successful value sits inside of :rust:`Result`, - forcing the developer to explicitly extract it. This encourages error - checking. In the case where an error should never happen, - :rust:`unwrap()` or :rust:`expect()` can be called, and this is a signal of - the developer intent too. -- :rust:`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. -- :rust:`Result` is the standard type to implement error handling as we - will see on Day 4. +======== +Result +======== + +-------- +Result +-------- + +:rust:`Result` is similar to :rust:`Option`, but indicates the success or +failure of an operation, each with a different enum variant. It is +generic: :rust:`Result` where :rust:`T` is used in the :rust:`Ok` variant and +:rust:`E` appears in the :rust:`Err` variant. + +.. code:: rust + + 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}"); + } + } + } + +--------- +Details +--------- + +- As with :rust:`Option`, the successful value sits inside of :rust:`Result`, + forcing the developer to explicitly extract it. This encourages error + checking. In the case where an error should never happen, + :rust:`unwrap()` or :rust:`expect()` can be called, and this is a signal of + the developer intent too. +- :rust:`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. +- :rust:`Result` is the standard type to implement error handling as we + will see on Day 4. 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 c55f5ea31..9cb65d412 100644 --- a/courses/comprehensive_rust_training/110_std_types/05_string.rst +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -1,71 +1,71 @@ -======== -String -======== - --------- -String --------- - -`String `__ -is a growable UTF-8 encoded string: - -.. code:: rust - - 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("a z"); - println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count()); - } - -:rust:`String` implements -`Deref `__, -which means that you can call all :rust:`str` methods on a :rust:`String`. - ---------- -Details ---------- - -- :rust:`String::new` returns a new empty string, use - :rust:`String::with_capacity` when you know how much data you want to - push to the string. -- :rust:`String::len` returns the size of the :rust:`String` in bytes (which - can be different from its length in characters). -- :rust:`String::chars` returns an iterator over the actual characters. - Note that a :rust:`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 - :rust:`&str` or :rust:`String`. -- When a type implements :rust:`Deref`, the compiler will let - you transparently call methods from :rust:`T`. - - - We haven't discussed the :rust:`Deref` trait yet, so at this point - this mostly explains the structure of the sidebar in the - documentation. - - :rust:`String` implements :rust:`Deref` which transparently - gives it access to :rust:`str`\ 's methods. - - Write and compare :rust:`let s3 = s1.deref();` and :rust:`let s3 = &*s1;`. - -- :rust:`String` is implemented as a wrapper around a vector of bytes, many - of the operations you see supported on vectors are also supported on - :rust:`String`, but with some extra guarantees. -- Compare the different ways to index a :rust:`String`: - - - To a character by using :rust:`s3.chars().nth(i).unwrap()` where :rust:`i` - is in-bound, out-of-bounds. - - To a substring by using :rust:`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 :rust:`Display`, so anything that can be formatted can also be - converted to a string. +======== +String +======== + +-------- +String +-------- + +`String `__ +is a growable UTF-8 encoded string: + +.. code:: rust + + 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("a z"); + println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count()); + } + +:rust:`String` implements +`Deref `__, +which means that you can call all :rust:`str` methods on a :rust:`String`. + +--------- +Details +--------- + +- :rust:`String::new` returns a new empty string, use + :rust:`String::with_capacity` when you know how much data you want to + push to the string. +- :rust:`String::len` returns the size of the :rust:`String` in bytes (which + can be different from its length in characters). +- :rust:`String::chars` returns an iterator over the actual characters. + Note that a :rust:`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 + :rust:`&str` or :rust:`String`. +- When a type implements :rust:`Deref`, the compiler will let + you transparently call methods from :rust:`T`. + + - We haven't discussed the :rust:`Deref` trait yet, so at this point + this mostly explains the structure of the sidebar in the + documentation. + - :rust:`String` implements :rust:`Deref` which transparently + gives it access to :rust:`str`\ 's methods. + - Write and compare :rust:`let s3 = s1.deref();` and :rust:`let s3 = &*s1;`. + +- :rust:`String` is implemented as a wrapper around a vector of bytes, many + of the operations you see supported on vectors are also supported on + :rust:`String`, but with some extra guarantees. +- Compare the different ways to index a :rust:`String`: + + - To a character by using :rust:`s3.chars().nth(i).unwrap()` where :rust:`i` + is in-bound, out-of-bounds. + - To a substring by using :rust:`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 :rust:`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 53423bbfd..1a3a517cf 100644 --- a/courses/comprehensive_rust_training/110_std_types/06_vec.rst +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -1,58 +1,58 @@ -========= -"Vec" -========= - ---------- -"Vec" ---------- - -`Vec `__ is the -standard resizable heap-allocated buffer: - -.. code:: rust - - 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:?}"); - } - -:rust:`Vec` implements -`Deref `__, -which means that you can call slice methods on a :rust:`Vec`. - ---------- -Details ---------- - -- :rust:`Vec` is a type of collection, along with :rust:`String` and - :rust:`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 :rust:`Vec` is a generic type too, but you don't have to - specify :rust:`T` explicitly. As always with Rust type inference, the - :rust:`T` was established during the first :rust:`push` call. -- :rust:`vec![...]` is a canonical macro to use instead of :rust:`Vec::new()` - and it supports adding initial elements to the vector. -- To index the vector you use :rust:`[` :rust:`]`, but they will panic if out - of bounds. Alternatively, using :rust:`get` will return an :rust:`Option`. - The :rust:`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 :rust:`Vec` gives access to all of the documented slice - methods, too. +========= +"Vec" +========= + +--------- +"Vec" +--------- + +`Vec `__ is the +standard resizable heap-allocated buffer: + +.. code:: rust + + 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:?}"); + } + +:rust:`Vec` implements +`Deref `__, +which means that you can call slice methods on a :rust:`Vec`. + +--------- +Details +--------- + +- :rust:`Vec` is a type of collection, along with :rust:`String` and + :rust:`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 :rust:`Vec` is a generic type too, but you don't have to + specify :rust:`T` explicitly. As always with Rust type inference, the + :rust:`T` was established during the first :rust:`push` call. +- :rust:`vec![...]` is a canonical macro to use instead of :rust:`Vec::new()` + and it supports adding initial elements to the vector. +- To index the vector you use :rust:`[` :rust:`]`, but they will panic if out + of bounds. Alternatively, using :rust:`get` will return an :rust:`Option`. + The :rust:`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 :rust:`Vec` gives access to all of the documented slice + methods, too. 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 3cf77dba9..95e58e9e7 100644 --- a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -1,86 +1,86 @@ -============= -"HashMap" -============= - -------------- -"HashMap" -------------- - -Standard hash map with protection against HashDoS attacks: - -.. code:: rust - - 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 Miserables") { - println!( - "We know about {} books, but not Les Miserables.", - 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:#?}"); - } - ---------- -Details ---------- - -- :rust:`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 - - 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 :rust:`vec!`, there is unfortunately no standard :rust:`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 - - 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 :rust:`Iterator` which yields - key-value tuples. - -- This type has several "method-specific" return types, such as - :rust:`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 :rust:`keys` method. +============= +"HashMap" +============= + +------------- +"HashMap" +------------- + +Standard hash map with protection against HashDoS attacks: + +.. code:: rust + + 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 Miserables") { + println!( + "We know about {} books, but not Les Miserables.", + 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:#?}"); + } + +--------- +Details +--------- + +- :rust:`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 + + 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 :rust:`vec!`, there is unfortunately no standard :rust:`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 + + 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 :rust:`Iterator` which yields + key-value tuples. + +- This type has several "method-specific" return types, such as + :rust:`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 :rust:`keys` method. 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 bfcd1142c..aa8ca503d 100644 --- a/courses/comprehensive_rust_training/110_std_types/08_exercise.rst +++ b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst @@ -1,56 +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 :rust:`Counter` is hard coded to only work for -:rust:`u32` values. Make the struct and its methods generic over the type of -value being tracked, that way :rust:`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 -:rust:`count` method. - -:: - - 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}} +=================== +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 :rust:`Counter` is hard coded to only work for +:rust:`u32` values. Make the struct and its methods generic over the type of +value being tracked, that way :rust:`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 +:rust:`count` method. + +:: + + 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 index 7025ac99a..17cfaca79 100644 --- a/courses/comprehensive_rust_training/120_std_traits.rst +++ b/courses/comprehensive_rust_training/120_std_traits.rst @@ -1,46 +1,46 @@ -************ -Std Traits -************ - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************ +Std Traits +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 0354ac9c0..139c274d7 100644 --- a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst +++ b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst @@ -1,83 +1,83 @@ -============= -Comparisons -============= - -------------- -Comparisons -------------- - -These traits support comparisons between values. All traits can be -derived for types containing fields that implement these traits. - --------------------------- -"PartialEq" and "Eq" --------------------------- - -:rust:`PartialEq` is a partial equivalence relation, with required method -:rust:`eq` and provided method :rust:`ne`. The :rust:`==` and :rust:`!=` operators will -call these methods. - -.. code:: rust - - struct Key { - id: u32, - metadata: Option, - } - impl PartialEq for Key { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } - } - -:rust:`Eq` is a full equivalence relation (reflexive, symmetric, and -transitive) and implies :rust:`PartialEq`. Functions that require full -equivalence will use :rust:`Eq` as a trait bound. - ----------------------------- -"PartialOrd" and "Ord" ----------------------------- - -:rust:`PartialOrd` defines a partial ordering, with a :rust:`partial_cmp` -method. It is used to implement the :rust:`<`, :rust:`<=`, :rust:`>=`, and :rust:`>` -operators. - -.. code:: rust - - 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, - } - } - } - -:rust:`Ord` is a total ordering, with :rust:`cmp` returning :rust:`Ordering`. - ---------- -Details ---------- - -:rust:`PartialEq` can be implemented between different types, but :rust:`Eq` -cannot, because it is reflexive: - -.. code:: rust - - 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. +============= +Comparisons +============= + +------------- +Comparisons +------------- + +These traits support comparisons between values. All traits can be +derived for types containing fields that implement these traits. + +-------------------------- +"PartialEq" and "Eq" +-------------------------- + +:rust:`PartialEq` is a partial equivalence relation, with required method +:rust:`eq` and provided method :rust:`ne`. The :rust:`==` and :rust:`!=` operators will +call these methods. + +.. code:: rust + + struct Key { + id: u32, + metadata: Option, + } + impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } + } + +:rust:`Eq` is a full equivalence relation (reflexive, symmetric, and +transitive) and implies :rust:`PartialEq`. Functions that require full +equivalence will use :rust:`Eq` as a trait bound. + +---------------------------- +"PartialOrd" and "Ord" +---------------------------- + +:rust:`PartialOrd` defines a partial ordering, with a :rust:`partial_cmp` +method. It is used to implement the :rust:`<`, :rust:`<=`, :rust:`>=`, and :rust:`>` +operators. + +.. code:: rust + + 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, + } + } + } + +:rust:`Ord` is a total ordering, with :rust:`cmp` returning :rust:`Ordering`. + +--------- +Details +--------- + +:rust:`PartialEq` can be implemented between different types, but :rust:`Eq` +cannot, because it is reflexive: + +.. code:: rust + + 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. 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 53c974308..4279411f2 100644 --- a/courses/comprehensive_rust_training/120_std_traits/02_operators.rst +++ b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst @@ -1,61 +1,61 @@ -=========== -Operators -=========== - ------------ -Operators ------------ - -Operator overloading is implemented via traits in -`std::ops `__: - -.. code:: rust - - #[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); - } - ---------- -Details ---------- - -Discussion points: - -- You could implement :rust:`Add` for :rust:`&Point`. In which situations is - that useful? - - - Answer: :rust:`Add:add` consumes :rust:`self`. If type :rust:`T` for which you - are overloading the operator is not :rust:`Copy`, you should consider - overloading the operator for :rust:`&T` as well. This avoids - unnecessary cloning on the call site. - -- Why is :rust:`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 :rust:`Output`) are controlled by - the implementer of a trait. - -- You could implement :rust:`Add` for two different types, e.g. - :rust:`impl Add<(i32, i32)> for Point` would add a tuple to a :rust:`Point`. - -The :rust:`Not` trait (:rust:`!` 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: :rust:`!5 == -6`. +=========== +Operators +=========== + +----------- +Operators +----------- + +Operator overloading is implemented via traits in +`std::ops `__: + +.. code:: rust + + #[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); + } + +--------- +Details +--------- + +Discussion points: + +- You could implement :rust:`Add` for :rust:`&Point`. In which situations is + that useful? + + - Answer: :rust:`Add:add` consumes :rust:`self`. If type :rust:`T` for which you + are overloading the operator is not :rust:`Copy`, you should consider + overloading the operator for :rust:`&T` as well. This avoids + unnecessary cloning on the call site. + +- Why is :rust:`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 :rust:`Output`) are controlled by + the implementer of a trait. + +- You could implement :rust:`Add` for two different types, e.g. + :rust:`impl Add<(i32, i32)> for Point` would add a tuple to a :rust:`Point`. + +The :rust:`Not` trait (:rust:`!` 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: :rust:`!5 == -6`. 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 f11728265..9f6bfe662 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 @@ -1,49 +1,49 @@ -======================= -"From" and "Into" -======================= - ------------------------ -"From" and "Into" ------------------------ - -Types implement -`From `__ and -`Into `__ to -facilitate type conversions. Unlike :rust:`as`, these traits correspond to -lossless, infallible conversions. - -.. code:: rust - - 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 - - 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}"); - } - ---------- -Details ---------- - -- That's why it is common to only implement :rust:`From`, as your type will - get :rust:`Into` implementation too. -- When declaring a function argument input type like "anything that can - be converted into a :rust:`String`", the rule is opposite, you should use - :rust:`Into`. Your function will accept types that implement :rust:`From` and - those that *only* implement :rust:`Into`. +======================= +"From" and "Into" +======================= + +----------------------- +"From" and "Into" +----------------------- + +Types implement +`From `__ and +`Into `__ to +facilitate type conversions. Unlike :rust:`as`, these traits correspond to +lossless, infallible conversions. + +.. code:: rust + + 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 + + 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}"); + } + +--------- +Details +--------- + +- That's why it is common to only implement :rust:`From`, as your type will + get :rust:`Into` implementation too. +- When declaring a function argument input type like "anything that can + be converted into a :rust:`String`", the rule is opposite, you should use + :rust:`Into`. Your function will accept types that implement :rust:`From` and + those that *only* implement :rust:`Into`. 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 aaa76e273..ac28d9039 100644 --- a/courses/comprehensive_rust_training/120_std_traits/04_casting.rst +++ b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst @@ -1,47 +1,47 @@ -========= -Casting -========= - ---------- -Casting ---------- - -Rust has no *implicit* type conversions, but does support explicit casts -with :rust:`as`. These generally follow C semantics where those are defined. - -.. code:: rust - - 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 :rust:`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 - check the docs, and comment for clarity. - -Casting with :rust:`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 :rust:`u64` with -:rust:`as u32`, regardless of what was in the high bits). - -For infallible casts (e.g. :rust:`u32` to :rust:`u64`), prefer using :rust:`From` or -:rust:`Into` over :rust:`as` to confirm that the cast is in fact infallible. For -fallible casts, :rust:`TryFrom` and :rust:`TryInto` are available when you want -to handle casts that fit differently from those that don't. - ---------- -Details ---------- - -Consider taking a break after this slide. - -:rust:`as` is similar to a C++ static cast. Use of :rust:`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 :rust:`usize` for use as an index. +========= +Casting +========= + +--------- +Casting +--------- + +Rust has no *implicit* type conversions, but does support explicit casts +with :rust:`as`. These generally follow C semantics where those are defined. + +.. code:: rust + + 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 :rust:`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 - check the docs, and comment for clarity. + +Casting with :rust:`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 :rust:`u64` with +:rust:`as u32`, regardless of what was in the high bits). + +For infallible casts (e.g. :rust:`u32` to :rust:`u64`), prefer using :rust:`From` or +:rust:`Into` over :rust:`as` to confirm that the cast is in fact infallible. For +fallible casts, :rust:`TryFrom` and :rust:`TryInto` are available when you want +to handle casts that fit differently from those that don't. + +--------- +Details +--------- + +Consider taking a break after this slide. + +:rust:`as` is similar to a C++ static cast. Use of :rust:`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 :rust:`usize` for use as an index. 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 c39d2b4ca..39626bffd 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 @@ -1,51 +1,51 @@ -======================== -"Read" and "Write" -======================== - ------------------------- -"Read" and "Write" ------------------------- - -Using `Read `__ -and -`BufRead `__, -you can abstract over :rust:`u8` sources: - -.. code:: rust - - 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 :rust:`u8` sinks: - -.. code:: rust - - 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(()) - } +======================== +"Read" and "Write" +======================== + +------------------------ +"Read" and "Write" +------------------------ + +Using `Read `__ +and +`BufRead `__, +you can abstract over :rust:`u8` sources: + +.. code:: rust + + 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 :rust:`u8` sinks: + +.. code:: rust + + 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 index 520647705..b029c6767 100644 --- a/courses/comprehensive_rust_training/120_std_traits/06_default.rst +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -1,59 +1,59 @@ -======================= -The "Default" Trait -======================= - ------------------------ -The "Default" Trait ------------------------ - -`Default `__ -trait produces a default value for a type. - -.. code:: rust - - #[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()); - } - ---------- -Details ---------- - -- It can be implemented directly or it can be derived via - :rust:`#[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 :rust:`Default` too. - -- Standard Rust types often implement :rust:`Default` with reasonable - values (e.g. :rust:`0`, :rust:`""`, etc). -- The partial struct initialization works nicely with default. -- The Rust standard library is aware that types can implement - :rust:`Default` and provides convenience methods that use it. -- The :rust:`..` syntax is called - `struct update syntax `__. +======================= +The "Default" Trait +======================= + +----------------------- +The "Default" Trait +----------------------- + +`Default `__ +trait produces a default value for a type. + +.. code:: rust + + #[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()); + } + +--------- +Details +--------- + +- It can be implemented directly or it can be derived via + :rust:`#[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 :rust:`Default` too. + +- Standard Rust types often implement :rust:`Default` with reasonable + values (e.g. :rust:`0`, :rust:`""`, etc). +- The partial struct initialization works nicely with default. +- The Rust standard library is aware that types can implement + :rust:`Default` and provides convenience methods that use it. +- The :rust:`..` syntax is called + `struct update syntax `__. 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 8e77d95fb..05335fb7c 100644 --- a/courses/comprehensive_rust_training/120_std_traits/07_closures.rst +++ b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst @@ -1,87 +1,87 @@ -========== -Closures -========== - ----------- -Closures ----------- - -Closures or lambda expressions have types which cannot be named. -However, they implement special -`Fn `__, -`FnMut `__, and -`FnOnce `__ -traits: - -.. code:: rust - - 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); - } - ---------- -Details ---------- - -An :rust:`Fn` (e.g. :rust:`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 :rust:`FnMut` (e.g. :rust:`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 :rust:`FnOnce` (e.g. :rust:`multiply_sum`), you may only call it -once. Doing so consumes the closure and any values captured by move. - -:rust:`FnMut` is a subtype of :rust:`FnOnce`. :rust:`Fn` is a subtype of :rust:`FnMut` -and :rust:`FnOnce`. I.e. you can use an :rust:`FnMut` wherever an :rust:`FnOnce` is -called for, and you can use an :rust:`Fn` wherever an :rust:`FnMut` or -:rust:`FnOnce` is called for. - -When you define a function that takes a closure, you should take -:rust:`FnOnce` if you can (i.e. you call it once), or :rust:`FnMut` else, and -last :rust:`Fn`. This allows the most flexibility for the caller. - -In contrast, when you have a closure, the most flexible you can have is -:rust:`Fn` (which can be passed to a consumer of any of the 3 closure -traits), then :rust:`FnMut`, and lastly :rust:`FnOnce`. - -The compiler also infers :rust:`Copy` (e.g. for :rust:`add_3`) and :rust:`Clone` -(e.g. :rust:`multiply_sum`), depending on what the closure captures. -Function pointers (references to :rust:`fn` items) implement :rust:`Copy` and -:rust:`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 :rust:`move` keyword -forces capture by value. - -.. code:: rust - - 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"); - } +========== +Closures +========== + +---------- +Closures +---------- + +Closures or lambda expressions have types which cannot be named. +However, they implement special +`Fn `__, +`FnMut `__, and +`FnOnce `__ +traits: + +.. code:: rust + + 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); + } + +--------- +Details +--------- + +An :rust:`Fn` (e.g. :rust:`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 :rust:`FnMut` (e.g. :rust:`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 :rust:`FnOnce` (e.g. :rust:`multiply_sum`), you may only call it +once. Doing so consumes the closure and any values captured by move. + +:rust:`FnMut` is a subtype of :rust:`FnOnce`. :rust:`Fn` is a subtype of :rust:`FnMut` +and :rust:`FnOnce`. I.e. you can use an :rust:`FnMut` wherever an :rust:`FnOnce` is +called for, and you can use an :rust:`Fn` wherever an :rust:`FnMut` or +:rust:`FnOnce` is called for. + +When you define a function that takes a closure, you should take +:rust:`FnOnce` if you can (i.e. you call it once), or :rust:`FnMut` else, and +last :rust:`Fn`. This allows the most flexibility for the caller. + +In contrast, when you have a closure, the most flexible you can have is +:rust:`Fn` (which can be passed to a consumer of any of the 3 closure +traits), then :rust:`FnMut`, and lastly :rust:`FnOnce`. + +The compiler also infers :rust:`Copy` (e.g. for :rust:`add_3`) and :rust:`Clone` +(e.g. :rust:`multiply_sum`), depending on what the closure captures. +Function pointers (references to :rust:`fn` items) implement :rust:`Copy` and +:rust:`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 :rust:`move` keyword +forces capture by value. + +.. code:: rust + + 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"); + } 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 81e247ea5..98ded3cdb 100644 --- a/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst +++ b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst @@ -1,23 +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. - -:: - - {{#include exercise.rs:head }} - - // Implement the `Read` trait for `RotDecoder`. - - {{#include exercise.rs:main }} - -What happens if you chain two :rust:`RotDecoder` instances together, each -rotating by 13 characters? +================= +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. + +:: + + {{#include exercise.rs:head }} + + // Implement the `Read` trait for `RotDecoder`. + + {{#include exercise.rs:main }} + +What happens if you chain two :rust:`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 index 328a173c9..129b8fbc6 100644 --- a/courses/comprehensive_rust_training/130_memory_management.rst +++ b/courses/comprehensive_rust_training/130_memory_management.rst @@ -1,46 +1,46 @@ -******************* -Memory Management -******************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +******************* +Memory Management +******************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index d8928edb0..0498bf993 100644 --- a/courses/comprehensive_rust_training/130_memory_management/01_review.rst +++ b/courses/comprehensive_rust_training/130_memory_management/01_review.rst @@ -1,85 +1,85 @@ -========================== -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 :rust:`String` puts fixed-sized metadata on the stack and -dynamically sized data, the actual string, on the heap: - -.. code:: rust - - fn main() { - let s1 = String::from("Hello"); - } - -.. code:: bob - - Stack - .- - - - - - - - - - - - - -. Heap - : : .- - - - - - - - - - - - - - - -. - : s1 : : : - : +-----------+-------+ : : : - : | capacity | 5 | : : +----+----+----+----+----+ : - : | ptr | o-+---+-----+-->| H | e | l | l | o | : - : | len | 5 | : : +----+----+----+----+----+ : - : +-----------+-------+ : : : - : : : : - `- - - - - - - - - - - - - -' `- - - - - - - - - - - - - - - -' - ---------- -Details ---------- - -- Mention that a :rust:`String` is backed by a :rust:`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 :rust:`unsafe` Rust. However, you -should point out that this is rightfully unsafe! - -.. code:: rust - - 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}"); - } - } +========================== +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 :rust:`String` puts fixed-sized metadata on the stack and +dynamically sized data, the actual string, on the heap: + +.. code:: rust + + fn main() { + let s1 = String::from("Hello"); + } + +.. code:: bob + + Stack + .- - - - - - - - - - - - - -. Heap + : : .- - - - - - - - - - - - - - - -. + : s1 : : : + : +-----------+-------+ : : : + : | capacity | 5 | : : +----+----+----+----+----+ : + : | ptr | o-+---+-----+-->| H | e | l | l | o | : + : | len | 5 | : : +----+----+----+----+----+ : + : +-----------+-------+ : : : + : : : : + `- - - - - - - - - - - - - -' `- - - - - - - - - - - - - - - -' + +--------- +Details +--------- + +- Mention that a :rust:`String` is backed by a :rust:`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 :rust:`unsafe` Rust. However, you +should point out that this is rightfully unsafe! + +.. code:: rust + + 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}"); + } + } 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 9ddeede66..0096ca210 100644 --- a/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst +++ b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst @@ -1,61 +1,61 @@ -================================= -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. - ---------- -Details ---------- - -This slide is intended to help students coming from other languages to -put Rust in context. - -- C must manage heap manually with :rust:`malloc` and :rust:`free`. Common - errors include forgetting to call :rust:`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 (:rust:`unique_ptr`, :rust:`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 - 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). +================================= +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. + +--------- +Details +--------- + +This slide is intended to help students coming from other languages to +put Rust in context. + +- C must manage heap manually with :rust:`malloc` and :rust:`free`. Common + errors include forgetting to call :rust:`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 (:rust:`unique_ptr`, :rust:`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 - 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/130_memory_management/03_ownership.rst b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst index 976548aff..2aad9aa22 100644 --- a/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst +++ b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst @@ -1,36 +1,36 @@ -=========== -Ownership -=========== - ------------ -Ownership ------------ - -All variable bindings have a *scope* where they are valid and it is an -error to use a variable outside its scope: - -.. code:: rust - - 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. - ---------- -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. +=========== +Ownership +=========== + +----------- +Ownership +----------- + +All variable bindings have a *scope* where they are valid and it is an +error to use a variable outside its scope: + +.. code:: rust + + 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. + +--------- +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. 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 8e20b7a5a..102f3b8ff 100644 --- a/courses/comprehensive_rust_training/130_memory_management/04_move.rst +++ b/courses/comprehensive_rust_training/130_memory_management/04_move.rst @@ -1,182 +1,182 @@ -================ -Move Semantics -================ - ----------------- -Move Semantics ----------------- - -An assignment will transfer *ownership* between variables: - -.. code:: rust - - fn main() { - let s1: String = String::from("Hello!"); - let s2: String = s1; - println!("s2: {s2}"); - // println!("s1: {s1}"); - } - -- The assignment of :rust:`s1` to :rust:`s2` transfers ownership. -- When :rust:`s1` goes out of scope, nothing happens: it does not own - anything. -- When :rust:`s2` goes out of scope, the string data is freed. - -Before move to :rust:`s2`: - -.. code:: bob - - Stack Heap - .- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -. - : : : : - : s1 : : : - : +-----------+-------+ : : +----+----+----+----+----+----+ : - : | ptr | o---+---+-----+-->| H | e | l | l | o | ! | : - : | len | 6 | : : +----+----+----+----+----+----+ : - : | capacity | 6 | : : : - : +-----------+-------+ : : : - : : `- - - - - - - - - - - - - - - - - - -' - : : - `- - - - - - - - - - - - - -' - -After move to :rust:`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 - - fn say_hello(name: String) { - println!("Hello {name}") - } - - fn main() { - let name = String::from("Alice"); - say_hello(name); - // say_hello(name); - } - ---------- -Details ---------- - -- Mention that this is the opposite of the defaults in C++, which - copies by value unless you use :rust:`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 :rust:`Copy` (see later - slides). - -- In Rust, clones are explicit (by using :rust:`clone`). - -In the :rust:`say_hello` example: - -- With the first call to :rust:`say_hello`, :rust:`main` gives up ownership of - :rust:`name`. Afterwards, :rust:`name` cannot be used anymore within - :rust:`main`. -- The heap memory allocated for :rust:`name` will be freed at the end of - the :rust:`say_hello` function. -- :rust:`main` can retain ownership if it passes :rust:`name` as a reference - (:rust:`&name`) and if :rust:`say_hello` accepts a reference as a parameter. -- Alternatively, :rust:`main` can pass a clone of :rust:`name` in the first - call (:rust:`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. - --------------------------------- -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 :rust:`s1` is duplicated and :rust:`s2` gets its own - independent copy. -- When :rust:`s1` and :rust:`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 :rust:`=` - 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 :rust:`s2 = std::move(s1)`, no heap allocation would - take place. After the move, :rust:`s1` would be in a valid but - unspecified state. Unlike Rust, the programmer is allowed to keep - using :rust:`s1`. - -- Unlike Rust, :rust:`=` in C++ can run arbitrary code as determined by the - type which is being copied or moved. +================ +Move Semantics +================ + +---------------- +Move Semantics +---------------- + +An assignment will transfer *ownership* between variables: + +.. code:: rust + + fn main() { + let s1: String = String::from("Hello!"); + let s2: String = s1; + println!("s2: {s2}"); + // println!("s1: {s1}"); + } + +- The assignment of :rust:`s1` to :rust:`s2` transfers ownership. +- When :rust:`s1` goes out of scope, nothing happens: it does not own + anything. +- When :rust:`s2` goes out of scope, the string data is freed. + +Before move to :rust:`s2`: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -. + : : : : + : s1 : : : + : +-----------+-------+ : : +----+----+----+----+----+----+ : + : | ptr | o---+---+-----+-->| H | e | l | l | o | ! | : + : | len | 6 | : : +----+----+----+----+----+----+ : + : | capacity | 6 | : : : + : +-----------+-------+ : : : + : : `- - - - - - - - - - - - - - - - - - -' + : : + `- - - - - - - - - - - - - -' + +After move to :rust:`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 + + fn say_hello(name: String) { + println!("Hello {name}") + } + + fn main() { + let name = String::from("Alice"); + say_hello(name); + // say_hello(name); + } + +--------- +Details +--------- + +- Mention that this is the opposite of the defaults in C++, which + copies by value unless you use :rust:`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 :rust:`Copy` (see later + slides). + +- In Rust, clones are explicit (by using :rust:`clone`). + +In the :rust:`say_hello` example: + +- With the first call to :rust:`say_hello`, :rust:`main` gives up ownership of + :rust:`name`. Afterwards, :rust:`name` cannot be used anymore within + :rust:`main`. +- The heap memory allocated for :rust:`name` will be freed at the end of + the :rust:`say_hello` function. +- :rust:`main` can retain ownership if it passes :rust:`name` as a reference + (:rust:`&name`) and if :rust:`say_hello` accepts a reference as a parameter. +- Alternatively, :rust:`main` can pass a clone of :rust:`name` in the first + call (:rust:`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. + +-------------------------------- +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 :rust:`s1` is duplicated and :rust:`s2` gets its own + independent copy. +- When :rust:`s1` and :rust:`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 :rust:`=` + 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 :rust:`s2 = std::move(s1)`, no heap allocation would + take place. After the move, :rust:`s1` would be in a valid but + unspecified state. Unlike Rust, the programmer is allowed to keep + using :rust:`s1`. + +- Unlike Rust, :rust:`=` in C++ can run arbitrary code as determined by the + type which is being copied or moved. diff --git a/courses/comprehensive_rust_training/130_memory_management/05_clone.rst b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst index ca61caddf..6f6484b7d 100644 --- a/courses/comprehensive_rust_training/130_memory_management/05_clone.rst +++ b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst @@ -1,40 +1,40 @@ -======= -Clone -======= - -------- -Clone -------- - -Sometimes you *want* to make a copy of a value. The :rust:`Clone` trait -accomplishes this. - -.. code:: rust - - fn say_hello(name: String) { - println!("Hello {name}") - } - - fn main() { - let name = String::from("Alice"); - say_hello(name.clone()); - say_hello(name); - } - ---------- -Details ---------- - -- The idea of :rust:`Clone` is to make it easy to spot where heap - allocations are occurring. Look for :rust:`.clone()` and a few others - like :rust:`vec!` or :rust:`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. - -- :rust:`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 :rust:`clone` is user-defined, so it can perform custom - cloning logic if needed. +======= +Clone +======= + +------- +Clone +------- + +Sometimes you *want* to make a copy of a value. The :rust:`Clone` trait +accomplishes this. + +.. code:: rust + + fn say_hello(name: String) { + println!("Hello {name}") + } + + fn main() { + let name = String::from("Alice"); + say_hello(name.clone()); + say_hello(name); + } + +--------- +Details +--------- + +- The idea of :rust:`Clone` is to make it easy to spot where heap + allocations are occurring. Look for :rust:`.clone()` and a few others + like :rust:`vec!` or :rust:`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. + +- :rust:`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 :rust:`clone` is user-defined, so it can perform custom + cloning logic if needed. 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 index 18c45f14e..81d95cffc 100644 --- a/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst +++ b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst @@ -1,69 +1,69 @@ -============ -Copy Types -============ - ------------- -Copy Types ------------- - -While move semantics are the default, certain types are copied by -default: - -.. code:: rust - - 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 :rust:`Copy` trait. - -You can opt-in your own types to use copy semantics: - -.. code:: rust - - #[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 :rust:`p1` and :rust:`p2` own their own data. -- We can also use :rust:`p1.clone()` to explicitly copy the data. - ---------- -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 :rust:`Clone` trait. -- Copying does not work on types that implement the :rust:`Drop` trait. - -In the above example, try the following: - -- Add a :rust:`String` field to :rust:`struct Point`. It will not compile - because :rust:`String` is not a :rust:`Copy` type. -- Remove :rust:`Copy` from the :rust:`derive` attribute. The compiler error is - now in the :rust:`println!` for :rust:`p1`. -- Show that it works if you clone :rust:`p1` instead. - ------------------ -More to Explore ------------------ - -- Shared references are :rust:`Copy`/:rust:`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. +============ +Copy Types +============ + +------------ +Copy Types +------------ + +While move semantics are the default, certain types are copied by +default: + +.. code:: rust + + 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 :rust:`Copy` trait. + +You can opt-in your own types to use copy semantics: + +.. code:: rust + + #[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 :rust:`p1` and :rust:`p2` own their own data. +- We can also use :rust:`p1.clone()` to explicitly copy the data. + +--------- +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 :rust:`Clone` trait. +- Copying does not work on types that implement the :rust:`Drop` trait. + +In the above example, try the following: + +- Add a :rust:`String` field to :rust:`struct Point`. It will not compile + because :rust:`String` is not a :rust:`Copy` type. +- Remove :rust:`Copy` from the :rust:`derive` attribute. The compiler error is + now in the :rust:`println!` for :rust:`p1`. +- Show that it works if you clone :rust:`p1` instead. + +----------------- +More to Explore +----------------- + +- Shared references are :rust:`Copy`/:rust:`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. 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 40e58fc60..1cc2b0409 100644 --- a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst +++ b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst @@ -1,68 +1,68 @@ -==================== -The "Drop" Trait -==================== - --------------------- -The "Drop" Trait --------------------- - -Values which implement -`Drop `__ can -specify code to run when they go out of scope: - -.. code:: rust - - 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"); - } - ---------- -Details ---------- - -- Note that :rust:`std::mem::drop` is not the same as - :rust:`std::ops::Drop::drop`. -- Values are automatically dropped when they go out of scope. -- When a value is dropped, if it implements :rust:`std::ops::Drop` then its - :rust:`Drop::drop` implementation will be called. -- All its fields will then be dropped too, whether or not it implements - :rust:`Drop`. -- :rust:`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 :rust:`drop`: - releasing locks, closing files, etc. - -Discussion points: - -- Why doesn't :rust:`Drop::drop` take :rust:`self`? - - - Short-answer: If it did, :rust:`std::mem::drop` would be called at the - end of the block, resulting in another call to :rust:`Drop::drop`, and - a stack overflow! - -- Try replacing :rust:`drop(a)` with :rust:`a.drop()`. +==================== +The "Drop" Trait +==================== + +-------------------- +The "Drop" Trait +-------------------- + +Values which implement +`Drop `__ can +specify code to run when they go out of scope: + +.. code:: rust + + 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"); + } + +--------- +Details +--------- + +- Note that :rust:`std::mem::drop` is not the same as + :rust:`std::ops::Drop::drop`. +- Values are automatically dropped when they go out of scope. +- When a value is dropped, if it implements :rust:`std::ops::Drop` then its + :rust:`Drop::drop` implementation will be called. +- All its fields will then be dropped too, whether or not it implements + :rust:`Drop`. +- :rust:`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 :rust:`drop`: + releasing locks, closing files, etc. + +Discussion points: + +- Why doesn't :rust:`Drop::drop` take :rust:`self`? + + - Short-answer: If it did, :rust:`std::mem::drop` would be called at the + end of the block, resulting in another call to :rust:`Drop::drop`, and + a stack overflow! + +- Try replacing :rust:`drop(a)` with :rust:`a.drop()`. diff --git a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst index 0c2838624..d6a9345f5 100644 --- a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst +++ b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst @@ -1,44 +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. - -:: - - {{#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}} +======================== +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. + +:: + + {{#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 index 9ff353a0b..2ca203d4e 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -1,42 +1,42 @@ -**************** -Smart Pointers -**************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +**************** +Smart Pointers +**************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 6fbc7740d..d40b1081d 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -1,100 +1,100 @@ -============ -"Box" -============ - ------------- -"Box" ------------- - -`Box `__ is an -owned pointer to data on the heap: - -.. code:: rust - - fn main() { - let five = Box::new(5); - println!("five: {}", *five); - } - -.. code:: bob - - Stack Heap - .- - - - - - -. .- - - - - - -. - : : : : - : five : : : - : +-----+ : : +-----+ : - : | o---|---+-----+-->| 5 | : - : +-----+ : : +-----+ : - : : : : - : : : : - `- - - - - - -' `- - - - - - -' - -:rust:`Box` implements :rust:`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. :rust:`Box` accomplishes that -indirection: - -.. code:: rust - - #[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 | // | // | : - : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : - : : : : - : : : : - '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' - ---------- -Details ---------- - -- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's - guaranteed to be not null. - -- A :rust:`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 :rust:`Box` so only the pointer is moved. - -- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly - into the :rust:`List`, the compiler would not be able to compute a fixed - size for the struct in memory (the :rust:`List` would be of infinite - size). - -- :rust:`Box` solves this problem as it has the same size as a regular - pointer and just points at the next element of the :rust:`List` in the - heap. - -- Remove the :rust:`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 :rust:`Box` or - reference of some kind, instead of storing the value directly. - -- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be - empty/null. This makes :rust:`Box` one of the types that allow the - compiler to optimize storage of some enums (the "niche - optimization"). +============ +"Box" +============ + +------------ +"Box" +------------ + +`Box `__ is an +owned pointer to data on the heap: + +.. code:: rust + + fn main() { + let five = Box::new(5); + println!("five: {}", *five); + } + +.. code:: bob + + Stack Heap + .- - - - - - -. .- - - - - - -. + : : : : + : five : : : + : +-----+ : : +-----+ : + : | o---|---+-----+-->| 5 | : + : +-----+ : : +-----+ : + : : : : + : : : : + `- - - - - - -' `- - - - - - -' + +:rust:`Box` implements :rust:`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. :rust:`Box` accomplishes that +indirection: + +.. code:: rust + + #[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 | // | // | : + : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : + : : : : + : : : : + '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' + +--------- +Details +--------- + +- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's + guaranteed to be not null. + +- A :rust:`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 :rust:`Box` so only the pointer is moved. + +- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly + into the :rust:`List`, the compiler would not be able to compute a fixed + size for the struct in memory (the :rust:`List` would be of infinite + size). + +- :rust:`Box` solves this problem as it has the same size as a regular + pointer and just points at the next element of the :rust:`List` in the + heap. + +- Remove the :rust:`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 :rust:`Box` or + reference of some kind, instead of storing the value directly. + +- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be + empty/null. This makes :rust:`Box` one of the types that allow the + compiler to optimize storage of some enums (the "niche + optimization"). 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 162fe6c48..cc68829f7 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -1,47 +1,47 @@ -======== -"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 - - use std::rc::Rc; - - fn main() { - let a = Rc::new(10); - let b = Rc::clone(&a); - - println!("a: {a}"); - println!("b: {b}"); - } - -- See :rust:`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. - ---------- -Details ---------- - -- :rust:`Rc`\ 's count ensures that its contained value is valid for as - long as there are references. -- :rust:`Rc` in Rust is like :rust:`std::shared_ptr` in C++. -- :rust:`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. -- :rust:`make_mut` actually clones the inner value if necessary - ("clone-on-write") and returns a mutable reference. -- Use :rust:`Rc::strong_count` to check the reference count. -- :rust:`Rc::downgrade` gives you a *weakly reference-counted* object to - create cycles that will be dropped properly (likely in combination - with :rust:`RefCell`). +======== +"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 + + use std::rc::Rc; + + fn main() { + let a = Rc::new(10); + let b = Rc::clone(&a); + + println!("a: {a}"); + println!("b: {b}"); + } + +- See :rust:`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. + +--------- +Details +--------- + +- :rust:`Rc`\ 's count ensures that its contained value is valid for as + long as there are references. +- :rust:`Rc` in Rust is like :rust:`std::shared_ptr` in C++. +- :rust:`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. +- :rust:`make_mut` actually clones the inner value if necessary + ("clone-on-write") and returns a mutable reference. +- Use :rust:`Rc::strong_count` to check the reference count. +- :rust:`Rc::downgrade` gives you a *weakly reference-counted* object to + create cycles that will be dropped properly (likely in combination + with :rust:`RefCell`). 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 a1c418b07..0efdc5715 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 @@ -1,111 +1,111 @@ -===================== -Owned Trait Objects -===================== - ---------------------- -Owned Trait Objects ---------------------- - -We previously saw how trait objects can be used with references, e.g -:rust:`&dyn Pet`. However, we can also use trait objects with smart pointers -like :rust:`Box` to create an owned trait object: :rust:`Box`. - -.. code:: rust - - 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 :rust:`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" | : - : +----------------------+ : - : : - '- - - - - - - - - - - - - - - - - - - - - - -' - ---------- -Details ---------- - -- Types that implement a given trait may be of different sizes. This - makes it impossible to have things like :rust:`Vec` in the - example above. - -- :rust:`dyn Pet` is a way to tell the compiler about a dynamically sized - type that implements :rust:`Pet`. - -- In the example, :rust:`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 :rust:`Pet` implementation of that particular object. - - The data for the :rust:`Dog` named Fido is the :rust:`name` and :rust:`age` - fields. The :rust:`Cat` has a :rust:`lives` field. - -- Compare these outputs in the above example: - - .. code:: rust - - 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::>()); +===================== +Owned Trait Objects +===================== + +--------------------- +Owned Trait Objects +--------------------- + +We previously saw how trait objects can be used with references, e.g +:rust:`&dyn Pet`. However, we can also use trait objects with smart pointers +like :rust:`Box` to create an owned trait object: :rust:`Box`. + +.. code:: rust + + 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 :rust:`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" | : + : +----------------------+ : + : : + '- - - - - - - - - - - - - - - - - - - - - - -' + +--------- +Details +--------- + +- Types that implement a given trait may be of different sizes. This + makes it impossible to have things like :rust:`Vec` in the + example above. + +- :rust:`dyn Pet` is a way to tell the compiler about a dynamically sized + type that implements :rust:`Pet`. + +- In the example, :rust:`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 :rust:`Pet` implementation of that particular object. + - The data for the :rust:`Dog` named Fido is the :rust:`name` and :rust:`age` + fields. The :rust:`Cat` has a :rust:`lives` field. + +- Compare these outputs in the above example: + + .. code:: rust + + 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::>()); diff --git a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst index b9fbe4f1f..0b6ea22d9 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst @@ -1,26 +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. - -:: - - include exercise.rs:types - - Implement new, insert, len, and has for Subtree. - - include exercise.rs:tests +======================= +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. + +:: + + 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 index 17b9f5f84..5283d993a 100644 --- a/courses/comprehensive_rust_training/150_borrowing.rst +++ b/courses/comprehensive_rust_training/150_borrowing.rst @@ -1,43 +1,43 @@ -*********** -Borrowing -*********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +*********** +Borrowing +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index ecff35252..1ef34b9e0 100644 --- a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst +++ b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst @@ -1,79 +1,79 @@ -=================== -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: - -.. code:: rust - - #[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 :rust:`add` function *borrows* two points and returns a new point. -- The caller retains ownership of the inputs. - ---------- -Details ---------- - -This slide is a review of the material on references from day 1, -expanding slightly to include function arguments and return values. - ------------------ -More to Explore ------------------ - -Notes on stack returns and inlining: - -- Demonstrate that the return from :rust:`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: - - .. code:: rust - - #[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 :rust:`#[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). +=================== +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: + +.. code:: rust + + #[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 :rust:`add` function *borrows* two points and returns a new point. +- The caller retains ownership of the inputs. + +--------- +Details +--------- + +This slide is a review of the material on references from day 1, +expanding slightly to include function arguments and return values. + +----------------- +More to Explore +----------------- + +Notes on stack returns and inlining: + +- Demonstrate that the return from :rust:`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: + + .. code:: rust + + #[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 :rust:`#[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). diff --git a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst index ff1526a51..9255467e4 100644 --- a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst +++ b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst @@ -1,70 +1,70 @@ -================= -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: - -.. code:: rust - - 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. - -.. code:: rust - - fn main() { - let mut a: i32 = 10; - let b: &i32 = &a; - - { - let c: &mut i32 = &mut a; - *c = 20; - } - - println!("a: {a}"); - println!("b: {b}"); - } - ---------- -Details ---------- - -- 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 :rust:`a` is borrowed as mutable - (through :rust:`c`) and as immutable (through :rust:`b`) at the same time. -- Move the :rust:`println!` statement for :rust:`b` before the scope that - introduces :rust:`c` to make the code compile. -- After that change, the compiler realizes that :rust:`b` is only ever used - before the new mutable borrow of :rust:`a` through :rust:`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." +================= +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: + +.. code:: rust + + 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. + +.. code:: rust + + fn main() { + let mut a: i32 = 10; + let b: &i32 = &a; + + { + let c: &mut i32 = &mut a; + *c = 20; + } + + println!("a: {a}"); + println!("b: {b}"); + } + +--------- +Details +--------- + +- 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 :rust:`a` is borrowed as mutable + (through :rust:`c`) and as immutable (through :rust:`b`) at the same time. +- Move the :rust:`println!` statement for :rust:`b` before the scope that + introduces :rust:`c` to make the code compile. +- After that change, the compiler realizes that :rust:`b` is only ever used + before the new mutable borrow of :rust:`a` through :rust:`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." diff --git a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst index a5f0944d3..950134b3e 100644 --- a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst +++ b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst @@ -1,39 +1,39 @@ -=============== -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 - - 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 - - fn main() { - let mut vec = vec![1, 2, 3, 4, 5]; - for elem in &vec { - vec.push(elem * 2); - } - } - ---------- -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. +=============== +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 + + 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 + + fn main() { + let mut vec = vec![1, 2, 3, 4, 5]; + for elem in &vec { + vec.push(elem * 2); + } + } + +--------- +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. diff --git a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst index 07a237814..b867a094a 100644 --- a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst +++ b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst @@ -1,113 +1,113 @@ -===================== -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. - ---------- -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. - --------------- -"Cell" --------------- - -:rust:`Cell` wraps a value and allows getting or setting the value using -only a shared reference to the :rust:`Cell`. However, it does not allow any -references to the inner value. Since there are no references, borrowing -rules cannot be broken. - -.. code:: rust - - use std::cell::Cell; - - fn main() { - // Note that `cell` is NOT declared as mutable. - let cell = Cell::new(5); - - cell.set(123); - println!("{}", cell.get()); - } - ----------------- -"Cell" Details ----------------- - -- :rust:`Cell` is a simple means to ensure safety: it has a :rust:`set` method - that takes :rust:`&self`. This needs no runtime check, but requires - moving values, which can have its own cost. - ------------------ -"RefCell" ------------------ - -:rust:`RefCell` allows accessing and mutating a wrapped value by providing -alternative types :rust:`Ref` and :rust:`RefMut` that emulate :rust:`&T`/:rust:`&mut T` -without actually being Rust references. - -These types perform dynamic checks using a counter in the :rust:`RefCell` to -prevent existence of a :rust:`RefMut` alongside another :rust:`Ref`/:rust:`RefMut`. - -By implementing :rust:`Deref` (and :rust:`DerefMut` for :rust:`RefMut`), these types -allow calling methods on the inner value without allowing references to -escape. - -.. code:: rust - - use std::cell::RefCell; - - fn main() { - // Note that `cell` is NOT declared as mutable. - let cell = RefCell::new(5); - - { - let mut cell_ref = cell.borrow_mut(); - *cell_ref = 123; - - // This triggers an error at runtime. - // let other = cell.borrow(); - // println!("{}", *other); - } - - println!("{cell:?}"); - } - -.. raw:: html - -------------------- -"RefCell" Details -------------------- - -- :rust:`RefCell` enforces Rust's usual borrowing rules (either multiple - shared references or a single exclusive reference) with a runtime - check. In this case, all borrows are very short and never overlap, so - the checks always succeed. - -- The extra block in the example is to end the borrow created by the - call to :rust:`borrow_mut` before we print the cell. Trying to print a - borrowed :rust:`RefCell` just shows the message :rust:`"{borrowed}"`. - ------------------ -More to Explore ------------------ - -There are also :rust:`OnceCell` and :rust:`OnceLock`, which allow initialization -on first use. Making these useful requires some more knowledge than -students have at this time. - +===================== +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. + +--------- +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. + +-------------- +"Cell" +-------------- + +:rust:`Cell` wraps a value and allows getting or setting the value using +only a shared reference to the :rust:`Cell`. However, it does not allow any +references to the inner value. Since there are no references, borrowing +rules cannot be broken. + +.. code:: rust + + use std::cell::Cell; + + fn main() { + // Note that `cell` is NOT declared as mutable. + let cell = Cell::new(5); + + cell.set(123); + println!("{}", cell.get()); + } + +---------------- +"Cell" Details +---------------- + +- :rust:`Cell` is a simple means to ensure safety: it has a :rust:`set` method + that takes :rust:`&self`. This needs no runtime check, but requires + moving values, which can have its own cost. + +----------------- +"RefCell" +----------------- + +:rust:`RefCell` allows accessing and mutating a wrapped value by providing +alternative types :rust:`Ref` and :rust:`RefMut` that emulate :rust:`&T`/:rust:`&mut T` +without actually being Rust references. + +These types perform dynamic checks using a counter in the :rust:`RefCell` to +prevent existence of a :rust:`RefMut` alongside another :rust:`Ref`/:rust:`RefMut`. + +By implementing :rust:`Deref` (and :rust:`DerefMut` for :rust:`RefMut`), these types +allow calling methods on the inner value without allowing references to +escape. + +.. code:: rust + + use std::cell::RefCell; + + fn main() { + // Note that `cell` is NOT declared as mutable. + let cell = RefCell::new(5); + + { + let mut cell_ref = cell.borrow_mut(); + *cell_ref = 123; + + // This triggers an error at runtime. + // let other = cell.borrow(); + // println!("{}", *other); + } + + println!("{cell:?}"); + } + +.. raw:: html + +------------------- +"RefCell" Details +------------------- + +- :rust:`RefCell` enforces Rust's usual borrowing rules (either multiple + shared references or a single exclusive reference) with a runtime + check. In this case, all borrows are very short and never overlap, so + the checks always succeed. + +- The extra block in the example is to end the borrow created by the + call to :rust:`borrow_mut` before we print the cell. Trying to print a + borrowed :rust:`RefCell` just shows the message :rust:`"{borrowed}"`. + +----------------- +More to Explore +----------------- + +There are also :rust:`OnceCell` and :rust:`OnceLock`, which allow initialization +on first use. Making these useful requires some more knowledge than +students have at this time. + diff --git a/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst index 9a2417914..8c7e8bbba 100644 --- a/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst +++ b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst @@ -1,28 +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: - -:: - - // 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}} +============================= +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: + +:: + + // 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 index e2fc4be60..cd1906794 100644 --- a/courses/comprehensive_rust_training/160_lifetimes.rst +++ b/courses/comprehensive_rust_training/160_lifetimes.rst @@ -1,42 +1,42 @@ -*********** -Lifetimes -*********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +*********** +Lifetimes +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 585645bc0..f723f6ca4 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst @@ -1,66 +1,66 @@ -====================== -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: :rust:`&'a Point`, :rust:`&'document str`. -Lifetimes start with :rust:`'` and :rust:`'a` is a typical default name. Read -:rust:`&'a Point` as "a borrowed :rust:`Point` which is valid for at least the -lifetime :rust:`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. - -.. code:: rust - - #[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:?}"); - } - ---------- -Details ---------- - -In this example, the compiler does not know what lifetime to infer for -:rust:`p3`. Looking inside the function body shows that it can only safely -assume that :rust:`p3`\ 's lifetime is the shorter of :rust:`p1` and :rust:`p2`. But -just like types, Rust requires explicit annotations of lifetimes on -function arguments and return values. - -Add :rust:`'a` appropriately to :rust:`left_most`: - -.. code:: rust - - fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { - -This says, "given p1 and p2 which both outlive :rust:`'a`, the return value -lives for at least :rust:`'a`. - -In common cases, lifetimes can be elided, as described on the next -slide. +====================== +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: :rust:`&'a Point`, :rust:`&'document str`. +Lifetimes start with :rust:`'` and :rust:`'a` is a typical default name. Read +:rust:`&'a Point` as "a borrowed :rust:`Point` which is valid for at least the +lifetime :rust:`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. + +.. code:: rust + + #[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:?}"); + } + +--------- +Details +--------- + +In this example, the compiler does not know what lifetime to infer for +:rust:`p3`. Looking inside the function body shows that it can only safely +assume that :rust:`p3`\ 's lifetime is the shorter of :rust:`p1` and :rust:`p2`. But +just like types, Rust requires explicit annotations of lifetimes on +function arguments and return values. + +Add :rust:`'a` appropriately to :rust:`left_most`: + +.. code:: rust + + fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { + +This says, "given p1 and p2 which both outlive :rust:`'a`, the return value +lives for at least :rust:`'a`. + +In common cases, lifetimes can be elided, as described on the next +slide. 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 9b7807547..764e527e0 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst @@ -1,73 +1,73 @@ -============================= -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 - 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 - :rust:`self`, that lifetime is given to all un-annotated return values. - -.. code:: rust - - #[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))); - } - ---------- -Details ---------- - -In this example, :rust:`cab_distance` is trivially elided. - -The :rust:`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 - - 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. +============================= +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 - 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 + :rust:`self`, that lifetime is given to all un-annotated return values. + +.. code:: rust + + #[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))); + } + +--------- +Details +--------- + +In this example, :rust:`cab_distance` is trivially elided. + +The :rust:`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 + + 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. diff --git a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst index c0e4b68d6..fda8ba4f5 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst @@ -1,47 +1,47 @@ -============================== -Lifetimes in Data Structures -============================== - ------------------------------- -Lifetimes in Data Structures ------------------------------- - -If a data type stores borrowed data, it must be annotated with a -lifetime: - -.. code:: rust - - #[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:?}"); - } - ---------- -Details ---------- - -- In the above example, the annotation on :rust:`Highlight` enforces that - the data underlying the contained :rust:`&str` lives at least as long as - any instance of :rust:`Highlight` that uses that data. -- If :rust:`text` is consumed before the end of the lifetime of :rust:`fox` (or - :rust:`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. +============================== +Lifetimes in Data Structures +============================== + +------------------------------ +Lifetimes in Data Structures +------------------------------ + +If a data type stores borrowed data, it must be annotated with a +lifetime: + +.. code:: rust + + #[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:?}"); + } + +--------- +Details +--------- + +- In the above example, the annotation on :rust:`Highlight` enforces that + the data underlying the contained :rust:`&str` lives at least as long as + any instance of :rust:`Highlight` that uses that data. +- If :rust:`text` is consumed before the end of the lifetime of :rust:`fox` (or + :rust:`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. diff --git a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst index f8c8cf75f..180b8368e 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst @@ -1,111 +1,111 @@ -============================ -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 -:rust:`proto` file. In this exercise, we'll encode that information into -:rust:`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., :rust:`2` for the :rust:`id` field of a :rust:`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 -:rust:`unpack_tag` below. - --------- -Varint --------- - -Integers, including the tag, are represented with a variable-length -encoding called VARINT. Luckily, :rust:`parse_varint` is defined for you -below. - ------------- -Wire Types ------------- - -Proto defines several wire types, only two of which are used in this -exercise. - -The :rust:`Varint` wire type contains a single varint, and is used to encode -proto values of type :rust:`int32` such as :rust:`Person.id`. - -The :rust:`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 :rust:`string` such as :rust:`Person.name`. It is also used to -encode proto values containing sub-messages such as :rust:`Person.phones`, -where the payload contains an encoding of the sub-message. - ----------- -Exercise ----------- - -The given code also defines callbacks to handle :rust:`Person` and -:rust:`PhoneNumber` fields, and to parse a message into a series of calls to -those callbacks. - -What remains for you is to implement the :rust:`parse_field` function and -the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. - -:: - - {{#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 }} - ---------- -Details ---------- - -- In this exercise there are various cases where protobuf parsing might - fail, e.g. if you try to parse an :rust:`i32` when there are fewer than 4 - bytes left in the data buffer. In normal Rust code we'd handle this - with the :rust:`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. +============================ +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 +:rust:`proto` file. In this exercise, we'll encode that information into +:rust:`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., :rust:`2` for the :rust:`id` field of a :rust:`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 +:rust:`unpack_tag` below. + +-------- +Varint +-------- + +Integers, including the tag, are represented with a variable-length +encoding called VARINT. Luckily, :rust:`parse_varint` is defined for you +below. + +------------ +Wire Types +------------ + +Proto defines several wire types, only two of which are used in this +exercise. + +The :rust:`Varint` wire type contains a single varint, and is used to encode +proto values of type :rust:`int32` such as :rust:`Person.id`. + +The :rust:`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 :rust:`string` such as :rust:`Person.name`. It is also used to +encode proto values containing sub-messages such as :rust:`Person.phones`, +where the payload contains an encoding of the sub-message. + +---------- +Exercise +---------- + +The given code also defines callbacks to handle :rust:`Person` and +:rust:`PhoneNumber` fields, and to parse a message into a series of calls to +those callbacks. + +What remains for you is to implement the :rust:`parse_field` function and +the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. + +:: + + {{#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 }} + +--------- +Details +--------- + +- In this exercise there are various cases where protobuf parsing might + fail, e.g. if you try to parse an :rust:`i32` when there are fewer than 4 + bytes left in the data buffer. In normal Rust code we'd handle this + with the :rust:`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. diff --git a/courses/comprehensive_rust_training/170_iterators.rst b/courses/comprehensive_rust_training/170_iterators.rst index b3c922f17..b57980e20 100644 --- a/courses/comprehensive_rust_training/170_iterators.rst +++ b/courses/comprehensive_rust_training/170_iterators.rst @@ -1,44 +1,44 @@ -*********** -Iterators -*********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +*********** +Iterators +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 1facf9371..59ab12638 100644 --- a/courses/comprehensive_rust_training/170_iterators/01_motivation.rst +++ b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst @@ -1,68 +1,68 @@ -====================== -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 - - 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". - ---------- -Details ---------- - -- This slide provides context for what Rust iterators do under the - hood. We use the (hopefully) familiar construct of a C-style :rust:`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 :rust:`for` loop, but we can express the same - thing with :rust:`while`: - - .. code:: rust - - 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 :rust:`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 - - 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). +====================== +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 + + 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". + +--------- +Details +--------- + +- This slide provides context for what Rust iterators do under the + hood. We use the (hopefully) familiar construct of a C-style :rust:`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 :rust:`for` loop, but we can express the same + thing with :rust:`while`: + + .. code:: rust + + 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 :rust:`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 + + 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). diff --git a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst index d2020da18..67dfc217e 100644 --- a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst @@ -1,73 +1,73 @@ -==================== -"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 - - 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}"); - } - } - ---------- -Details ---------- - -- The :rust:`SliceIter` example implements the same logic as the C-style - :rust:`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 :rust:`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 :rust:`0..` will keep going until integer overflow occurs. - ------------------ -More to Explore ------------------ - -- The "real" version of :rust:`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 :rust:`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 :rust:`SliceIter` - to allow it to work with any kind of slice (not just :rust:`&[i32]`). +==================== +"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 + + 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}"); + } + } + +--------- +Details +--------- + +- The :rust:`SliceIter` example implements the same logic as the C-style + :rust:`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 :rust:`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 :rust:`0..` will keep going until integer overflow occurs. + +----------------- +More to Explore +----------------- + +- The "real" version of :rust:`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 :rust:`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 :rust:`SliceIter` + to allow it to work with any kind of slice (not just :rust:`&[i32]`). diff --git a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst index b57fe1c1b..bbf3462fe 100644 --- a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst +++ b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst @@ -1,47 +1,47 @@ -============================= -"Iterator" Helper Methods -============================= - ------------------------------ -"Iterator" Helper Methods ------------------------------ - -In addition to the :rust:`next` method that defines how an iterator behaves, -the :rust:`Iterator` trait provides 70+ helper methods that can be used to -build customized iterators. - -.. code:: rust - - 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); - ---------- -Details ---------- - -- The :rust:`Iterator` trait implements many common functional programming - operations over collections (e.g. :rust:`map`, :rust:`filter`, :rust:`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 :rust:`sum` and :rust:`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. +============================= +"Iterator" Helper Methods +============================= + +----------------------------- +"Iterator" Helper Methods +----------------------------- + +In addition to the :rust:`next` method that defines how an iterator behaves, +the :rust:`Iterator` trait provides 70+ helper methods that can be used to +build customized iterators. + +.. code:: rust + + 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); + +--------- +Details +--------- + +- The :rust:`Iterator` trait implements many common functional programming + operations over collections (e.g. :rust:`map`, :rust:`filter`, :rust:`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 :rust:`sum` and :rust:`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. diff --git a/courses/comprehensive_rust_training/170_iterators/04_collect.rst b/courses/comprehensive_rust_training/170_iterators/04_collect.rst index 2c7f26747..246a92639 100644 --- a/courses/comprehensive_rust_training/170_iterators/04_collect.rst +++ b/courses/comprehensive_rust_training/170_iterators/04_collect.rst @@ -1,56 +1,56 @@ -============= -"collect" -============= - -------------- -"collect" -------------- - -The -`collect `__ -method lets you build a collection from an -`Iterator `__. - -.. code:: rust - - 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:?}"); - } - ---------- -Details ---------- - -- Any iterator can be collected in to a :rust:`Vec`, :rust:`VecDeque`, or - :rust:`HashSet`. Iterators that produce key-value pairs (i.e. a - two-element tuple) can also be collected into :rust:`HashMap` and - :rust:`BTreeMap`. - -Show the students the definition for :rust:`collect` in the standard library -docs. There are two ways to specify the generic type :rust:`B` for this -method: - -- With the "turbofish": :rust:`some_iterator.collect::()`, - as shown. The :rust:`_` shorthand used here lets Rust infer the type of - the :rust:`Vec` elements. -- With type inference: - :rust:`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 :rust:`FromIterator` for - :rust:`Vec`, :rust:`HashMap`, etc., there are also more specialized - implementations which let you do cool things like convert an - :rust:`Iterator>` into a :rust:`Result, E>`. -- The reason type annotations are often needed with :rust:`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. +============= +"collect" +============= + +------------- +"collect" +------------- + +The +`collect `__ +method lets you build a collection from an +`Iterator `__. + +.. code:: rust + + 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:?}"); + } + +--------- +Details +--------- + +- Any iterator can be collected in to a :rust:`Vec`, :rust:`VecDeque`, or + :rust:`HashSet`. Iterators that produce key-value pairs (i.e. a + two-element tuple) can also be collected into :rust:`HashMap` and + :rust:`BTreeMap`. + +Show the students the definition for :rust:`collect` in the standard library +docs. There are two ways to specify the generic type :rust:`B` for this +method: + +- With the "turbofish": :rust:`some_iterator.collect::()`, + as shown. The :rust:`_` shorthand used here lets Rust infer the type of + the :rust:`Vec` elements. +- With type inference: + :rust:`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 :rust:`FromIterator` for + :rust:`Vec`, :rust:`HashMap`, etc., there are also more specialized + implementations which let you do cool things like convert an + :rust:`Iterator>` into a :rust:`Result, E>`. +- The reason type annotations are often needed with :rust:`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. diff --git a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst index 15838928f..67acee511 100644 --- a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst @@ -1,92 +1,92 @@ -================== -"IntoIterator" -================== - ------------------- -"IntoIterator" ------------------- - -The :rust:`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 :rust:`for` loop. - -.. code:: rust - - 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}"); - } - } - ---------- -Details ---------- - -- :rust:`IntoIterator` is the trait that makes for loops work. It is - implemented by collection types such as :rust:`Vec` and references to - them such as :rust:`&Vec` and :rust:`&[T]`. Ranges also implement it. This - is why you can iterate over a vector with - :rust:`for i in some_vec { .. }` but :rust:`some_vec.next()` doesn't exist. - -Click through to the docs for :rust:`IntoIterator`. Every implementation of -:rust:`IntoIterator` must declare two types: - -- :rust:`Item`: the type to iterate over, such as :rust:`i8`, -- :rust:`IntoIter`: the :rust:`Iterator` type returned by the :rust:`into_iter` - method. - -Note that :rust:`IntoIter` and :rust:`Item` are linked: the iterator must have -the same :rust:`Item` type, which means that it returns :rust:`Option` - -The example iterates over all combinations of x and y coordinates. - -Try iterating over the grid twice in :rust:`main`. Why does this fail? Note -that :rust:`IntoIterator::into_iter` takes ownership of :rust:`self`. - -Fix this issue by implementing :rust:`IntoIterator` for :rust:`&Grid` and -storing a reference to the :rust:`Grid` in :rust:`GridIter`. - -The same problem can occur for standard library types: -:rust:`for e in some_vector` will take ownership of :rust:`some_vector` and -iterate over owned elements from that vector. Use -:rust:`for e in &some_vector` instead, to iterate over references to -elements of :rust:`some_vector`. +================== +"IntoIterator" +================== + +------------------ +"IntoIterator" +------------------ + +The :rust:`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 :rust:`for` loop. + +.. code:: rust + + 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}"); + } + } + +--------- +Details +--------- + +- :rust:`IntoIterator` is the trait that makes for loops work. It is + implemented by collection types such as :rust:`Vec` and references to + them such as :rust:`&Vec` and :rust:`&[T]`. Ranges also implement it. This + is why you can iterate over a vector with + :rust:`for i in some_vec { .. }` but :rust:`some_vec.next()` doesn't exist. + +Click through to the docs for :rust:`IntoIterator`. Every implementation of +:rust:`IntoIterator` must declare two types: + +- :rust:`Item`: the type to iterate over, such as :rust:`i8`, +- :rust:`IntoIter`: the :rust:`Iterator` type returned by the :rust:`into_iter` + method. + +Note that :rust:`IntoIter` and :rust:`Item` are linked: the iterator must have +the same :rust:`Item` type, which means that it returns :rust:`Option` + +The example iterates over all combinations of x and y coordinates. + +Try iterating over the grid twice in :rust:`main`. Why does this fail? Note +that :rust:`IntoIterator::into_iter` takes ownership of :rust:`self`. + +Fix this issue by implementing :rust:`IntoIterator` for :rust:`&Grid` and +storing a reference to the :rust:`Grid` in :rust:`GridIter`. + +The same problem can occur for standard library types: +:rust:`for e in some_vector` will take ownership of :rust:`some_vector` and +iterate over owned elements from that vector. Use +:rust:`for e in &some_vector` instead, to iterate over references to +elements of :rust:`some_vector`. diff --git a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst index 887994697..b93cc5777 100644 --- a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst +++ b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst @@ -1,24 +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 :rust:`collect` the result to -construct the return value. - -:: - - {{#include exercise.rs:offset_differences}} - unimplemented!() - } - - {{#include exercise.rs:unit-tests}} +==================================== +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 :rust:`collect` the result to +construct the return value. + +:: + + {{#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 index beda480e2..57d38f913 100644 --- a/courses/comprehensive_rust_training/180_modules.rst +++ b/courses/comprehensive_rust_training/180_modules.rst @@ -1,44 +1,44 @@ -********* -Modules -********* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +********* +Modules +********* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 6cef97d19..8b06f08bd 100644 --- a/courses/comprehensive_rust_training/180_modules/01_modules.rst +++ b/courses/comprehensive_rust_training/180_modules/01_modules.rst @@ -1,41 +1,41 @@ -========= -Modules -========= - ---------- -Modules ---------- - -We have seen how :rust:`impl` blocks let us namespace functions to a type. - -Similarly, :rust:`mod` lets us namespace types and functions: - -.. code:: rust - - 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(); - } - ---------- -Details ---------- - -- Packages provide functionality and include a :rust:`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. +========= +Modules +========= + +--------- +Modules +--------- + +We have seen how :rust:`impl` blocks let us namespace functions to a type. + +Similarly, :rust:`mod` lets us namespace types and functions: + +.. code:: rust + + 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(); + } + +--------- +Details +--------- + +- Packages provide functionality and include a :rust:`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. diff --git a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst index f0627aabf..8f990b702 100644 --- a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst +++ b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst @@ -1,80 +1,80 @@ -====================== -Filesystem Hierarchy -====================== - ----------------------- -Filesystem Hierarchy ----------------------- - -Omitting the module content will tell Rust to look for it in another -file: - -.. code:: rust - - mod garden; - -This tells Rust that the :rust:`garden` module content is found at -:rust:`src/garden.rs`. Similarly, a :rust:`garden::vegetables` module can be -found at :rust:`src/garden/vegetables.rs`. - -The :rust:`crate` root is in: - -- :rust:`src/lib.rs` (for a library crate) -- :rust:`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 - in this case, a -module. - -.. code:: rust - - //! 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!() - } - ---------- -Details ---------- - -- Before Rust 2018, modules needed to be located at :rust:`module/mod.rs` - instead of :rust:`module.rs`, and this is still a working alternative for - editions after 2018. - -- The main reason to introduce :rust:`filename.rs` as alternative to - :rust:`filename/mod.rs` was because many files named :rust:`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/ - |-- main.rs - |-- top_module.rs - |-- top_module/ - |-- sub_module.rs - -- The place rust will look for modules can be changed with a compiler - directive: - - .. code:: rust - - #[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 :rust:`some_module_test.rs`, similar to the - convention in Go. +====================== +Filesystem Hierarchy +====================== + +---------------------- +Filesystem Hierarchy +---------------------- + +Omitting the module content will tell Rust to look for it in another +file: + +.. code:: rust + + mod garden; + +This tells Rust that the :rust:`garden` module content is found at +:rust:`src/garden.rs`. Similarly, a :rust:`garden::vegetables` module can be +found at :rust:`src/garden/vegetables.rs`. + +The :rust:`crate` root is in: + +- :rust:`src/lib.rs` (for a library crate) +- :rust:`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 - in this case, a +module. + +.. code:: rust + + //! 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!() + } + +--------- +Details +--------- + +- Before Rust 2018, modules needed to be located at :rust:`module/mod.rs` + instead of :rust:`module.rs`, and this is still a working alternative for + editions after 2018. + +- The main reason to introduce :rust:`filename.rs` as alternative to + :rust:`filename/mod.rs` was because many files named :rust:`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/ + |-- main.rs + |-- top_module.rs + |-- top_module/ + |-- sub_module.rs + +- The place rust will look for modules can be changed with a compiler + directive: + + .. code:: rust + + #[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 :rust:`some_module_test.rs`, similar to the + convention in Go. diff --git a/courses/comprehensive_rust_training/180_modules/03_visibility.rst b/courses/comprehensive_rust_training/180_modules/03_visibility.rst index 3d565c962..f500803cc 100644 --- a/courses/comprehensive_rust_training/180_modules/03_visibility.rst +++ b/courses/comprehensive_rust_training/180_modules/03_visibility.rst @@ -1,57 +1,57 @@ -============ -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 :rust:`foo`, it's visible - in all the descendants of :rust:`foo`. - -.. code:: rust - - 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(); - } - ---------- -Details ---------- - -- Use the :rust:`pub` keyword to make modules public. - -Additionally, there are advanced :rust:`pub(...)` specifiers to restrict the -scope of public visibility. - -- See the - `Rust Reference `__. -- Configuring :rust:`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). +============ +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 :rust:`foo`, it's visible + in all the descendants of :rust:`foo`. + +.. code:: rust + + 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(); + } + +--------- +Details +--------- + +- Use the :rust:`pub` keyword to make modules public. + +Additionally, there are advanced :rust:`pub(...)` specifiers to restrict the +scope of public visibility. + +- See the + `Rust Reference `__. +- Configuring :rust:`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). diff --git a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst index 6fbfd3821..52d2d6073 100644 --- a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst +++ b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst @@ -1,85 +1,85 @@ -============================== -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 - - 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); - } - ---------- -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 :rust:`is_big` field is fully controlled by :rust:`Foo`, - allowing :rust:`Foo` to control how it's initialized and enforce any - invariants it needs to (e.g. that :rust:`is_big` is only :rust:`true` if - :rust:`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 :rust:`#[doc_hidden]` and :rust:`#[non_exhaustive]` - and show how they're used to limit what can be done with an enum. - -- Module privacy still applies when there are :rust:`impl` blocks in other - modules - `(example in the playground) `__. +============================== +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 + + 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); + } + +--------- +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 :rust:`is_big` field is fully controlled by :rust:`Foo`, + allowing :rust:`Foo` to control how it's initialized and enforce any + invariants it needs to (e.g. that :rust:`is_big` is only :rust:`true` if + :rust:`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 :rust:`#[doc_hidden]` and :rust:`#[non_exhaustive]` + and show how they're used to limit what can be done with an enum. + +- Module privacy still applies when there are :rust:`impl` blocks in other + modules + `(example in the playground) `__. diff --git a/courses/comprehensive_rust_training/180_modules/05_paths.rst b/courses/comprehensive_rust_training/180_modules/05_paths.rst index 3a26f2594..8e3b5bb74 100644 --- a/courses/comprehensive_rust_training/180_modules/05_paths.rst +++ b/courses/comprehensive_rust_training/180_modules/05_paths.rst @@ -1,58 +1,58 @@ -================== -use, super, self -================== - ------------------- -use, super, self ------------------- - -A module can bring symbols from another module into scope with :rust:`use`. -You will typically see something like this at the top of each module: - -.. code:: rust - - use std::collections::HashSet; - use std::process::abort; - -------- -Paths -------- - -Paths are resolved as follows: - -1. As a relative path: - - - :rust:`foo` or :rust:`self::foo` refers to :rust:`foo` in the current module, - - :rust:`super::foo` refers to :rust:`foo` in the parent module. - -2. As an absolute path: - - - :rust:`crate::foo` refers to :rust:`foo` in the root of the current crate, - - :rust:`bar::foo` refers to :rust:`foo` in the :rust:`bar` crate. - ---------- -Details ---------- - -- It is common to "re-export" symbols at a shorter path. For example, - the top-level :rust:`lib.rs` in a crate might have - - .. code:: rust - - mod storage; - - pub use storage::disk::DiskStorage; - pub use storage::network::NetworkStorage; - - making :rust:`DiskStorage` and :rust:`NetworkStorage` available to other - crates with a convenient, short path. - -- For the most part, only items that appear in a module need to be - :rust:`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 :rust:`read_to_string` method on a type - implementing the :rust:`Read` trait, you need to :rust:`use std::io::Read`. - -- The :rust:`use` statement can have a wildcard: :rust:`use std::io::*`. This - is discouraged because it is not clear which items are imported, and - those might change over time. +================== +use, super, self +================== + +------------------ +use, super, self +------------------ + +A module can bring symbols from another module into scope with :rust:`use`. +You will typically see something like this at the top of each module: + +.. code:: rust + + use std::collections::HashSet; + use std::process::abort; + +------- +Paths +------- + +Paths are resolved as follows: + +1. As a relative path: + + - :rust:`foo` or :rust:`self::foo` refers to :rust:`foo` in the current module, + - :rust:`super::foo` refers to :rust:`foo` in the parent module. + +2. As an absolute path: + + - :rust:`crate::foo` refers to :rust:`foo` in the root of the current crate, + - :rust:`bar::foo` refers to :rust:`foo` in the :rust:`bar` crate. + +--------- +Details +--------- + +- It is common to "re-export" symbols at a shorter path. For example, + the top-level :rust:`lib.rs` in a crate might have + + .. code:: rust + + mod storage; + + pub use storage::disk::DiskStorage; + pub use storage::network::NetworkStorage; + + making :rust:`DiskStorage` and :rust:`NetworkStorage` available to other + crates with a convenient, short path. + +- For the most part, only items that appear in a module need to be + :rust:`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 :rust:`read_to_string` method on a type + implementing the :rust:`Read` trait, you need to :rust:`use std::io::Read`. + +- The :rust:`use` statement can have a wildcard: :rust:`use std::io::*`. This + is discouraged because it is not clear which items are imported, and + those might change over time. diff --git a/courses/comprehensive_rust_training/180_modules/06_exercise.rst b/courses/comprehensive_rust_training/180_modules/06_exercise.rst index 627e41f1c..b9844d937 100644 --- a/courses/comprehensive_rust_training/180_modules/06_exercise.rst +++ b/courses/comprehensive_rust_training/180_modules/06_exercise.rst @@ -1,48 +1,48 @@ -===================================== -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 :rust:`Widget` trait and a few -implementations of that trait, as well as a :rust:`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 :rust:`src/main.rs` to add :rust:`mod` statements, and add -additional files in the :rust:`src` directory. - --------- -Source --------- - -Here's the single-module implementation of the GUI library: - -:: - - {{#include exercise.rs:single-module}} - ---------- -Details ---------- - -Encourage students to divide the code in a way that feels natural for -them, and get accustomed to the required :rust:`mod`, :rust:`use`, and :rust:`pub` -declarations. Afterward, discuss what organizations are most idiomatic. +===================================== +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 :rust:`Widget` trait and a few +implementations of that trait, as well as a :rust:`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 :rust:`src/main.rs` to add :rust:`mod` statements, and add +additional files in the :rust:`src` directory. + +-------- +Source +-------- + +Here's the single-module implementation of the GUI library: + +:: + + {{#include exercise.rs:single-module}} + +--------- +Details +--------- + +Encourage students to divide the code in a way that feels natural for +them, and get accustomed to the required :rust:`mod`, :rust:`use`, and :rust:`pub` +declarations. Afterward, discuss what organizations are most idiomatic. diff --git a/courses/comprehensive_rust_training/190_testing.rst b/courses/comprehensive_rust_training/190_testing.rst index 68b64b594..888079c78 100644 --- a/courses/comprehensive_rust_training/190_testing.rst +++ b/courses/comprehensive_rust_training/190_testing.rst @@ -1,42 +1,42 @@ -********* -Testing -********* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +********* +Testing +********* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 742279e56..ac36eccc9 100644 --- a/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst +++ b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst @@ -1,51 +1,51 @@ -============ -Unit Tests -============ - ------------- -Unit Tests ------------- - -Rust and Cargo come with a simple unit test framework. Tests are marked -with :rust:`#[test]`. Unit tests are often put in a nested :rust:`tests` module, -using :rust:`#[cfg(test)]` to conditionally compile them only when building -tests. - -.. code:: rust - - 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 :rust:`#[cfg(test)]` attribute is only active when you run - :rust:`cargo test`. - ---------- -Details ---------- - -Run the tests in the playground in order to show their results. +============ +Unit Tests +============ + +------------ +Unit Tests +------------ + +Rust and Cargo come with a simple unit test framework. Tests are marked +with :rust:`#[test]`. Unit tests are often put in a nested :rust:`tests` module, +using :rust:`#[cfg(test)]` to conditionally compile them only when building +tests. + +.. code:: rust + + 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 :rust:`#[cfg(test)]` attribute is only active when you run + :rust:`cargo test`. + +--------- +Details +--------- + +Run the tests in the playground in order to show their results. diff --git a/courses/comprehensive_rust_training/190_testing/02_other.rst b/courses/comprehensive_rust_training/190_testing/02_other.rst index 685a8cc73..177917e23 100644 --- a/courses/comprehensive_rust_training/190_testing/02_other.rst +++ b/courses/comprehensive_rust_training/190_testing/02_other.rst @@ -1,53 +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 :rust:`.rs` file under :rust:`tests/`: - -.. code:: rust - - // 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. - /// - /// :rust:` - /// # use playground::shorten_string; - /// assert_eq!(shorten_string("Hello World", 5), "Hello"); - /// assert_eq!(shorten_string("Hello World", 20), "Hello World"); - /// :rust:` - pub fn shorten_string(s: &str, length: usize) -> &str { - &s[..std::cmp::min(length, s.len())] - } - -- Code blocks in :rust:`///` comments are automatically seen as Rust code. -- The code will be compiled and executed as part of :rust:`cargo test`. -- Adding :rust:`#` in the code will hide it from the docs, but will still - compile/run it. -- Test the above code on the - `Rust Playground `__. +====================== +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 :rust:`.rs` file under :rust:`tests/`: + +.. code:: rust + + // 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. + /// + /// :rust:` + /// # use playground::shorten_string; + /// assert_eq!(shorten_string("Hello World", 5), "Hello"); + /// assert_eq!(shorten_string("Hello World", 20), "Hello World"); + /// :rust:` + pub fn shorten_string(s: &str, length: usize) -> &str { + &s[..std::cmp::min(length, s.len())] + } + +- Code blocks in :rust:`///` comments are automatically seen as Rust code. +- The code will be compiled and executed as part of :rust:`cargo test`. +- Adding :rust:`#` 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 index 6955b590d..443015390 100644 --- a/courses/comprehensive_rust_training/190_testing/03_lints.rst +++ b/courses/comprehensive_rust_training/190_testing/03_lints.rst @@ -1,34 +1,34 @@ -=========================== -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 - - #[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); - } - ---------- -Details ---------- - -There are compiler lints visible here, but not clippy lints. Run -:rust:`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 :rust:`help: ...` can be fixed with -:rust:`cargo fix` or via your editor. +=========================== +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 + + #[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); + } + +--------- +Details +--------- + +There are compiler lints visible here, but not clippy lints. Run +:rust:`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 :rust:`help: ...` can be fixed with +:rust:`cargo fix` or via your editor. diff --git a/courses/comprehensive_rust_training/190_testing/04_exercise.rst b/courses/comprehensive_rust_training/190_testing/04_exercise.rst index 582ec127b..fac188323 100644 --- a/courses/comprehensive_rust_training/190_testing/04_exercise.rst +++ b/courses/comprehensive_rust_training/190_testing/04_exercise.rst @@ -1,39 +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 :rust:`1234`, we double :rust:`3` and :rust:`1`. For the number :rust:`98765`, - we double :rust:`6` and :rust:`8`. - -- After doubling a digit, sum the digits if the result is greater than - 9. So doubling :rust:`7` becomes :rust:`14` which becomes :rust:`1 + 4 = 5`. - -- Sum all the undoubled and doubled digits. - -- The credit card number is valid if the sum ends with :rust:`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. - -:: - - {{#include exercise.rs:luhn}} - - {{#include exercise.rs:unit-tests}} - } +========================== +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 :rust:`1234`, we double :rust:`3` and :rust:`1`. For the number :rust:`98765`, + we double :rust:`6` and :rust:`8`. + +- After doubling a digit, sum the digits if the result is greater than + 9. So doubling :rust:`7` becomes :rust:`14` which becomes :rust:`1 + 4 = 5`. + +- Sum all the undoubled and doubled digits. + +- The credit card number is valid if the sum ends with :rust:`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. + +:: + + {{#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 index e8a88c20b..d3a871ca9 100644 --- a/courses/comprehensive_rust_training/200_error_handling.rst +++ b/courses/comprehensive_rust_training/200_error_handling.rst @@ -1,46 +1,46 @@ -**************** -Error Handling -**************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +**************** +Error Handling +**************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index a1d0dc7d1..a79e122ea 100644 --- a/courses/comprehensive_rust_training/200_error_handling/01_panics.rst +++ b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst @@ -1,58 +1,58 @@ -======== -Panics -======== - --------- -Panics --------- - -Rust handles fatal errors with a "panic". - -Rust will trigger a panic if a fatal error happens at runtime: - -.. code:: rust - - 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 :rust:`assert!`) panic on failure - - Purpose-specific panics can use the :rust:`panic!` macro. - -- A panic will "unwind" the stack, dropping values just as if the - functions had returned. -- Use non-panicking APIs (such as :rust:`Vec::get`) if crashing is not - acceptable. - ---------- -Details ---------- - -By default, a panic will cause the stack to unwind. The unwinding can be -caught: - -.. code:: rust - - 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 - :rust:`catch_unwind`! -- This can be useful in servers which should keep running even if a - single request crashes. -- This does not work if :rust:`panic = 'abort'` is set in your - :rust:`Cargo.toml`. +======== +Panics +======== + +-------- +Panics +-------- + +Rust handles fatal errors with a "panic". + +Rust will trigger a panic if a fatal error happens at runtime: + +.. code:: rust + + 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 :rust:`assert!`) panic on failure + - Purpose-specific panics can use the :rust:`panic!` macro. + +- A panic will "unwind" the stack, dropping values just as if the + functions had returned. +- Use non-panicking APIs (such as :rust:`Vec::get`) if crashing is not + acceptable. + +--------- +Details +--------- + +By default, a panic will cause the stack to unwind. The unwinding can be +caught: + +.. code:: rust + + 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 + :rust:`catch_unwind`! +- This can be useful in servers which should keep running even if a + single request crashes. +- This does not work if :rust:`panic = 'abort'` is set in your + :rust:`Cargo.toml`. 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 6e85049e1..c417097b9 100644 --- a/courses/comprehensive_rust_training/200_error_handling/02_result.rst +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -1,87 +1,87 @@ -============ -"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 - - 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}"); - } - } - } - ---------- -Details ---------- - -- :rust:`Result` has two variants: :rust:`Ok` which contains the success value, - and :rust:`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 :rust:`Result` - value. - -- Like with :rust:`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 :rust:`Result` to check which variant you - have. Methods like :rust:`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 ------------------ - -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 :rust:`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. +============ +"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 + + 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}"); + } + } + } + +--------- +Details +--------- + +- :rust:`Result` has two variants: :rust:`Ok` which contains the success value, + and :rust:`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 :rust:`Result` + value. + +- Like with :rust:`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 :rust:`Result` to check which variant you + have. Methods like :rust:`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 +----------------- + +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 :rust:`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. diff --git a/courses/comprehensive_rust_training/200_error_handling/03_try.rst b/courses/comprehensive_rust_training/200_error_handling/03_try.rst index 615c73710..46678e563 100644 --- a/courses/comprehensive_rust_training/200_error_handling/03_try.rst +++ b/courses/comprehensive_rust_training/200_error_handling/03_try.rst @@ -1,69 +1,69 @@ -============== -Try Operator -============== - --------------- -Try Operator --------------- - -Runtime errors like connection-refused or file-not-found are handled -with the :rust:`Result` type, but matching this type on every call can be -cumbersome. The try-operator :rust:`?` is used to return errors to the -caller. It lets you turn the common - -.. code:: rust - - match some_expression { - Ok(value) => value, - Err(err) => return Err(err), - } - -into the much simpler - -.. code:: rust - - some_expression? - -We can use this to simplify our error handling code: - -.. code:: rust - - 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:?}"); - } - ---------- -Details ---------- - -Simplify the :rust:`read_username` function to use :rust:`?`. - -Key points: - -- The :rust:`username` variable can be either :rust:`Ok(string)` or - :rust:`Err(error)`. -- Use the :rust:`fs::write` call to test out the different scenarios: no - file, empty file, file with username. -- Note that :rust:`main` can return a :rust:`Result<(), E>` as long as it - implements :rust:`std::process::Termination`. In practice, this means - that :rust:`E` implements :rust:`Debug`. The executable will print the - :rust:`Err` variant and return a nonzero exit status on error. +============== +Try Operator +============== + +-------------- +Try Operator +-------------- + +Runtime errors like connection-refused or file-not-found are handled +with the :rust:`Result` type, but matching this type on every call can be +cumbersome. The try-operator :rust:`?` is used to return errors to the +caller. It lets you turn the common + +.. code:: rust + + match some_expression { + Ok(value) => value, + Err(err) => return Err(err), + } + +into the much simpler + +.. code:: rust + + some_expression? + +We can use this to simplify our error handling code: + +.. code:: rust + + 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:?}"); + } + +--------- +Details +--------- + +Simplify the :rust:`read_username` function to use :rust:`?`. + +Key points: + +- The :rust:`username` variable can be either :rust:`Ok(string)` or + :rust:`Err(error)`. +- Use the :rust:`fs::write` call to test out the different scenarios: no + file, empty file, file with username. +- Note that :rust:`main` can return a :rust:`Result<(), E>` as long as it + implements :rust:`std::process::Termination`. In practice, this means + that :rust:`E` implements :rust:`Debug`. The executable will print the + :rust:`Err` variant and return a nonzero exit status on error. 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 index cdba7b705..667b8793a 100644 --- a/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst +++ b/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst @@ -1,98 +1,98 @@ -================= -Try Conversions -================= - ------------------ -Try Conversions ------------------ - -The effective expansion of :rust:`?` is a little more complicated than -previously indicated: - -.. code:: rust - - expression? - -works the same as - -.. code:: rust - - match expression { - Ok(value) => value, - Err(err) => return Err(From::from(err)), - } - -The :rust:`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 - - 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:?}"); - } - ---------- -Details ---------- - -The :rust:`?` operator must return a value compatible with the return type -of the function. For :rust:`Result`, it means that the error types have to -be compatible. A function that returns :rust:`Result` can -only use :rust:`?` on a value of type :rust:`Result` if -:rust:`ErrorOuter` and :rust:`ErrorInner` are the same type or if :rust:`ErrorOuter` -implements :rust:`From`. - -A common alternative to a :rust:`From` implementation is -:rust:`Result::map_err`, especially when the conversion only happens in one -place. - -There is no compatibility requirement for :rust:`Option`. A function -returning :rust:`Option` can use the :rust:`?` operator on :rust:`Option` for -arbitrary :rust:`T` and :rust:`U` types. - -A function that returns :rust:`Result` cannot use :rust:`?` on :rust:`Option` and -vice versa. However, :rust:`Option::ok_or` converts :rust:`Option` to :rust:`Result` -whereas :rust:`Result::ok` turns :rust:`Result` into :rust:`Option`. +================= +Try Conversions +================= + +----------------- +Try Conversions +----------------- + +The effective expansion of :rust:`?` is a little more complicated than +previously indicated: + +.. code:: rust + + expression? + +works the same as + +.. code:: rust + + match expression { + Ok(value) => value, + Err(err) => return Err(From::from(err)), + } + +The :rust:`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 + + 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:?}"); + } + +--------- +Details +--------- + +The :rust:`?` operator must return a value compatible with the return type +of the function. For :rust:`Result`, it means that the error types have to +be compatible. A function that returns :rust:`Result` can +only use :rust:`?` on a value of type :rust:`Result` if +:rust:`ErrorOuter` and :rust:`ErrorInner` are the same type or if :rust:`ErrorOuter` +implements :rust:`From`. + +A common alternative to a :rust:`From` implementation is +:rust:`Result::map_err`, especially when the conversion only happens in one +place. + +There is no compatibility requirement for :rust:`Option`. A function +returning :rust:`Option` can use the :rust:`?` operator on :rust:`Option` for +arbitrary :rust:`T` and :rust:`U` types. + +A function that returns :rust:`Result` cannot use :rust:`?` on :rust:`Option` and +vice versa. However, :rust:`Option::ok_or` converts :rust:`Option` to :rust:`Result` +whereas :rust:`Result::ok` turns :rust:`Result` into :rust:`Option`. diff --git a/courses/comprehensive_rust_training/200_error_handling/05_error.rst b/courses/comprehensive_rust_training/200_error_handling/05_error.rst index 7efb00f66..06aa97a50 100644 --- a/courses/comprehensive_rust_training/200_error_handling/05_error.rst +++ b/courses/comprehensive_rust_training/200_error_handling/05_error.rst @@ -1,49 +1,49 @@ -===================== -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 -:rust:`std::error::Error` trait makes it easy to create a trait object that -can contain any error. - -.. code:: rust - - 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}"), - } - } - ---------- -Details ---------- - -The :rust:`read_count` function can return :rust:`std::io::Error` (from file -operations) or :rust:`std::num::ParseIntError` (from :rust:`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 :rust:`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 :rust:`std::error::Error` trait when defining a -custom error type so it can be boxed. +===================== +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 +:rust:`std::error::Error` trait makes it easy to create a trait object that +can contain any error. + +.. code:: rust + + 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}"), + } + } + +--------- +Details +--------- + +The :rust:`read_count` function can return :rust:`std::io::Error` (from file +operations) or :rust:`std::num::ParseIntError` (from :rust:`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 :rust:`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 :rust:`std::error::Error` trait when defining a +custom error type so it can be boxed. 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 57faa3115..cf36b73f9 100644 --- a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -1,55 +1,55 @@ -=============== -"thiserror" -=============== - ---------------- -"thiserror" ---------------- - -The `thiserror `__ crate provides macros -to help avoid boilerplate when defining error types. It provides derive -macros that assist in implementing :rust:`From`, :rust:`Display`, and the -:rust:`Error` trait. - -.. code:: rust - - 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:?}"), - } - } - ---------- -Details ---------- - -- The :rust:`Error` derive macro is provided by :rust:`thiserror`, and has lots - of useful attributes to help define error types in a compact way. -- The message from :rust:`#[error]` is used to derive the :rust:`Display` - trait. -- Note that the (:rust:`thiserror::`)\ :rust:`Error` derive macro, while it has - the effect of implementing the (:rust:`std::error::`)\ :rust:`Error` trait, - is not the same this; traits and macros do not share a namespace. +=============== +"thiserror" +=============== + +--------------- +"thiserror" +--------------- + +The `thiserror `__ crate provides macros +to help avoid boilerplate when defining error types. It provides derive +macros that assist in implementing :rust:`From`, :rust:`Display`, and the +:rust:`Error` trait. + +.. code:: rust + + 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:?}"), + } + } + +--------- +Details +--------- + +- The :rust:`Error` derive macro is provided by :rust:`thiserror`, and has lots + of useful attributes to help define error types in a compact way. +- The message from :rust:`#[error]` is used to derive the :rust:`Display` + trait. +- Note that the (:rust:`thiserror::`)\ :rust:`Error` derive macro, while it has + the effect of implementing the (:rust:`std::error::`)\ :rust:`Error` trait, + is not the same this; traits and macros do not share a namespace. 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 d4e1c8f26..40939c94e 100644 --- a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -1,74 +1,74 @@ -============ -"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 - - 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:?}"), - } - } - ---------- -Details ---------- - -- :rust:`anyhow::Error` is essentially a wrapper around :rust:`Box`. - As such it's again generally not a good choice for the public API of - a library, but is widely used in applications. -- :rust:`anyhow::Result` is a type alias for - :rust:`Result`. -- Functionality provided by :rust:`anyhow::Error` may be familiar to Go - developers, as it provides similar behavior to the Go :rust:`error` type - and :rust:`Result` is much like a Go :rust:`(T, error)` - (with the convention that only one element of the pair is - meaningful). -- :rust:`anyhow::Context` is a trait implemented for the standard - :rust:`Result` and :rust:`Option` types. :rust:`use anyhow::Context` is necessary - to enable :rust:`.context()` and :rust:`.with_context()` on those types. - ------------------ -More to Explore ------------------ - -- :rust:`anyhow::Error` has support for downcasting, much like - :rust:`std::any::Any`; the specific error type stored inside can be - extracted for examination if desired with - `Error::downcast `__. +============ +"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 + + 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:?}"), + } + } + +--------- +Details +--------- + +- :rust:`anyhow::Error` is essentially a wrapper around :rust:`Box`. + As such it's again generally not a good choice for the public API of + a library, but is widely used in applications. +- :rust:`anyhow::Result` is a type alias for + :rust:`Result`. +- Functionality provided by :rust:`anyhow::Error` may be familiar to Go + developers, as it provides similar behavior to the Go :rust:`error` type + and :rust:`Result` is much like a Go :rust:`(T, error)` + (with the convention that only one element of the pair is + meaningful). +- :rust:`anyhow::Context` is a trait implemented for the standard + :rust:`Result` and :rust:`Option` types. :rust:`use anyhow::Context` is necessary + to enable :rust:`.context()` and :rust:`.with_context()` on those types. + +----------------- +More to Explore +----------------- + +- :rust:`anyhow::Error` has support for downcasting, much like + :rust:`std::any::Any`; the specific error type stored inside can be + extracted for examination if desired with + `Error::downcast `__. diff --git a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst index aa20c3f2c..95540d07e 100644 --- a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst +++ b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst @@ -1,27 +1,27 @@ -================================= -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 :rust:`eval` to instead use idiomatic error -handling to handle this error case and return an error when it occurs. -We provide a simple :rust:`DivideByZeroError` type to use as the error type -for :rust:`eval`. - -:: - - #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. +================================= +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 :rust:`eval` to instead use idiomatic error +handling to handle this error case and return an error when it occurs. +We provide a simple :rust:`DivideByZeroError` type to use as the error type +for :rust:`eval`. + +:: + + #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. diff --git a/courses/comprehensive_rust_training/210_unsafe_rust.rst b/courses/comprehensive_rust_training/210_unsafe_rust.rst index 5dcfeb734..e14d7e0f2 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust.rst @@ -1,45 +1,45 @@ -************* -Unsafe Rust -************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************* +Unsafe Rust +************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 4c8b58885..e4723f218 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst @@ -1,41 +1,41 @@ -============= -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 :rust:`union` fields. -- Call :rust:`unsafe` functions, including :rust:`extern` functions. -- Implement :rust:`unsafe` traits. - -We will briefly cover unsafe capabilities next. For full details, please -see -`Chapter 19.1 in the Rust Book `__ and -the `Rustonomicon `__. - ---------- -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. +============= +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 :rust:`union` fields. +- Call :rust:`unsafe` functions, including :rust:`extern` functions. +- Implement :rust:`unsafe` traits. + +We will briefly cover unsafe capabilities next. For full details, please +see +`Chapter 19.1 in the Rust Book `__ and +the `Rustonomicon `__. + +--------- +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. diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst index 5958a9f45..9ff9db122 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst @@ -1,65 +1,65 @@ -============================ -Dereferencing Raw Pointers -============================ - ----------------------------- -Dereferencing Raw Pointers ----------------------------- - -Creating pointers is safe, but dereferencing them requires :rust:`unsafe`: - -.. code:: rust - - 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); - */ - } - ---------- -Details ---------- - -It is good practice (and required by the Android Rust style guide) to -write a comment for each :rust:`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: -:rust:`*r1` has the :rust:`'static` lifetime, so :rust:`r3` has type -:rust:`&'static String`, and thus outlives :rust:`s`. Creating a reference from -a pointer requires *great care*. +============================ +Dereferencing Raw Pointers +============================ + +---------------------------- +Dereferencing Raw Pointers +---------------------------- + +Creating pointers is safe, but dereferencing them requires :rust:`unsafe`: + +.. code:: rust + + 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); + */ + } + +--------- +Details +--------- + +It is good practice (and required by the Android Rust style guide) to +write a comment for each :rust:`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: +:rust:`*r1` has the :rust:`'static` lifetime, so :rust:`r3` has type +:rust:`&'static String`, and thus outlives :rust:`s`. Creating a reference from +a pointer requires *great care*. 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 index 5b7f2376f..21000afe9 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst @@ -1,53 +1,53 @@ -========================== -Mutable Static Variables -========================== - --------------------------- -Mutable Static Variables --------------------------- - -It is safe to read an immutable static variable: - -.. code:: rust - - 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 - - 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}"); - } - } - ---------- -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 :rust:`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 :rust:`no_std` code, such as - implementing a heap allocator or working with some C APIs. +========================== +Mutable Static Variables +========================== + +-------------------------- +Mutable Static Variables +-------------------------- + +It is safe to read an immutable static variable: + +.. code:: rust + + 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 + + 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}"); + } + } + +--------- +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 :rust:`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 :rust:`no_std` code, such as + implementing a heap allocator or working with some C APIs. 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 b18d3de2a..7cce7f320 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst @@ -1,36 +1,36 @@ -======== -Unions -======== - --------- -Unions --------- - -Unions are like enums, but you need to track the active field yourself: - -.. code:: rust - - #[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! - } - ---------- -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. +======== +Unions +======== + +-------- +Unions +-------- + +Unions are like enums, but you need to track the active field yourself: + +.. code:: rust + + #[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! + } + +--------- +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. 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 953c045b4..7a3d11469 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 @@ -1,106 +1,106 @@ -================== -Unsafe Functions -================== - --------------------------- -Calling Unsafe Functions --------------------------- - -A function or method can be marked :rust:`unsafe` if it has extra -preconditions you must uphold to avoid undefined behaviour: - -.. code:: rust - - 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 :rust:`unsafe` if they require particular -conditions to avoid undefined behaviour. - -.. code:: rust - - /// 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); - } - --------------------------- -Calling Unsafe Functions --------------------------- - -:rust:`get_unchecked`, like most :rust:`_unchecked` functions, is unsafe, -because it can create UB if the range is incorrect. :rust:`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 :rust:`"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 :rust:`swap` function - it can be -done safely with references. - -Note that unsafe code is allowed within an unsafe function without an -:rust:`unsafe` block. We can prohibit this with -:rust:`#[deny(unsafe_op_in_unsafe_fn)]`. Try adding it and see what happens. -This will likely change in a future Rust edition. +================== +Unsafe Functions +================== + +-------------------------- +Calling Unsafe Functions +-------------------------- + +A function or method can be marked :rust:`unsafe` if it has extra +preconditions you must uphold to avoid undefined behaviour: + +.. code:: rust + + 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 :rust:`unsafe` if they require particular +conditions to avoid undefined behaviour. + +.. code:: rust + + /// 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); + } + +-------------------------- +Calling Unsafe Functions +-------------------------- + +:rust:`get_unchecked`, like most :rust:`_unchecked` functions, is unsafe, +because it can create UB if the range is incorrect. :rust:`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 :rust:`"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 :rust:`swap` function - it can be +done safely with references. + +Note that unsafe code is allowed within an unsafe function without an +:rust:`unsafe` block. We can prohibit this with +:rust:`#[deny(unsafe_op_in_unsafe_fn)]`. Try adding it and see what happens. +This will likely change in a future Rust edition. 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 75e90d129..9a9fbd0ef 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 @@ -1,44 +1,44 @@ -============================ -Implementing Unsafe Traits -============================ - ----------------------------- -Implementing Unsafe Traits ----------------------------- - -Like with functions, you can mark a trait as :rust:`unsafe` if the -implementation must guarantee particular conditions to avoid undefined -behaviour. - -For example, the :rust:`zerocopy` crate has an unsafe trait that looks -`something like this `__: - -.. code:: rust - - 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 {} - ---------- -Details ---------- - -There should be a :rust:`# Safety` section on the Rustdoc for the trait -explaining the requirements for the trait to be safely implemented. - -The actual safety section for :rust:`IntoBytes` is rather longer and more -complicated. - -The built-in :rust:`Send` and :rust:`Sync` traits are unsafe. +============================ +Implementing Unsafe Traits +============================ + +---------------------------- +Implementing Unsafe Traits +---------------------------- + +Like with functions, you can mark a trait as :rust:`unsafe` if the +implementation must guarantee particular conditions to avoid undefined +behaviour. + +For example, the :rust:`zerocopy` crate has an unsafe trait that looks +`something like this `__: + +.. code:: rust + + 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 {} + +--------- +Details +--------- + +There should be a :rust:`# Safety` section on the Rustdoc for the trait +explaining the requirements for the trait to be safely implemented. + +The actual safety section for :rust:`IntoBytes` is rather longer and more +complicated. + +The built-in :rust:`Send` and :rust:`Sync` traits are unsafe. 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 ab42b5405..5e3e05349 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst @@ -1,85 +1,85 @@ -================== -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 -:rust:`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: - -.. 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: - -- :rust:`&str` to :rust:`CString`: you need to allocate space for a trailing - :rust:`\0` character, -- :rust:`CString` to :rust:`*const i8`: you need a pointer to call C functions, -- :rust:`*const i8` to :rust:`&CStr`: you need something which can find the - trailing :rust:`\0` character, -- :rust:`&CStr` to :rust:`&[u8]`: a slice of bytes is the universal interface - for "some unknown data", -- :rust:`&[u8]` to :rust:`&OsStr`: :rust:`&OsStr` is a step towards :rust:`OsString`, - use - `OsStrExt `__ - to create it, -- :rust:`&OsStr` to :rust:`OsString`: you need to clone the data in :rust:`&OsStr` - to be able to return it and call :rust:`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: - -:: - - // 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 +================== +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 +:rust:`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: + +.. 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: + +- :rust:`&str` to :rust:`CString`: you need to allocate space for a trailing + :rust:`\0` character, +- :rust:`CString` to :rust:`*const i8`: you need a pointer to call C functions, +- :rust:`*const i8` to :rust:`&CStr`: you need something which can find the + trailing :rust:`\0` character, +- :rust:`&CStr` to :rust:`&[u8]`: a slice of bytes is the universal interface + for "some unknown data", +- :rust:`&[u8]` to :rust:`&OsStr`: :rust:`&OsStr` is a step towards :rust:`OsString`, + use + `OsStrExt `__ + to create it, +- :rust:`&OsStr` to :rust:`OsString`: you need to clone the data in :rust:`&OsStr` + to be able to return it and call :rust:`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: + +:: + + // 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 From 2a7e57a6b2932973c7f84bfd14c5a6111c6f9741 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Sun, 9 Feb 2025 10:36:26 -0500 Subject: [PATCH 11/55] Trying to find failure --- DO_NOT_MERGE.SINGLE_COURSE | 1 + courses/comprehensive_rust_training/test_course.txt | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 DO_NOT_MERGE.SINGLE_COURSE create mode 100644 courses/comprehensive_rust_training/test_course.txt diff --git a/DO_NOT_MERGE.SINGLE_COURSE b/DO_NOT_MERGE.SINGLE_COURSE new file mode 100644 index 000000000..7ca9521b8 --- /dev/null +++ b/DO_NOT_MERGE.SINGLE_COURSE @@ -0,0 +1 @@ +comprehensive_rust_training/test_course.txt diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt new file mode 100644 index 000000000..f373555da --- /dev/null +++ b/courses/comprehensive_rust_training/test_course.txt @@ -0,0 +1,10 @@ +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 From b446b29988eda7710c310d26aa89fbc8696d8ae7 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Sun, 9 Feb 2025 10:46:34 -0500 Subject: [PATCH 12/55] More testing --- .../test_course.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index f373555da..4d99eaf67 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,10 +1,10 @@ -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 +200_error_handling.rst From 7adcc80eeb0f6730cc9a043d512f12b018b35706 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:03:13 -0500 Subject: [PATCH 13/55] More testing --- courses/comprehensive_rust_training/test_course.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 4d99eaf67..72ed32b7d 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -3,8 +3,3 @@ 120_std_traits.rst 130_memory_management.rst 140_smart_pointers.rst -150_borrowing.rst -160_lifetimes.rst -170_iterators.rst -180_modules.rst -200_error_handling.rst From 5286bac49399231dd047b1ff334a1f591e95baec Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:05:17 -0500 Subject: [PATCH 14/55] more testing --- courses/comprehensive_rust_training/test_course.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 72ed32b7d..7e0b6e737 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,5 +1,3 @@ 100_generics.rst 110_std_types.rst 120_std_traits.rst -130_memory_management.rst -140_smart_pointers.rst From bc636564a9e544016e4ec7856ca88834800d528e Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Sun, 9 Feb 2025 16:50:53 -0500 Subject: [PATCH 15/55] Testing --- courses/comprehensive_rust_training/test_course.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 7e0b6e737..9120db804 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,3 +1,2 @@ 100_generics.rst 110_std_types.rst -120_std_traits.rst From 412e6cbbdff758950a8f17974a7d4e179d438314 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:19:04 -0500 Subject: [PATCH 16/55] Fixes? --- .../030_types_and_values/04_arithmetic.rst | 2 +- .../040_control_flow_basics/08_exercise.rst | 26 +++++++++---------- .../060_references/03_slices.rst | 2 +- .../04_let_control_flow.rst | 2 +- .../110_std_types/05_string.rst | 2 +- .../140_smart_pointers/02_rc.rst | 2 +- .../160_lifetimes/01_lifetime_annotations.rst | 2 +- .../180_modules/05_paths.rst | 2 +- .../200_error_handling/06_thiserror.rst | 4 +-- .../test_course.txt | 19 ++++++++++++++ 10 files changed, 41 insertions(+), 22 deletions(-) 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 index 91f88dcbf..c8f7f5d5d 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst @@ -30,7 +30,7 @@ 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 :rust:`i32`\ 's to :rust:`i16` to see an integer overflow, which +Change the :rust:`i32` to :rust:`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., 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 ff786cc87..fb176fe3f 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 @@ -10,19 +10,19 @@ 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 +- 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 diff --git a/courses/comprehensive_rust_training/060_references/03_slices.rst b/courses/comprehensive_rust_training/060_references/03_slices.rst index 1221a346f..1f7e74565 100644 --- a/courses/comprehensive_rust_training/060_references/03_slices.rst +++ b/courses/comprehensive_rust_training/060_references/03_slices.rst @@ -38,7 +38,7 @@ Details - To easily create a slice of the full array, we can therefore use :rust:`&a[..]`. -- :rust:`s` is a reference to a slice of :rust:`i32`\ s. Notice that the type +- :rust:`s` is a reference to a slice of :rust:`i32`. Notice that the type of :rust:`s` (:rust:`&[i32]`) no longer mentions the array length. This allows us to perform computation on slices of different sizes. 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 737d310d7..c95abc155 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 @@ -106,7 +106,7 @@ if-let let-else ---------- -:rust:`if-let`\ s can pile up, as shown. The :rust:`let-else` construct supports +:rust:`if-let` can pile up, as shown. The :rust:`let-else` construct supports flattening this nested code. Rewrite the awkward version for students, so they can see the transformation. 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 9cb65d412..0b15a04f0 100644 --- a/courses/comprehensive_rust_training/110_std_types/05_string.rst +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -51,7 +51,7 @@ Details this mostly explains the structure of the sidebar in the documentation. - :rust:`String` implements :rust:`Deref` which transparently - gives it access to :rust:`str`\ 's methods. + gives it access to :rust:`str` methods. - Write and compare :rust:`let s3 = s1.deref();` and :rust:`let s3 = &*s1;`. - :rust:`String` is implemented as a wrapper around a vector of bytes, many 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 cc68829f7..d79a35179 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -33,7 +33,7 @@ same data from multiple places: Details --------- -- :rust:`Rc`\ 's count ensures that its contained value is valid for as +- :rust:`Rc` count ensures that its contained value is valid for as long as there are references. - :rust:`Rc` in Rust is like :rust:`std::shared_ptr` in C++. - :rust:`Rc::clone` is cheap: it creates a pointer to the same allocation diff --git a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst index f723f6ca4..05c01f338 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst @@ -49,7 +49,7 @@ Details In this example, the compiler does not know what lifetime to infer for :rust:`p3`. Looking inside the function body shows that it can only safely -assume that :rust:`p3`\ 's lifetime is the shorter of :rust:`p1` and :rust:`p2`. But +assume that :rust:`p3` lifetime is the shorter of :rust:`p1` and :rust:`p2`. But just like types, Rust requires explicit annotations of lifetimes on function arguments and return values. diff --git a/courses/comprehensive_rust_training/180_modules/05_paths.rst b/courses/comprehensive_rust_training/180_modules/05_paths.rst index 8e3b5bb74..cba432b6c 100644 --- a/courses/comprehensive_rust_training/180_modules/05_paths.rst +++ b/courses/comprehensive_rust_training/180_modules/05_paths.rst @@ -48,7 +48,7 @@ Details crates with a convenient, short path. - For the most part, only items that appear in a module need to be - :rust:`use`\ 'd. However, a trait must be in scope to call any methods on + :rust:`use`. 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 :rust:`read_to_string` method on a type implementing the :rust:`Read` trait, you need to :rust:`use std::io::Read`. 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 cf36b73f9..b72d5b28e 100644 --- a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -50,6 +50,6 @@ Details of useful attributes to help define error types in a compact way. - The message from :rust:`#[error]` is used to derive the :rust:`Display` trait. -- Note that the (:rust:`thiserror::`)\ :rust:`Error` derive macro, while it has - the effect of implementing the (:rust:`std::error::`)\ :rust:`Error` trait, +- Note that the (:rust:`thiserror::`) :rust:`Error` derive macro, while it has + the effect of implementing the (:rust:`std::error::`) :rust:`Error` trait, is not the same this; traits and macros do not share a namespace. diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 9120db804..f53aca18c 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,2 +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 From 328d8dbacdb7458f93efc812944181ea40e87411 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Sun, 9 Feb 2025 22:49:42 -0500 Subject: [PATCH 17/55] More testing --- courses/comprehensive_rust_training/test_course.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index f53aca18c..6602d9db7 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -3,7 +3,6 @@ 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 From 437351d61f65c4ae224e4ffce59328742cfb40aa Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 12:08:55 -0500 Subject: [PATCH 18/55] Still testing --- courses/comprehensive_rust_training/test_course.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 6602d9db7..b3f78f82f 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,11 +1,3 @@ -010_introduction.rst -020_hello_world.rst -030_types_and_values.rst -040_control_flow_basics.rst -050_tuples_and_arrays.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 From 3b919a6b17c2e82d9858a5df704aae0144303546 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 12:27:56 -0500 Subject: [PATCH 19/55] More --- courses/comprehensive_rust_training/test_course.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index b3f78f82f..5b6db68da 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,9 +1,3 @@ -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 From 6079be6e99e466baa7a50b126418fa2a91b9d717 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 12:42:04 -0500 Subject: [PATCH 20/55] More test --- courses/comprehensive_rust_training/test_course.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 5b6db68da..00b07fc89 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,6 +1,7 @@ +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 78054cfe9b7f676ab095e757ec30052f809a2795 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 12:57:54 -0500 Subject: [PATCH 21/55] Still more testing --- courses/comprehensive_rust_training/test_course.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 00b07fc89..5aeb50670 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,7 +1,3 @@ -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 From 91326427d9f705f1668cd020aa78c86b30f5784b Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:06:47 -0500 Subject: [PATCH 22/55] More --- courses/comprehensive_rust_training/test_course.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 5aeb50670..fdeac15a3 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,3 +1,4 @@ -160_lifetimes.rst -170_iterators.rst 180_modules.rst +190_testing.rst +200_error_handling.rst +210_unsafe_rust.rst From 5707a27ad2b3c809257981ff3f2e627a8ecbef2b Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:09:06 -0500 Subject: [PATCH 23/55] Still more --- courses/comprehensive_rust_training/test_course.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index fdeac15a3..43cf940ce 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,4 +1,2 @@ 180_modules.rst 190_testing.rst -200_error_handling.rst -210_unsafe_rust.rst From b5c098910446ac1a265c9727945f5dab60a33f32 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:40:07 -0500 Subject: [PATCH 24/55] Once more --- courses/comprehensive_rust_training/test_course.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 43cf940ce..89f1cc545 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,2 +1,2 @@ -180_modules.rst 190_testing.rst +200_error_handling.rst From cbf1f1536041f45f56d991ea57aff16104910ec4 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:47:46 -0500 Subject: [PATCH 25/55] Should be it --- courses/comprehensive_rust_training/test_course.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 89f1cc545..e9870123d 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,2 +1,2 @@ -190_testing.rst 200_error_handling.rst +210_unsafe_rust.rst From f11bd8a82d46381ee4eea250395098c9fb0f0f3f Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:51:15 -0500 Subject: [PATCH 26/55] WTF --- courses/comprehensive_rust_training/test_course.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index e9870123d..fdeac15a3 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,2 +1,4 @@ +180_modules.rst +190_testing.rst 200_error_handling.rst 210_unsafe_rust.rst From 7b84f2a02d45c9d0ea4b565136cb9de98e6d3e49 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:01:11 -0500 Subject: [PATCH 27/55] Trying again --- .../comprehensive_rust_training/test_course.txt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index fdeac15a3..f373555da 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,4 +1,10 @@ -180_modules.rst -190_testing.rst -200_error_handling.rst -210_unsafe_rust.rst +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 From 8c381442c1d8de982237889a9caf2c9bb2326e21 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:05:29 -0500 Subject: [PATCH 28/55] OMG --- courses/comprehensive_rust_training/test_course.txt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index f373555da..96f7ede88 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,10 +1,5 @@ -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 + From 6401f27e0639c158b0416488f18296bca973144f Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:11:23 -0500 Subject: [PATCH 29/55] More --- courses/comprehensive_rust_training/test_course.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 96f7ede88..7f68ecbdc 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,5 +1,6 @@ -100_generics.rst -110_std_types.rst 120_std_traits.rst 130_memory_management.rst +140_smart_pointers.rst +150_borrowing.rst + From c31291a363d8c7b37a3eb7e9dd1d944ef316db7b Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:15:23 -0500 Subject: [PATCH 30/55] Still more --- courses/comprehensive_rust_training/test_course.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 7f68ecbdc..c9462c8be 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,6 +1,7 @@ -120_std_traits.rst -130_memory_management.rst 140_smart_pointers.rst 150_borrowing.rst +160_lifetimes.rst +170_iterators.rst + From b238b9e3924cd641938e245d5df2e0e33f7e561f Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:19:59 -0500 Subject: [PATCH 31/55] Again --- courses/comprehensive_rust_training/test_course.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index c9462c8be..5aeb50670 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,7 +1,3 @@ -140_smart_pointers.rst -150_borrowing.rst 160_lifetimes.rst 170_iterators.rst - - - +180_modules.rst From 84f2ae4969b0fc771bdbf4dee10ebb1dab1c5107 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:22:59 -0500 Subject: [PATCH 32/55] boring --- courses/comprehensive_rust_training/test_course.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 5aeb50670..9b9d388ea 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,3 +1,3 @@ -160_lifetimes.rst -170_iterators.rst +140_smart_pointers.rst +150_borrowing.rst 180_modules.rst From a8e076cc8b90592c218125dd5348e407152e605f Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:28:16 -0500 Subject: [PATCH 33/55] Last one? --- courses/comprehensive_rust_training/test_course.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt index 9b9d388ea..85a8adb0f 100644 --- a/courses/comprehensive_rust_training/test_course.txt +++ b/courses/comprehensive_rust_training/test_course.txt @@ -1,3 +1,2 @@ 140_smart_pointers.rst -150_borrowing.rst 180_modules.rst From 9f706b1bd8ecdf31e1d4bc6e90d202e84fb15f61 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:37:08 -0500 Subject: [PATCH 34/55] Narrowing it down --- courses/comprehensive_rust_training/140_smart_pointers.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/courses/comprehensive_rust_training/140_smart_pointers.rst b/courses/comprehensive_rust_training/140_smart_pointers.rst index 2ca203d4e..16b839a27 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -38,5 +38,3 @@ Smart Pointers .. 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 From 99620eacf03a87e5685f9bc83afd9475e52abec2 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:40:19 -0500 Subject: [PATCH 35/55] More --- courses/comprehensive_rust_training/140_smart_pointers.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/courses/comprehensive_rust_training/140_smart_pointers.rst b/courses/comprehensive_rust_training/140_smart_pointers.rst index 16b839a27..f747b6f69 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -37,4 +37,3 @@ Smart Pointers .. container:: PRELUDE END .. include:: 140_smart_pointers/01_box.rst -.. include:: 140_smart_pointers/02_rc.rst From 6e115e0097dc6071c3b1e6d08b743af6ba26444f Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:43:28 -0500 Subject: [PATCH 36/55] Last one? --- courses/comprehensive_rust_training/140_smart_pointers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courses/comprehensive_rust_training/140_smart_pointers.rst b/courses/comprehensive_rust_training/140_smart_pointers.rst index f747b6f69..2d81d40d0 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -36,4 +36,4 @@ Smart Pointers .. container:: PRELUDE END -.. include:: 140_smart_pointers/01_box.rst +.. include:: 140_smart_pointers/02_rc.rst From c7e33479da2528079b9a1f766c0e08a0aaf25c60 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:47:12 -0500 Subject: [PATCH 37/55] Closer --- .../140_smart_pointers/01_box.rst | 34 ------------------- 1 file changed, 34 deletions(-) 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 d40b1081d..33f8c3573 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -64,37 +64,3 @@ indirection: : : : : : : : : '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' - ---------- -Details ---------- - -- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's - guaranteed to be not null. - -- A :rust:`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 :rust:`Box` so only the pointer is moved. - -- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly - into the :rust:`List`, the compiler would not be able to compute a fixed - size for the struct in memory (the :rust:`List` would be of infinite - size). - -- :rust:`Box` solves this problem as it has the same size as a regular - pointer and just points at the next element of the :rust:`List` in the - heap. - -- Remove the :rust:`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 :rust:`Box` or - reference of some kind, instead of storing the value directly. - -- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be - empty/null. This makes :rust:`Box` one of the types that allow the - compiler to optimize storage of some enums (the "niche - optimization"). From 5f200e5ff4895d1037b77e9ce842561dbafe1d00 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:50:12 -0500 Subject: [PATCH 38/55] Closer still --- .../140_smart_pointers/01_box.rst | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 33f8c3573..7766a051e 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -64,3 +64,23 @@ indirection: : : : : : : : : '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' + +--------- +Details +--------- + +- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's + guaranteed to be not null. + +- A :rust:`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 :rust:`Box` so only the pointer is moved. + +- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly + into the :rust:`List`, the compiler would not be able to compute a fixed + size for the struct in memory (the :rust:`List` would be of infinite + size). From 55fbaa6b84be856c8f40d95c47f24b334c946971 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:54:21 -0500 Subject: [PATCH 39/55] Yeesh --- .../140_smart_pointers/01_box.rst | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) 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 7766a051e..f5022b53c 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -69,18 +69,16 @@ indirection: Details --------- -- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's - guaranteed to be not null. - -- A :rust:`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 :rust:`Box` so only the pointer is moved. - -- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly - into the :rust:`List`, the compiler would not be able to compute a fixed - size for the struct in memory (the :rust:`List` would be of infinite - size). +- :rust:`Box` solves this problem as it has the same size as a regular + pointer and just points at the next element of the :rust:`List` in the + heap. + +- Remove the :rust:`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 :rust:`Box` or + reference of some kind, instead of storing the value directly. + +- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be + empty/null. This makes :rust:`Box` one of the types that allow the + compiler to optimize storage of some enums (the "niche + optimization"). From 6415e013f8368172efc247dc7ac68240da5fc917 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:57:56 -0500 Subject: [PATCH 40/55] WTF --- .../140_smart_pointers/01_box.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 f5022b53c..e78f8986c 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -69,6 +69,19 @@ indirection: Details --------- +- A :rust:`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 :rust:`Box` so only the pointer is moved. + +- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly + into the :rust:`List`, the compiler would not be able to compute a fixed + size for the struct in memory (the :rust:`List` would be of infinite + size). + - :rust:`Box` solves this problem as it has the same size as a regular pointer and just points at the next element of the :rust:`List` in the heap. From eca6542b685d1703a9e75c83cde077f127b2a194 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:30:11 -0500 Subject: [PATCH 41/55] ... --- .../140_smart_pointers/01_box.rst | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) 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 e78f8986c..3a75903ef 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -69,29 +69,5 @@ indirection: Details --------- -- A :rust:`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 :rust:`Box` so only the pointer is moved. - -- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly - into the :rust:`List`, the compiler would not be able to compute a fixed - size for the struct in memory (the :rust:`List` would be of infinite - size). - -- :rust:`Box` solves this problem as it has the same size as a regular - pointer and just points at the next element of the :rust:`List` in the - heap. - -- Remove the :rust:`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 :rust:`Box` or - reference of some kind, instead of storing the value directly. - -- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be - empty/null. This makes :rust:`Box` one of the types that allow the - compiler to optimize storage of some enums (the "niche - optimization"). +- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's + guaranteed to be not null. From e42fc595d481d39996ddaa7463b0f1f15f29a859 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:34:03 -0500 Subject: [PATCH 42/55] WTF! --- .../140_smart_pointers/01_box.rst | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) 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 3a75903ef..d40b1081d 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -71,3 +71,30 @@ Details - :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's guaranteed to be not null. + +- A :rust:`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 :rust:`Box` so only the pointer is moved. + +- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly + into the :rust:`List`, the compiler would not be able to compute a fixed + size for the struct in memory (the :rust:`List` would be of infinite + size). + +- :rust:`Box` solves this problem as it has the same size as a regular + pointer and just points at the next element of the :rust:`List` in the + heap. + +- Remove the :rust:`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 :rust:`Box` or + reference of some kind, instead of storing the value directly. + +- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be + empty/null. This makes :rust:`Box` one of the types that allow the + compiler to optimize storage of some enums (the "niche + optimization"). From bd6d978e0956588a69ec565fe102ec1f226fd0c7 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:38:09 -0500 Subject: [PATCH 43/55] ridiculous --- courses/comprehensive_rust_training/140_smart_pointers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courses/comprehensive_rust_training/140_smart_pointers.rst b/courses/comprehensive_rust_training/140_smart_pointers.rst index 2d81d40d0..f747b6f69 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -36,4 +36,4 @@ Smart Pointers .. container:: PRELUDE END -.. include:: 140_smart_pointers/02_rc.rst +.. include:: 140_smart_pointers/01_box.rst From 0c4154127485d342aa5a2fee3ea93d80dd3b7e2e Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:41:19 -0500 Subject: [PATCH 44/55] ... --- .../140_smart_pointers/01_box.rst | 34 ------------------- 1 file changed, 34 deletions(-) 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 d40b1081d..33f8c3573 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -64,37 +64,3 @@ indirection: : : : : : : : : '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' - ---------- -Details ---------- - -- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's - guaranteed to be not null. - -- A :rust:`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 :rust:`Box` so only the pointer is moved. - -- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly - into the :rust:`List`, the compiler would not be able to compute a fixed - size for the struct in memory (the :rust:`List` would be of infinite - size). - -- :rust:`Box` solves this problem as it has the same size as a regular - pointer and just points at the next element of the :rust:`List` in the - heap. - -- Remove the :rust:`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 :rust:`Box` or - reference of some kind, instead of storing the value directly. - -- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be - empty/null. This makes :rust:`Box` one of the types that allow the - compiler to optimize storage of some enums (the "niche - optimization"). From 62d604cdf6795ece89670ff12d388b97277d18ab Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:44:02 -0500 Subject: [PATCH 45/55] 111 --- .../140_smart_pointers/01_box.rst | 13 ------------- 1 file changed, 13 deletions(-) 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 33f8c3573..6a5d5e62e 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -51,16 +51,3 @@ indirection: 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 | // | // | : - : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : - : : : : - : : : : - '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' From 70967c15b2da347ca5139bf01fc0c57d213047c9 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:47:27 -0500 Subject: [PATCH 46/55] 222 --- .../140_smart_pointers/01_box.rst | 13 ------------- 1 file changed, 13 deletions(-) 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 6a5d5e62e..020509968 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -16,19 +16,6 @@ owned pointer to data on the heap: println!("five: {}", *five); } -.. code:: bob - - Stack Heap - .- - - - - - -. .- - - - - - -. - : : : : - : five : : : - : +-----+ : : +-----+ : - : | o---|---+-----+-->| 5 | : - : +-----+ : : +-----+ : - : : : : - : : : : - `- - - - - - -' `- - - - - - -' - :rust:`Box` implements :rust:`Deref`, which means that you can `call methods from T directly on a Box `__. From 7476d0d28e28f4eceecb61c52ab0f61b915955a3 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:52:43 -0500 Subject: [PATCH 47/55] 333 --- .../140_smart_pointers/01_box.rst | 19 ------------------- 1 file changed, 19 deletions(-) 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 020509968..e9b2a0f10 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -19,22 +19,3 @@ owned pointer to data on the heap: :rust:`Box` implements :rust:`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. :rust:`Box` accomplishes that -indirection: - -.. code:: rust - - #[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:?}"); - } From 7125c069ee17fe4300b4a8bfc3e3b8216730ee4f Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:56:27 -0500 Subject: [PATCH 48/55] 444 --- .../comprehensive_rust_training/140_smart_pointers/01_box.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e9b2a0f10..6f69a6d87 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -1,9 +1,9 @@ ============ -"Box" +"Box" ============ ------------ -"Box" +"Box" ------------ `Box `__ is an From 9b2a97717c78dfbf5f05b942b155ddd90b7d34c5 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:58:49 -0500 Subject: [PATCH 49/55] 555 --- .../comprehensive_rust_training/140_smart_pointers/01_box.rst | 4 ---- 1 file changed, 4 deletions(-) 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 6f69a6d87..7a0ed9285 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -15,7 +15,3 @@ owned pointer to data on the heap: let five = Box::new(5); println!("five: {}", *five); } - -:rust:`Box` implements :rust:`Deref`, which means that you can -`call methods from T directly on a Box `__. - From bdc6f045adb1b9fbabf7127960bd790235ced6ce Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:02:40 -0500 Subject: [PATCH 50/55] Maybe --- .../140_smart_pointers/01_box.rst | 5 +++++ 1 file changed, 5 insertions(+) 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 7a0ed9285..6938087fe 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -15,3 +15,8 @@ owned pointer to data on the heap: let five = Box::new(5); println!("five: {}", *five); } + +:rust:`Box` implements :rust:`Deref`, which means that you can +`call methods from T directly `__. +on a :rust:`Box` + From 87053ca132ac2d9faf09b8bca4b09482f0ffb321 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:07:30 -0500 Subject: [PATCH 51/55] Fixed? --- .../140_smart_pointers.rst | 3 + .../140_smart_pointers/01_box.rst | 83 ++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/courses/comprehensive_rust_training/140_smart_pointers.rst b/courses/comprehensive_rust_training/140_smart_pointers.rst index f747b6f69..2ca203d4e 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -37,3 +37,6 @@ Smart Pointers .. 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 index 6938087fe..c79acfb7c 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -1,9 +1,9 @@ ============ -"Box" +"Box" ============ ------------ -"Box" +"Box" ------------ `Box `__ is an @@ -16,7 +16,86 @@ owned pointer to data on the heap: println!("five: {}", *five); } +.. code:: bob + + Stack Heap + .- - - - - - -. .- - - - - - -. + : : : : + : five : : : + : +-----+ : : +-----+ : + : | o---|---+-----+-->| 5 | : + : +-----+ : : +-----+ : + : : : : + : : : : + `- - - - - - -' `- - - - - - -' + :rust:`Box` implements :rust:`Deref`, which means that you can `call methods from T directly `__. on a :rust:`Box` +Recursive data types or data types with dynamic sizes cannot be stored +inline without a pointer indirection. :rust:`Box` accomplishes that +indirection: + +.. code:: rust + + #[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 | // | // | : + : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : + : : : : + : : : : + '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' + +--------- +Details +--------- + +- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's + guaranteed to be not null. + +- A :rust:`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 :rust:`Box` so only the pointer is moved. + +- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly + into the :rust:`List`, the compiler would not be able to compute a fixed + size for the struct in memory (the :rust:`List` would be of infinite + size). + +- :rust:`Box` solves this problem as it has the same size as a regular + pointer and just points at the next element of the :rust:`List` in the + heap. + +- Remove the :rust:`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 :rust:`Box` or + reference of some kind, instead of storing the value directly. + +- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be + empty/null. This makes :rust:`Box` one of the types that allow the + compiler to optimize storage of some enums (the "niche + optimization"). From a61753240294a7e908a261d67b33256b74f9f775 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:10:26 -0500 Subject: [PATCH 52/55] Fixed! --- DO_NOT_MERGE.SINGLE_COURSE | 1 - courses/comprehensive_rust_training/test_course.txt | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 DO_NOT_MERGE.SINGLE_COURSE delete mode 100644 courses/comprehensive_rust_training/test_course.txt diff --git a/DO_NOT_MERGE.SINGLE_COURSE b/DO_NOT_MERGE.SINGLE_COURSE deleted file mode 100644 index 7ca9521b8..000000000 --- a/DO_NOT_MERGE.SINGLE_COURSE +++ /dev/null @@ -1 +0,0 @@ -comprehensive_rust_training/test_course.txt diff --git a/courses/comprehensive_rust_training/test_course.txt b/courses/comprehensive_rust_training/test_course.txt deleted file mode 100644 index 85a8adb0f..000000000 --- a/courses/comprehensive_rust_training/test_course.txt +++ /dev/null @@ -1,2 +0,0 @@ -140_smart_pointers.rst -180_modules.rst From 02b143cf8d6da6698b8b674784a0cf72bce4d6bb Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:20:37 -0500 Subject: [PATCH 53/55] Fix more instances of the problem --- .../comprehensive_rust_training/110_std_types/05_string.rst | 6 +++++- .../comprehensive_rust_training/110_std_types/06_vec.rst | 6 +++++- .../110_std_types/07_hashmap.rst | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) 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 0b15a04f0..c03ad5b84 100644 --- a/courses/comprehensive_rust_training/110_std_types/05_string.rst +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -26,7 +26,11 @@ is a growable UTF-8 encoded string: } :rust:`String` implements -`Deref `__, +:rust:`Deref` + +.. + https://doc.rust-lang.org/std/string/struct.String.html#deref-methods-str + which means that you can call all :rust:`str` methods on a :rust:`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 1a3a517cf..c82554155 100644 --- a/courses/comprehensive_rust_training/110_std_types/06_vec.rst +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -34,7 +34,11 @@ standard resizable heap-allocated buffer: } :rust:`Vec` implements -`Deref `__, +:rust:`Deref` + +.. + https://doc.rust-lang.org/std/vec/struct.Vec.html#deref-methods-%5BT%5D + which means that you can call slice methods on a :rust:`Vec`. --------- 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 95e58e9e7..3a550502a 100644 --- a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -66,7 +66,11 @@ Details macro. - Although, since Rust 1.56, HashMap implements - `From<[(K, V); N]> `__, + rust:`From<[(K, V); N]>` + + .. + https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.html#impl-From%3C%5B(K,+V);+N%5D%3E-for-HashMap%3CK,+V,+RandomState%3E + which allows us to easily initialize a hash map from a literal array: From fdf34e009b61f132a06f3baf22d89c7521cf23b8 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:37:33 -0500 Subject: [PATCH 54/55] Fix line endings --- .../010_introduction.rst | 88 ++--- .../010_introduction/01-about_adacore.rst | 42 +- .../02-about_this_training.rst | 166 ++++---- .../020_hello_world.rst | 82 ++-- .../020_hello_world/01_what_is_rust.rst | 82 ++-- .../020_hello_world/02_benefits.rst | 120 +++--- .../020_hello_world/03_playground.rst | 64 +-- .../030_types_and_values.rst | 88 ++--- .../030_types_and_values/01_hello_world.rst | 112 +++--- .../030_types_and_values/02_variables.rst | 70 ++-- .../030_types_and_values/03_values.rst | 96 ++--- .../030_types_and_values/04_arithmetic.rst | 80 ++-- .../030_types_and_values/05_inference.rst | 108 +++--- .../030_types_and_values/06_exercise.rst | 56 +-- .../040_control_flow_basics.rst | 92 ++--- .../040_control_flow_basics/01_if.rst | 96 ++--- .../040_control_flow_basics/02_match.rst | 160 ++++---- .../040_control_flow_basics/03_loops.rst | 172 ++++----- .../04_break_continue.rst | 156 ++++---- .../05_blocks_and_scopes.rst | 68 ++-- .../040_control_flow_basics/06_functions.rst | 84 ++-- .../040_control_flow_basics/07_macros.rst | 104 ++--- .../040_control_flow_basics/08_exercise.rst | 78 ++-- .../050_tuples_and_arrays.rst | 86 ++--- .../050_tuples_and_arrays/01_arrays.rst | 80 ++-- .../050_tuples_and_arrays/02_tuples.rst | 60 +-- .../050_tuples_and_arrays/03_iteration.rst | 62 +-- .../04_destructuring.rst | 90 ++--- .../050_tuples_and_arrays/05_exercise.rst | 108 +++--- .../060_references.rst | 88 ++--- .../060_references/01_shared.rst | 132 +++---- .../060_references/02_exclusive.rst | 72 ++-- .../060_references/03_slices.rst | 92 ++--- .../060_references/04_strings.rst | 166 ++++---- .../060_references/05_dangling.rst | 84 ++-- .../060_references/06_exercise.rst | 68 ++-- .../070_user_defined_types.rst | 90 ++--- .../01_named_structs.rst | 126 +++--- .../02_tuple_structs.rst | 124 +++--- .../070_user_defined_types/03_enums.rst | 258 ++++++------- .../070_user_defined_types/04_aliases.rst | 68 ++-- .../070_user_defined_types/05_const.rst | 102 ++--- .../070_user_defined_types/06_static.rst | 98 ++--- .../070_user_defined_types/07_exercise.rst | 92 ++--- .../080_pattern_matching.rst | 86 ++--- .../080_pattern_matching/01_match.rst | 162 ++++---- .../02_destructuring_structs.rst | 50 +-- .../03_destructuring_enums.rst | 112 +++--- .../04_let_control_flow.rst | 284 +++++++------- .../080_pattern_matching/05_exercise.rst | 160 ++++---- .../090_methods_and_traits.rst | 84 ++-- .../090_methods_and_traits/01_methods.rst | 202 +++++----- .../090_methods_and_traits/02_traits.rst | 322 ++++++++-------- .../090_methods_and_traits/03_deriving.rst | 72 ++-- .../090_methods_and_traits/04_exercise.rst | 58 +-- .../100_generics.rst | 90 ++--- .../100_generics/01_generic_functions.rst | 114 +++--- .../100_generics/02_generic_data.rst | 108 +++--- .../100_generics/03_generic_traits.rst | 110 +++--- .../100_generics/04_trait_bounds.rst | 112 +++--- .../100_generics/05_impl_trait.rst | 114 +++--- .../100_generics/06_dyn_trait.rst | 180 ++++----- .../100_generics/07_exercise.rst | 60 +-- .../110_std_types.rst | 92 ++--- .../110_std_types/01_std.rst | 44 +-- .../110_std_types/02_docs.rst | 108 +++--- .../110_std_types/03_option.rst | 88 ++--- .../110_std_types/04_result.rst | 98 ++--- .../110_std_types/05_string.rst | 150 ++++---- .../110_std_types/06_vec.rst | 124 +++--- .../110_std_types/07_hashmap.rst | 180 ++++----- .../110_std_types/08_exercise.rst | 112 +++--- .../120_std_traits.rst | 92 ++--- .../120_std_traits/01_comparisons.rst | 166 ++++---- .../120_std_traits/02_operators.rst | 122 +++--- .../120_std_traits/03_from_and_into.rst | 98 ++--- .../120_std_traits/04_casting.rst | 94 ++--- .../120_std_traits/05_read_and_write.rst | 102 ++--- .../120_std_traits/06_default.rst | 118 +++--- .../120_std_traits/07_closures.rst | 174 ++++----- .../120_std_traits/08_exercise.rst | 46 +-- .../130_memory_management.rst | 92 ++--- .../130_memory_management/01_review.rst | 170 ++++---- .../130_memory_management/02_approaches.rst | 122 +++--- .../130_memory_management/03_ownership.rst | 72 ++-- .../130_memory_management/04_move.rst | 364 +++++++++--------- .../130_memory_management/05_clone.rst | 80 ++-- .../130_memory_management/06_copy_types.rst | 138 +++---- .../130_memory_management/07_drop.rst | 136 +++---- .../130_memory_management/08_exercise.rst | 88 ++--- .../140_smart_pointers.rst | 84 ++-- .../140_smart_pointers/01_box.rst | 202 +++++----- .../140_smart_pointers/02_rc.rst | 94 ++--- .../140_smart_pointers/03_trait_objects.rst | 222 +++++------ .../140_smart_pointers/04_exercise.rst | 52 +-- .../150_borrowing.rst | 86 ++--- .../150_borrowing/01_shared.rst | 158 ++++---- .../150_borrowing/02_borrowck.rst | 140 +++---- .../150_borrowing/03_examples.rst | 78 ++-- .../150_borrowing/04_interior_mutability.rst | 226 +++++------ .../150_borrowing/05_exercise.rst | 56 +-- .../160_lifetimes.rst | 84 ++-- .../160_lifetimes/01_lifetime_annotations.rst | 132 +++---- .../160_lifetimes/02_lifetime_elision.rst | 146 +++---- .../160_lifetimes/03_struct_lifetimes.rst | 94 ++--- .../160_lifetimes/04_exercise.rst | 222 +++++------ .../170_iterators.rst | 88 ++--- .../170_iterators/01_motivation.rst | 136 +++---- .../170_iterators/02_iterator.rst | 146 +++---- .../170_iterators/03_helpers.rst | 94 ++--- .../170_iterators/04_collect.rst | 112 +++--- .../170_iterators/05_intoiterator.rst | 184 ++++----- .../170_iterators/06_exercise.rst | 48 +-- .../180_modules.rst | 88 ++--- .../180_modules/01_modules.rst | 82 ++-- .../180_modules/02_filesystem.rst | 160 ++++---- .../180_modules/03_visibility.rst | 114 +++--- .../180_modules/04_encapsulation.rst | 170 ++++---- .../180_modules/05_paths.rst | 116 +++--- .../180_modules/06_exercise.rst | 96 ++--- .../190_testing.rst | 84 ++-- .../190_testing/01_unit_tests.rst | 102 ++--- .../190_testing/02_other.rst | 106 ++--- .../190_testing/03_lints.rst | 68 ++-- .../190_testing/04_exercise.rst | 78 ++-- .../200_error_handling.rst | 92 ++--- .../200_error_handling/01_panics.rst | 116 +++--- .../200_error_handling/02_result.rst | 174 ++++----- .../200_error_handling/03_try.rst | 138 +++---- .../200_error_handling/04_try_conversions.rst | 196 +++++----- .../200_error_handling/05_error.rst | 98 ++--- .../200_error_handling/06_thiserror.rst | 110 +++--- .../200_error_handling/07_anyhow.rst | 148 +++---- .../200_error_handling/08_exercise.rst | 54 +-- .../210_unsafe_rust.rst | 90 ++--- .../210_unsafe_rust/01_unsafe.rst | 82 ++-- .../210_unsafe_rust/02_dereferencing.rst | 130 +++---- .../210_unsafe_rust/03_mutable_static.rst | 106 ++--- .../210_unsafe_rust/04_unions.rst | 72 ++-- .../210_unsafe_rust/05_unsafe_functions.rst | 212 +++++----- .../210_unsafe_rust/06_unsafe_traits.rst | 88 ++--- .../210_unsafe_rust/07_exercise.rst | 170 ++++---- 142 files changed, 8192 insertions(+), 8192 deletions(-) diff --git a/courses/comprehensive_rust_training/010_introduction.rst b/courses/comprehensive_rust_training/010_introduction.rst index 83f5ebfdf..871d7e9d0 100644 --- a/courses/comprehensive_rust_training/010_introduction.rst +++ b/courses/comprehensive_rust_training/010_introduction.rst @@ -1,44 +1,44 @@ -************ -Introduction -************ - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************ +Introduction +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 58c6a6afe..24d52ae24 100644 --- a/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst +++ b/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst @@ -1,21 +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) - +============= +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 index 85b97e022..81b578774 100644 --- a/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst +++ b/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst @@ -1,83 +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 +=================== +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/020_hello_world.rst b/courses/comprehensive_rust_training/020_hello_world.rst index d90348eb2..dfebc51e3 100644 --- a/courses/comprehensive_rust_training/020_hello_world.rst +++ b/courses/comprehensive_rust_training/020_hello_world.rst @@ -1,41 +1,41 @@ -************* -Hello World -************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************* +Hello World +************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 00677b9ae..7ba69ab11 100644 --- 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 @@ -1,41 +1,41 @@ -=============== -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++ - - - :rust:`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. - ---------- -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. +=============== +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++ + + - :rust:`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. + +--------- +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. diff --git a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst index 14bd1c3e1..5203a9644 100644 --- a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst +++ b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst @@ -1,60 +1,60 @@ -================== -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 :rust:`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. - ---------- -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). +================== +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 :rust:`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. + +--------- +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). diff --git a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst index 023e8f603..af0099428 100644 --- a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst +++ b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst @@ -1,32 +1,32 @@ -============ -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 :rust:`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. - ---------- -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. +============ +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 :rust:`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. + +--------- +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. diff --git a/courses/comprehensive_rust_training/030_types_and_values.rst b/courses/comprehensive_rust_training/030_types_and_values.rst index c45f40579..4444b8c0b 100644 --- a/courses/comprehensive_rust_training/030_types_and_values.rst +++ b/courses/comprehensive_rust_training/030_types_and_values.rst @@ -1,44 +1,44 @@ -****************** -Types And Values -****************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +****************** +Types And Values +****************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index a1ea568d3..6661e6cc8 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 @@ -1,56 +1,56 @@ -============== -Hello, World -============== - --------------- -Hello, World --------------- - -Let us jump into the simplest possible Rust program, a classic Hello -World program: - -.. code:: rust - - fn main() { - println!("Hello World!"); - } - -What you see: - -- Functions are introduced with :rust:`fn`. -- Blocks are delimited by curly braces like in C and C++. -- The :rust:`main` function is the entry point of the program. -- Rust has hygienic macros, :rust:`println!` is an example of this. -- Rust strings are UTF-8 encoded and can contain any Unicode character. - ---------- -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 `__. +============== +Hello, World +============== + +-------------- +Hello, World +-------------- + +Let us jump into the simplest possible Rust program, a classic Hello +World program: + +.. code:: rust + + fn main() { + println!("Hello World!"); + } + +What you see: + +- Functions are introduced with :rust:`fn`. +- Blocks are delimited by curly braces like in C and C++. +- The :rust:`main` function is the entry point of the program. +- Rust has hygienic macros, :rust:`println!` is an example of this. +- Rust strings are UTF-8 encoded and can contain any Unicode character. + +--------- +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 `__. 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 index ebce89d7e..f1f361c19 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst @@ -1,35 +1,35 @@ -=========== -Variables -=========== - ------------ -Variables ------------ - -Rust provides type safety via static typing. Variable bindings are made -with :rust:`let`: - -.. code:: rust - - fn main() { - let x: i32 = 10; - println!("x: {x}"); - // x = 20; - // println!("x: {x}"); - } - ---------- -Details ---------- - -- Uncomment the :rust:`x = 20` to demonstrate that variables are immutable - by default. Add the :rust:`mut` keyword to allow changes. - -- Warnings are enabled for this slide, such as for unused variables or - unnecessary :rust:`mut`. These are omitted in most slides to avoid - distracting warnings. Try removing the mutation but leaving the - :rust:`mut` keyword in place. - -- The :rust:`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. +=========== +Variables +=========== + +----------- +Variables +----------- + +Rust provides type safety via static typing. Variable bindings are made +with :rust:`let`: + +.. code:: rust + + fn main() { + let x: i32 = 10; + println!("x: {x}"); + // x = 20; + // println!("x: {x}"); + } + +--------- +Details +--------- + +- Uncomment the :rust:`x = 20` to demonstrate that variables are immutable + by default. Add the :rust:`mut` keyword to allow changes. + +- Warnings are enabled for this slide, such as for unused variables or + unnecessary :rust:`mut`. These are omitted in most slides to avoid + distracting warnings. Try removing the mutation but leaving the + :rust:`mut` keyword in place. + +- The :rust:`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. 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 691522290..1a865d0ac 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 @@ -1,48 +1,48 @@ -======== -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'``, | -| scalar values | | ':math:`\alpha`', | -| | | ':math:`\infty`' | -+---------------+-------------------------------+---------------------+ -| Booleans | ``bool`` | ``true``, ``false`` | -+---------------+-------------------------------+---------------------+ - -The types have widths as follows: - -- :rust:`iN`, :rust:`uN`, and :rust:`fN` are *N* bits wide, -- :rust:`isize` and :rust:`usize` are the width of a pointer, -- :rust:`char` is 32 bits wide, -- :rust:`bool` is 8 bits wide. - ---------- -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 :rust:`1_000` can be written as :rust:`1000` (or :rust:`10_00`), and - :rust:`123_i64` can be written as :rust:`123i64`. +======== +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'``, | +| scalar values | | ':math:`\alpha`', | +| | | ':math:`\infty`' | ++---------------+-------------------------------+---------------------+ +| Booleans | ``bool`` | ``true``, ``false`` | ++---------------+-------------------------------+---------------------+ + +The types have widths as follows: + +- :rust:`iN`, :rust:`uN`, and :rust:`fN` are *N* bits wide, +- :rust:`isize` and :rust:`usize` are the width of a pointer, +- :rust:`char` is 32 bits wide, +- :rust:`bool` is 8 bits wide. + +--------- +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 :rust:`1_000` can be written as :rust:`1000` (or :rust:`10_00`), and + :rust:`123_i64` can be written as :rust:`123i64`. 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 index c8f7f5d5d..f929b2e1c 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst @@ -1,40 +1,40 @@ -============ -Arithmetic -============ - ------------- -Arithmetic ------------- - -.. code:: rust - - fn interproduct(a: i32, b: i32, c: i32) -> i32 { - return a * b + b * c + c * a; - } - - fn main() { - println!("result: {}", interproduct(120, 100, 248)); - } - ---------- -Details ---------- - -This is the first time we've seen a function other than :rust:`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 :rust:`i32` to :rust:`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., -:rust:`(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. +============ +Arithmetic +============ + +------------ +Arithmetic +------------ + +.. code:: rust + + fn interproduct(a: i32, b: i32, c: i32) -> i32 { + return a * b + b * c + c * a; + } + + fn main() { + println!("result: {}", interproduct(120, 100, 248)); + } + +--------- +Details +--------- + +This is the first time we've seen a function other than :rust:`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 :rust:`i32` to :rust:`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., +:rust:`(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. 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 index 8e09f3fd7..c555187b1 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst @@ -1,54 +1,54 @@ -================ -Type Inference -================ - ----------------- -Type Inference ----------------- - -Rust will look at how the variable is *used* to determine the type: - -.. code:: rust - - 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); - } - ---------- -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 -:rust:`i32`. This sometimes appears as :rust:`{integer}` in error messages. -Similarly, floating-point literals default to :rust:`f64`. - -.. code:: rust - - fn main() { - let x = 3.14; - let y = 20; - assert_eq!(x, y); - // ERROR: no implementation for `{float} == {integer}` - } +================ +Type Inference +================ + +---------------- +Type Inference +---------------- + +Rust will look at how the variable is *used* to determine the type: + +.. code:: rust + + 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); + } + +--------- +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 +:rust:`i32`. This sometimes appears as :rust:`{integer}` in error messages. +Similarly, floating-point literals default to :rust:`f64`. + +.. code:: rust + + fn main() { + let x = 3.14; + let y = 20; + assert_eq!(x, y); + // ERROR: no implementation for `{float} == {integer}` + } 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 index 728a04e3a..6db2faa8a 100644 --- a/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst +++ b/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst @@ -1,28 +1,28 @@ -===================== -Exercise: Fibonacci -===================== - ---------------------- -Exercise: Fibonacci ---------------------- - -The Fibonacci sequence begins with :rust:`[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 :rust:`fib(n)` that calculates the n'th Fibonacci number. -When will this function 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}} +===================== +Exercise: Fibonacci +===================== + +--------------------- +Exercise: Fibonacci +--------------------- + +The Fibonacci sequence begins with :rust:`[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 :rust:`fib(n)` that calculates the n'th Fibonacci number. +When will this function 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 index 3caa1bd06..856f38bac 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics.rst @@ -1,46 +1,46 @@ -********************* -Control Flow Basics -********************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +********************* +Control Flow Basics +********************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index dcb2cf5fd..25677defc 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 @@ -1,48 +1,48 @@ -==================== -"if" expressions -==================== - --------------------- -"if" expressions --------------------- - -You use -`if expressions `__ -exactly like :rust:`if` statements in other languages: - -.. code:: rust - - fn main() { - let x = 10; - if x == 0 { - println!("zero!"); - } else if x < 100 { - println!("biggish"); - } else { - println!("huge"); - } - } - -In addition, you can use :rust:`if` as an expression. The last expression of -each block becomes the value of the :rust:`if` expression: - -.. code:: rust - - fn main() { - let x = 10; - let size = if x < 20 { "small" } else { "large" }; - println!("number size: {}", size); - } - ---------- -Details ---------- - -Because :rust:`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 -:rust:`;` after :rust:`"small"` in the second example. - -An :rust:`if` expression should be used in the same way as the other -expressions. For example, when it is used in a :rust:`let` statement, the -statement must be terminated with a :rust:`;` as well. Remove the :rust:`;` -before :rust:`println!` to see the compiler error. +==================== +"if" expressions +==================== + +-------------------- +"if" expressions +-------------------- + +You use +`if expressions `__ +exactly like :rust:`if` statements in other languages: + +.. code:: rust + + fn main() { + let x = 10; + if x == 0 { + println!("zero!"); + } else if x < 100 { + println!("biggish"); + } else { + println!("huge"); + } + } + +In addition, you can use :rust:`if` as an expression. The last expression of +each block becomes the value of the :rust:`if` expression: + +.. code:: rust + + fn main() { + let x = 10; + let size = if x < 20 { "small" } else { "large" }; + println!("number size: {}", size); + } + +--------- +Details +--------- + +Because :rust:`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 +:rust:`;` after :rust:`"small"` in the second example. + +An :rust:`if` expression should be used in the same way as the other +expressions. For example, when it is used in a :rust:`let` statement, the +statement must be terminated with a :rust:`;` as well. Remove the :rust:`;` +before :rust:`println!` to see the compiler error. 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 index 5bef5cb4a..2846f0a82 100644 --- a/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst +++ b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst @@ -1,80 +1,80 @@ -======================= -"match" Expressions -======================= - ------------------------ -"match" Expressions ------------------------ - -:rust:`match` can be used to check a value against one or more options: - -.. code:: rust - - fn main() { - let val = 1; - match val { - 1 => println!("one"), - 10 => println!("ten"), - 100 => println!("one hundred"), - _ => { - println!("something else"); - } - } - } - -Like :rust:`if` expressions, :rust:`match` can also return a value; - -.. code:: rust - - fn main() { - let flag = true; - let val = match flag { - true => 1, - false => 0, - }; - println!("The value of {flag} is {val}"); - } - ---------- -Details ---------- - -- :rust:`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 :rust:`switch` works - in other languages. - -- The body of a :rust:`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. - -- :rust:`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 :rust:`_`. Exhaustiveness is easiest to demonstrate with enums, but - enums haven't been introduced yet. Instead we demonstrate matching on - a :rust:`bool`, which is the simplest primitive type. - -- This slide introduces :rust:`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 :rust:`match`, you can compare the - examples to their equivalents written with :rust:`if`. In the second case - matching on a :rust:`bool` an :rust:`if {} else {}` block is pretty similar. - But in the first example that checks multiple cases, a :rust:`match` - expression can be more concise than - :rust:`if {} else if {} else if {} else`. - -- :rust:`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. +======================= +"match" Expressions +======================= + +----------------------- +"match" Expressions +----------------------- + +:rust:`match` can be used to check a value against one or more options: + +.. code:: rust + + fn main() { + let val = 1; + match val { + 1 => println!("one"), + 10 => println!("ten"), + 100 => println!("one hundred"), + _ => { + println!("something else"); + } + } + } + +Like :rust:`if` expressions, :rust:`match` can also return a value; + +.. code:: rust + + fn main() { + let flag = true; + let val = match flag { + true => 1, + false => 0, + }; + println!("The value of {flag} is {val}"); + } + +--------- +Details +--------- + +- :rust:`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 :rust:`switch` works + in other languages. + +- The body of a :rust:`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. + +- :rust:`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 :rust:`_`. Exhaustiveness is easiest to demonstrate with enums, but + enums haven't been introduced yet. Instead we demonstrate matching on + a :rust:`bool`, which is the simplest primitive type. + +- This slide introduces :rust:`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 :rust:`match`, you can compare the + examples to their equivalents written with :rust:`if`. In the second case + matching on a :rust:`bool` an :rust:`if {} else {}` block is pretty similar. + But in the first example that checks multiple cases, a :rust:`match` + expression can be more concise than + :rust:`if {} else if {} else if {} else`. + +- :rust:`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. 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 16047d2f2..bc6c872ea 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 @@ -1,86 +1,86 @@ -======= -Loops -======= - -------- -Loops -------- - -There are three looping keywords in Rust: :rust:`while`, :rust:`loop`, and -:rust:`for`: - ------------ -"while" ------------ - -The -`while keyword `__ -works much like in other languages, executing the loop body as long as -the condition is true. - -.. code:: rust - - fn main() { - let mut x = 200; - while x >= 10 { - x = x / 2; - } - println!("Final x: {x}"); - } - -------------- -"for" -------------- - -The :rust:`for` `loop `__ -iterates over ranges of values or the items in a collection: - -.. code:: rust - - fn main() { - for x in 1..5 { - println!("x: {x}"); - } - - for elem in [1, 2, 3, 4, 5] { - println!("elem: {elem}"); - } - } - ---------------- -"for" Details ---------------- - -- Under the hood :rust:`for` loops use a concept called :dfn:`iterators` to - handle iterating over different kinds of ranges/collections. - Iterators will be discussed in more detail later. -- Note that the first :rust:`for` loop only iterates to :rust:`4`. Show the - :rust:`1..=5` syntax for an inclusive range. - --------------- -"loop" --------------- - -The :rust:`loop` -`statement `__ just -loops forever, until a :rust:`break`. - -.. code:: rust - - fn main() { - let mut i = 0; - loop { - i += 1; - println!("{i}"); - if i > 100 { - break; - } - } - } - ----------------- -"loop" Details ----------------- - -- The :rust:`loop` statement works like a :rust:`while true` loop. Use it for - things like servers which will serve connections forever. +======= +Loops +======= + +------- +Loops +------- + +There are three looping keywords in Rust: :rust:`while`, :rust:`loop`, and +:rust:`for`: + +----------- +"while" +----------- + +The +`while keyword `__ +works much like in other languages, executing the loop body as long as +the condition is true. + +.. code:: rust + + fn main() { + let mut x = 200; + while x >= 10 { + x = x / 2; + } + println!("Final x: {x}"); + } + +------------- +"for" +------------- + +The :rust:`for` `loop `__ +iterates over ranges of values or the items in a collection: + +.. code:: rust + + fn main() { + for x in 1..5 { + println!("x: {x}"); + } + + for elem in [1, 2, 3, 4, 5] { + println!("elem: {elem}"); + } + } + +--------------- +"for" Details +--------------- + +- Under the hood :rust:`for` loops use a concept called :dfn:`iterators` to + handle iterating over different kinds of ranges/collections. + Iterators will be discussed in more detail later. +- Note that the first :rust:`for` loop only iterates to :rust:`4`. Show the + :rust:`1..=5` syntax for an inclusive range. + +-------------- +"loop" +-------------- + +The :rust:`loop` +`statement `__ just +loops forever, until a :rust:`break`. + +.. code:: rust + + fn main() { + let mut i = 0; + loop { + i += 1; + println!("{i}"); + if i > 100 { + break; + } + } + } + +---------------- +"loop" Details +---------------- + +- The :rust:`loop` statement works like a :rust:`while true` loop. Use it for + things like servers which will serve connections forever. 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 18fe5696b..adebca184 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 @@ -1,78 +1,78 @@ -============================ -"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 :rust:`loop`, this can take an optional expression that becomes the -value of the :rust:`loop` expression. - -.. code:: rust - - fn main() { - let mut i = 0; - loop { - i += 1; - if i > 5 { - break; - } - if i % 2 == 0 { - continue; - } - println!("{}", i); - } - } - ---------- -Details ---------- - -Note that :rust:`loop` is the only looping construct which can return a -non-trivial value. This is because it's guaranteed to only return at a -:rust:`break` statement (unlike :rust:`while` and :rust:`for` loops, which can also -return when the condition fails). - --------- -Labels --------- - -Both :rust:`continue` and :rust:`break` can optionally take a label argument -which is used to break out of nested loops: - -.. code:: rust - - fn main() { - let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]]; - let mut elements_searched = 0; - let target_value = 10; - 'outer: for i in 0..=2 { - for j in 0..=2 { - elements_searched += 1; - if s[i][j] == target_value { - break 'outer; - } - } - } - print!("elements searched: {elements_searched}"); - } - ---------- -Details ---------- - -- Labeled break also works on arbitrary blocks, e.g. - - .. code:: rust - - 'label: { - break 'label; - println!("This line gets skipped"); - } - +============================ +"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 :rust:`loop`, this can take an optional expression that becomes the +value of the :rust:`loop` expression. + +.. code:: rust + + fn main() { + let mut i = 0; + loop { + i += 1; + if i > 5 { + break; + } + if i % 2 == 0 { + continue; + } + println!("{}", i); + } + } + +--------- +Details +--------- + +Note that :rust:`loop` is the only looping construct which can return a +non-trivial value. This is because it's guaranteed to only return at a +:rust:`break` statement (unlike :rust:`while` and :rust:`for` loops, which can also +return when the condition fails). + +-------- +Labels +-------- + +Both :rust:`continue` and :rust:`break` can optionally take a label argument +which is used to break out of nested loops: + +.. code:: rust + + fn main() { + let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]]; + let mut elements_searched = 0; + let target_value = 10; + 'outer: for i in 0..=2 { + for j in 0..=2 { + elements_searched += 1; + if s[i][j] == target_value { + break 'outer; + } + } + } + print!("elements searched: {elements_searched}"); + } + +--------- +Details +--------- + +- Labeled break also works on arbitrary blocks, e.g. + + .. code:: rust + + 'label: { + break 'label; + println!("This line gets skipped"); + } + 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 index ca55f7e3e..96923496d 100644 --- 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 @@ -1,34 +1,34 @@ -=================== -Blocks and Scopes -=================== - --------- -Blocks --------- - -A block in Rust contains a sequence of expressions, enclosed by braces -:rust:`{}`. Each block has a value and a type, which are those of the last -expression of the block: - -.. code:: rust - - fn main() { - let z = 13; - let x = { - let y = 10; - println!("y: {y}"); - z - y - }; - println!("x: {x}"); - } - -If the last expression ends with :rust:`;`, then the resulting value and -type is :rust:`()`. - ---------- -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 :rust:`return`. +=================== +Blocks and Scopes +=================== + +-------- +Blocks +-------- + +A block in Rust contains a sequence of expressions, enclosed by braces +:rust:`{}`. Each block has a value and a type, which are those of the last +expression of the block: + +.. code:: rust + + fn main() { + let z = 13; + let x = { + let y = 10; + println!("y: {y}"); + z - y + }; + println!("x: {x}"); + } + +If the last expression ends with :rust:`;`, then the resulting value and +type is :rust:`()`. + +--------- +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 :rust:`return`. 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 4a189ae45..979f3b4b3 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 @@ -1,42 +1,42 @@ -=========== -Functions -=========== - ------------ -Functions ------------ - -.. code:: rust - - fn gcd(a: u32, b: u32) -> u32 { - if b > 0 { - gcd(b, a % b) - } else { - a - } - } - - fn main() { - println!("gcd: {}", gcd(143, 52)); - } - ---------- -Details ---------- - -- Declaration parameters are followed by a type (the reverse of some - programming languages), then a return type. -- The last expression in a function body (or any block) becomes the - return value. Simply omit the :rust:`;` at the end of the expression. The - :rust:`return` keyword can be used for early return, but the "bare value" - form is idiomatic at the end of a function (refactor :rust:`gcd` to use a - :rust:`return`). -- Some functions have no return value, and return the 'unit type', - :rust:`()`. The compiler will infer this if the return type is omitted. -- Overloading is not supported - each function has a single - implementation. - - - Always takes a fixed number of parameters. Default arguments are - not supported. Macros can be used to support variadic functions. - - Always takes a single set of parameter types. These types can be - generic, which will be covered later. +=========== +Functions +=========== + +----------- +Functions +----------- + +.. code:: rust + + fn gcd(a: u32, b: u32) -> u32 { + if b > 0 { + gcd(b, a % b) + } else { + a + } + } + + fn main() { + println!("gcd: {}", gcd(143, 52)); + } + +--------- +Details +--------- + +- Declaration parameters are followed by a type (the reverse of some + programming languages), then a return type. +- The last expression in a function body (or any block) becomes the + return value. Simply omit the :rust:`;` at the end of the expression. The + :rust:`return` keyword can be used for early return, but the "bare value" + form is idiomatic at the end of a function (refactor :rust:`gcd` to use a + :rust:`return`). +- Some functions have no return value, and return the 'unit type', + :rust:`()`. The compiler will infer this if the return type is omitted. +- Overloading is not supported - each function has a single + implementation. + + - Always takes a fixed number of parameters. Default arguments are + not supported. Macros can be used to support variadic functions. + - Always takes a single set of parameter types. These types can be + generic, which will be covered later. 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 9b5381baa..7cb3bf031 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 @@ -1,52 +1,52 @@ -======== -Macros -======== - --------- -Macros --------- - -Macros are expanded into Rust code during compilation, and can take a -variable number of arguments. They are distinguished by a :rust:`!` at the -end. The Rust standard library includes an assortment of useful macros. - -- :rust:`println!(format, ..)` prints a line to standard output, applying - formatting described in - `std::fmt `__. -- :rust:`format!(format, ..)` works just like :rust:`println!` but returns the - result as a string. -- :rust:`dbg!(expression)` logs the value of the expression and returns it. -- :rust:`todo!()` marks a bit of code as not-yet-implemented. If executed, - it will panic. -- :rust:`unreachable!()` marks a bit of code as unreachable. If executed, - it will panic. - -.. code:: rust - - 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)); - } - ---------- -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. +======== +Macros +======== + +-------- +Macros +-------- + +Macros are expanded into Rust code during compilation, and can take a +variable number of arguments. They are distinguished by a :rust:`!` at the +end. The Rust standard library includes an assortment of useful macros. + +- :rust:`println!(format, ..)` prints a line to standard output, applying + formatting described in + `std::fmt `__. +- :rust:`format!(format, ..)` works just like :rust:`println!` but returns the + result as a string. +- :rust:`dbg!(expression)` logs the value of the expression and returns it. +- :rust:`todo!()` marks a bit of code as not-yet-implemented. If executed, + it will panic. +- :rust:`unreachable!()` marks a bit of code as unreachable. If executed, + it will panic. + +.. code:: rust + + 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)); + } + +--------- +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. 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 fb176fe3f..9226ee961 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 @@ -1,39 +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 :rust:`n`. - -:: - - {{#include exercise.rs:collatz_length}} - todo!("Implement this") - } - - {{#include exercise.rs:tests}} - - {{#include exercise.rs:main}} +============================ +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 :rust:`n`. + +:: + + {{#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 index 1d337ef40..526591d33 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays.rst @@ -1,43 +1,43 @@ -******************* -Tuples And Arrays -******************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +******************* +Tuples And Arrays +******************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 37d310929..8103f0766 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst @@ -1,40 +1,40 @@ -======== -Arrays -======== - --------- -Arrays --------- - -.. code:: rust - - fn main() { - let mut a: [i8; 10] = [42; 10]; - a[5] = 0; - println!("a: {a:?}"); - } - ---------- -Details ---------- - -- A value of the array type :rust:`[T; N]` holds :rust:`N` (a compile-time - constant) elements of the same type :rust:`T`. Note that the length of - the array is *part of its type*, which means that :rust:`[u8; 3]` and - :rust:`[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 :rust:`println!` macro asks for the debug implementation with the - :rust:`?` format parameter: :rust:`{}` gives the default output, :rust:`{:?}` - 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 :rust:`#`, eg :rust:`{a:#?}`, invokes a "pretty printing" format, - which can be easier to read. +======== +Arrays +======== + +-------- +Arrays +-------- + +.. code:: rust + + fn main() { + let mut a: [i8; 10] = [42; 10]; + a[5] = 0; + println!("a: {a:?}"); + } + +--------- +Details +--------- + +- A value of the array type :rust:`[T; N]` holds :rust:`N` (a compile-time + constant) elements of the same type :rust:`T`. Note that the length of + the array is *part of its type*, which means that :rust:`[u8; 3]` and + :rust:`[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 :rust:`println!` macro asks for the debug implementation with the + :rust:`?` format parameter: :rust:`{}` gives the default output, :rust:`{:?}` + 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 :rust:`#`, eg :rust:`{a:#?}`, invokes a "pretty printing" format, + which can be easier to read. 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 index 652eecb60..7fcb167fb 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst @@ -1,30 +1,30 @@ -======== -Tuples -======== - --------- -Tuples --------- - -.. code:: rust - - fn main() { - let t: (i8, bool) = (7, true); - println!("t.0: {}", t.0); - println!("t.1: {}", t.1); - } - ---------- -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. :rust:`t.0`, :rust:`t.1`. - -- The empty tuple :rust:`()` is referred to as the "unit type" and - signifies absence of a return value, akin to :rust:`void` in other - languages. +======== +Tuples +======== + +-------- +Tuples +-------- + +.. code:: rust + + fn main() { + let t: (i8, bool) = (7, true); + println!("t.0: {}", t.0); + println!("t.1: {}", t.1); + } + +--------- +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. :rust:`t.0`, :rust:`t.1`. + +- The empty tuple :rust:`()` is referred to as the "unit type" and + signifies absence of a return value, akin to :rust:`void` in other + languages. 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 index 589b76e7f..740ba9c71 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst @@ -1,31 +1,31 @@ -================= -Array Iteration -================= - ------------------ -Array Iteration ------------------ - -The :rust:`for` statement supports iterating over arrays (but not tuples). - -.. code:: rust - - fn main() { - let primes = [2, 3, 5, 7, 11, 13, 17, 19]; - for prime in primes { - for i in 2..prime { - assert_ne!(prime % i, 0); - } - } - } - ---------- -Details ---------- - -This functionality uses the :rust:`IntoIterator` trait, but we haven't -covered that yet. - -The :rust:`assert_ne!` macro is new here. There are also :rust:`assert_eq!` and -:rust:`assert!` macros. These are always checked, while debug-only variants -like :rust:`debug_assert!` compile to nothing in release builds. +================= +Array Iteration +================= + +----------------- +Array Iteration +----------------- + +The :rust:`for` statement supports iterating over arrays (but not tuples). + +.. code:: rust + + fn main() { + let primes = [2, 3, 5, 7, 11, 13, 17, 19]; + for prime in primes { + for i in 2..prime { + assert_ne!(prime % i, 0); + } + } + } + +--------- +Details +--------- + +This functionality uses the :rust:`IntoIterator` trait, but we haven't +covered that yet. + +The :rust:`assert_ne!` macro is new here. There are also :rust:`assert_eq!` and +:rust:`assert!` macros. These are always checked, while debug-only variants +like :rust:`debug_assert!` compile to nothing in release builds. 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 index 8b1132c42..21ff863f5 100644 --- a/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst @@ -1,45 +1,45 @@ -============================ -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 - - 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 - - fn print_tuple(tuple: (i32, i32)) { - let (left, right) = tuple; - println!("left: {left}, right: {right}"); - } - ---------- -Details ---------- - -- The patterns used here are "irrefutable", meaning that the compiler - can statically verify that the value on the right of :rust:`=` 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 :rust:`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. +============================ +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 + + 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 + + fn print_tuple(tuple: (i32, i32)) { + let (left, right) = tuple; + println!("left: {left}, right: {right}"); + } + +--------- +Details +--------- + +- The patterns used here are "irrefutable", meaning that the compiler + can statically verify that the value on the right of :rust:`=` 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 :rust:`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. 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 a456dcf07..4208e9a4f 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 @@ -1,54 +1,54 @@ -========================= -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 :rust:`transpose` which -will transpose a matrix (turn rows into columns): - -Transpose - -.. math:: - - \begin{bmatrix} - 1 & 2 & 3 \\ - 4 & 5 & 6 \\ - 7 & 8 & 9 - \end{bmatrix} - -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. - -:: - - // 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}} +========================= +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 :rust:`transpose` which +will transpose a matrix (turn rows into columns): + +Transpose + +.. math:: + + \begin{bmatrix} + 1 & 2 & 3 \\ + 4 & 5 & 6 \\ + 7 & 8 & 9 + \end{bmatrix} + +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. + +:: + + // 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 index 515bf14ae..cf81e3e22 100644 --- a/courses/comprehensive_rust_training/060_references.rst +++ b/courses/comprehensive_rust_training/060_references.rst @@ -1,44 +1,44 @@ -************ -References -************ - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************ +References +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 95aa18859..fca3aa92e 100644 --- a/courses/comprehensive_rust_training/060_references/01_shared.rst +++ b/courses/comprehensive_rust_training/060_references/01_shared.rst @@ -1,66 +1,66 @@ -=================== -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. - -.. code:: rust - - 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 :rust:`T` has type :rust:`&T`. A reference value is -made with the :rust:`&` operator. The :rust:`*` operator "dereferences" a -reference, yielding its value. - ---------- -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 :rust:`&` is - always required. - -- Rust will auto-dereference in some cases, in particular when invoking - methods (try :rust:`r.is_ascii()`). There is no need for an :rust:`->` - operator like in C++. - -- In this example, :rust:`r` is mutable so that it can be reassigned - (:rust:`r = &b`). Note that this re-binds :rust:`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 :rust:`*r = 'X'`. - -- Rust is tracking the lifetimes of all references to ensure they live - long enough. Dangling references cannot occur in safe Rust. - :rust:`x_axis` would return a reference to :rust:`point`, but :rust:`point` will - be deallocated when the function returns, so this will not compile. - -- We will talk more about borrowing when we get to ownership. +=================== +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. + +.. code:: rust + + 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 :rust:`T` has type :rust:`&T`. A reference value is +made with the :rust:`&` operator. The :rust:`*` operator "dereferences" a +reference, yielding its value. + +--------- +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 :rust:`&` is + always required. + +- Rust will auto-dereference in some cases, in particular when invoking + methods (try :rust:`r.is_ascii()`). There is no need for an :rust:`->` + operator like in C++. + +- In this example, :rust:`r` is mutable so that it can be reassigned + (:rust:`r = &b`). Note that this re-binds :rust:`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 :rust:`*r = 'X'`. + +- Rust is tracking the lifetimes of all references to ensure they live + long enough. Dangling references cannot occur in safe Rust. + :rust:`x_axis` would return a reference to :rust:`point`, but :rust:`point` will + be deallocated when the function returns, so this will not compile. + +- We will talk more about borrowing when we get to ownership. diff --git a/courses/comprehensive_rust_training/060_references/02_exclusive.rst b/courses/comprehensive_rust_training/060_references/02_exclusive.rst index e54fa7fac..a8b8c6b50 100644 --- a/courses/comprehensive_rust_training/060_references/02_exclusive.rst +++ b/courses/comprehensive_rust_training/060_references/02_exclusive.rst @@ -1,36 +1,36 @@ -====================== -Exclusive References -====================== - ----------------------- -Exclusive References ----------------------- - -Exclusive references, also known as mutable references, allow changing -the value they refer to. They have type :rust:`&mut T`. - -.. code:: rust - - fn main() { - let mut point = (1, 2); - let x_coord = &mut point.0; - *x_coord = 20; - println!("point: {point:?}"); - } - ---------- -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 :rust:`&point.0` or changing - :rust:`point.0` while :rust:`x_coord` is alive. - -- Be sure to note the difference between :rust:`let mut x_coord: &i32` and - :rust:`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. +====================== +Exclusive References +====================== + +---------------------- +Exclusive References +---------------------- + +Exclusive references, also known as mutable references, allow changing +the value they refer to. They have type :rust:`&mut T`. + +.. code:: rust + + fn main() { + let mut point = (1, 2); + let x_coord = &mut point.0; + *x_coord = 20; + println!("point: {point:?}"); + } + +--------- +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 :rust:`&point.0` or changing + :rust:`point.0` while :rust:`x_coord` is alive. + +- Be sure to note the difference between :rust:`let mut x_coord: &i32` and + :rust:`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. diff --git a/courses/comprehensive_rust_training/060_references/03_slices.rst b/courses/comprehensive_rust_training/060_references/03_slices.rst index 1f7e74565..62fe9a5e5 100644 --- a/courses/comprehensive_rust_training/060_references/03_slices.rst +++ b/courses/comprehensive_rust_training/060_references/03_slices.rst @@ -1,46 +1,46 @@ -======== -Slices -======== - --------- -Slices --------- - -A slice gives you a view into a larger collection: - -.. code:: rust - - 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. - ---------- -Details ---------- - -- We create a slice by borrowing :rust:`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 :rust:`&a[0..a.len()]` and - :rust:`&a[..a.len()]` are identical. - -- The same is true for the last index, so :rust:`&a[2..a.len()]` and - :rust:`&a[2..]` are identical. - -- To easily create a slice of the full array, we can therefore use - :rust:`&a[..]`. - -- :rust:`s` is a reference to a slice of :rust:`i32`. Notice that the type - of :rust:`s` (:rust:`&[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, :rust:`a` has - to remain 'alive' (in scope) for at least as long as our slice. +======== +Slices +======== + +-------- +Slices +-------- + +A slice gives you a view into a larger collection: + +.. code:: rust + + 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. + +--------- +Details +--------- + +- We create a slice by borrowing :rust:`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 :rust:`&a[0..a.len()]` and + :rust:`&a[..a.len()]` are identical. + +- The same is true for the last index, so :rust:`&a[2..a.len()]` and + :rust:`&a[2..]` are identical. + +- To easily create a slice of the full array, we can therefore use + :rust:`&a[..]`. + +- :rust:`s` is a reference to a slice of :rust:`i32`. Notice that the type + of :rust:`s` (:rust:`&[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, :rust:`a` has + to remain 'alive' (in scope) for at least as long as our slice. diff --git a/courses/comprehensive_rust_training/060_references/04_strings.rst b/courses/comprehensive_rust_training/060_references/04_strings.rst index 3ef3a65d2..fd7e446ff 100644 --- a/courses/comprehensive_rust_training/060_references/04_strings.rst +++ b/courses/comprehensive_rust_training/060_references/04_strings.rst @@ -1,83 +1,83 @@ -========= -Strings -========= - ---------- -Strings ---------- - -We can now understand the two string types in Rust: - -- :rust:`&str` is a slice of UTF-8 encoded bytes, similar to :rust:`&[u8]`. -- :rust:`String` is an owned buffer of UTF-8 encoded bytes, similar to - :rust:`Vec`. - - - -.. code:: rust - - 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}"); - } - ---------- -Details ---------- - -- :rust:`&str` introduces a string slice, which is an immutable reference - to UTF-8 encoded string data stored in a block of memory. String - literals (:rust:`"Hello"`), are stored in the program's binary. - -- Rust's :rust:`String` type is a wrapper around a vector of bytes. As with - a :rust:`Vec`, it is owned. - -- As with many other types :rust:`String::from()` creates a string from a - string literal; :rust:`String::new()` creates a new empty string, to - which string data can be added using the :rust:`push()` and - :rust:`push_str()` methods. - -- The :rust:`format!()` macro is a convenient way to generate an owned - string from dynamic values. It accepts the same format specification - as :rust:`println!()`. - -- You can borrow :rust:`&str` slices from :rust:`String` via :rust:`&` and - optionally range selection. If you select a byte range that is not - aligned to character boundaries, the expression will panic. The - :rust:`chars` iterator iterates over characters and is preferred over - trying to get character boundaries right. - -- For C++ programmers: think of :rust:`&str` as :rust:`std::string_view` from - C++, but the one that always points to a valid string in memory. Rust - :rust:`String` is a rough equivalent of :rust:`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 :rust:`&[u8]` value directly: - - .. code:: rust - - fn main() { - println!("{:?}", b"abc"); - println!("{:?}", &[97, 98, 99]); - } - -- Raw strings allow you to create a :rust:`&str` value with escapes - disabled: :rust:`r"\n" == "\\n"`. You can embed double-quotes by using an - equal amount of :rust:`#` on either side of the quotes: - - .. code:: rust - - fn main() { - println!(r#"link"#); - println!("link"); - } +========= +Strings +========= + +--------- +Strings +--------- + +We can now understand the two string types in Rust: + +- :rust:`&str` is a slice of UTF-8 encoded bytes, similar to :rust:`&[u8]`. +- :rust:`String` is an owned buffer of UTF-8 encoded bytes, similar to + :rust:`Vec`. + + + +.. code:: rust + + 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}"); + } + +--------- +Details +--------- + +- :rust:`&str` introduces a string slice, which is an immutable reference + to UTF-8 encoded string data stored in a block of memory. String + literals (:rust:`"Hello"`), are stored in the program's binary. + +- Rust's :rust:`String` type is a wrapper around a vector of bytes. As with + a :rust:`Vec`, it is owned. + +- As with many other types :rust:`String::from()` creates a string from a + string literal; :rust:`String::new()` creates a new empty string, to + which string data can be added using the :rust:`push()` and + :rust:`push_str()` methods. + +- The :rust:`format!()` macro is a convenient way to generate an owned + string from dynamic values. It accepts the same format specification + as :rust:`println!()`. + +- You can borrow :rust:`&str` slices from :rust:`String` via :rust:`&` and + optionally range selection. If you select a byte range that is not + aligned to character boundaries, the expression will panic. The + :rust:`chars` iterator iterates over characters and is preferred over + trying to get character boundaries right. + +- For C++ programmers: think of :rust:`&str` as :rust:`std::string_view` from + C++, but the one that always points to a valid string in memory. Rust + :rust:`String` is a rough equivalent of :rust:`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 :rust:`&[u8]` value directly: + + .. code:: rust + + fn main() { + println!("{:?}", b"abc"); + println!("{:?}", &[97, 98, 99]); + } + +- Raw strings allow you to create a :rust:`&str` value with escapes + disabled: :rust:`r"\n" == "\\n"`. You can embed double-quotes by using an + equal amount of :rust:`#` on either side of the quotes: + + .. code:: rust + + fn main() { + println!(r#"link"#); + println!("link"); + } diff --git a/courses/comprehensive_rust_training/060_references/05_dangling.rst b/courses/comprehensive_rust_training/060_references/05_dangling.rst index b052f9aab..de790861f 100644 --- a/courses/comprehensive_rust_training/060_references/05_dangling.rst +++ b/courses/comprehensive_rust_training/060_references/05_dangling.rst @@ -1,42 +1,42 @@ -==================== -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 :rust:`null`, making -them safe to use without :rust:`null` checks. The other rule we'll look at -for now is that references can't *outlive* the data they point to. - -.. code:: rust - - fn main() { - let x_ref = { - let x = 10; - &x - }; - println!("x: {x_ref}"); - } - ---------- -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 :rust:`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. +==================== +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 :rust:`null`, making +them safe to use without :rust:`null` checks. The other rule we'll look at +for now is that references can't *outlive* the data they point to. + +.. code:: rust + + fn main() { + let x_ref = { + let x = 10; + &x + }; + println!("x: {x_ref}"); + } + +--------- +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 :rust:`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. diff --git a/courses/comprehensive_rust_training/060_references/06_exercise.rst b/courses/comprehensive_rust_training/060_references/06_exercise.rst index 8dc796e15..2e5a800c5 100644 --- a/courses/comprehensive_rust_training/060_references/06_exercise.rst +++ b/courses/comprehensive_rust_training/060_references/06_exercise.rst @@ -1,34 +1,34 @@ -==================== -Exercise: Geometry -==================== - --------------------- -Exercise: Geometry --------------------- - -We will create a few utility functions for 3-dimensional geometry, -representing a point as :rust:`[f64;3]`. It is up to you to determine the -function signatures. - -:: - - // 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}} +==================== +Exercise: Geometry +==================== + +-------------------- +Exercise: Geometry +-------------------- + +We will create a few utility functions for 3-dimensional geometry, +representing a point as :rust:`[f64;3]`. It is up to you to determine the +function signatures. + +:: + + // 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 index 83aad69ac..2823130b1 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types.rst @@ -1,45 +1,45 @@ -******************** -User Defined Types -******************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +******************** +User Defined Types +******************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index c4011c8e7..2d0a1fe06 100644 --- 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 @@ -1,63 +1,63 @@ -=============== -Named Structs -=============== - ---------------- -Named Structs ---------------- - -Like C and C++, Rust has support for custom structs: - -.. code:: rust - - 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); - } - ---------- -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. :rust:`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 :rust:`..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. +=============== +Named Structs +=============== + +--------------- +Named Structs +--------------- + +Like C and C++, Rust has support for custom structs: + +.. code:: rust + + 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); + } + +--------- +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. :rust:`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 :rust:`..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. 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 a168ab387..e0722fa1a 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 @@ -1,62 +1,62 @@ -=============== -Tuple Structs -=============== - ---------------- -Tuple Structs ---------------- - -If the field names are unimportant, you can use a tuple struct: - -.. code:: rust - - 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 - - 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); - } - ---------- -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: :rust:`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: - :rust:`PhoneNumber(String)` or :rust:`OddNumber(u32)`. - -- Demonstrate how to add a :rust:`f64` value to a :rust:`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. +=============== +Tuple Structs +=============== + +--------------- +Tuple Structs +--------------- + +If the field names are unimportant, you can use a tuple struct: + +.. code:: rust + + 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 + + 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); + } + +--------- +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: :rust:`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: + :rust:`PhoneNumber(String)` or :rust:`OddNumber(u32)`. + +- Demonstrate how to add a :rust:`f64` value to a :rust:`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. 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 fec50750c..073166f40 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 @@ -1,129 +1,129 @@ -======= -Enums -======= - -------- -Enums -------- - -The :rust:`enum` keyword allows the creation of a type which has a few -different variants: - -.. code:: rust - - #[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:?}"); - } - ---------- -Details ---------- - -Key Points: - -- Enumerations allow you to collect a set of values under one type. -- :rust:`Direction` is a type with variants. There are two values of - :rust:`Direction`: :rust:`Direction::Left` and :rust:`Direction::Right`. -- :rust:`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 :rust:`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, :rust:`Option<&u8>` stores either - a pointer to an integer or :rust:`NULL` for the :rust:`None` variant. - - - You can control the discriminant if needed (e.g., for - compatibility with C): - - .. code:: rust - - #[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 :rust:`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 :rust:`size_of::()` equals :rust:`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. - - .. code:: rust - - 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); - } - } +======= +Enums +======= + +------- +Enums +------- + +The :rust:`enum` keyword allows the creation of a type which has a few +different variants: + +.. code:: rust + + #[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:?}"); + } + +--------- +Details +--------- + +Key Points: + +- Enumerations allow you to collect a set of values under one type. +- :rust:`Direction` is a type with variants. There are two values of + :rust:`Direction`: :rust:`Direction::Left` and :rust:`Direction::Right`. +- :rust:`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 :rust:`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, :rust:`Option<&u8>` stores either + a pointer to an integer or :rust:`NULL` for the :rust:`None` variant. + + - You can control the discriminant if needed (e.g., for + compatibility with C): + + .. code:: rust + + #[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 :rust:`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 :rust:`size_of::()` equals :rust:`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. + + .. code:: rust + + 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); + } + } 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 index b194dad8a..c479b7821 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst @@ -1,34 +1,34 @@ -============== -Type Aliases -============== - --------------- -Type Aliases --------------- - -A type alias creates a name for another type. The two types can be used -interchangeably. - -.. code:: rust - - 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>>>; - ---------- -Details ---------- - -- A `newtype `__ is often a better alternative - since it creates a distinct type. Prefer - :rust:`struct InventoryCount(usize)` to :rust:`type InventoryCount = usize`. - -- C programmers will recognize this as similar to a :rust:`typedef`. +============== +Type Aliases +============== + +-------------- +Type Aliases +-------------- + +A type alias creates a name for another type. The two types can be used +interchangeably. + +.. code:: rust + + 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>>>; + +--------- +Details +--------- + +- A `newtype `__ is often a better alternative + since it creates a distinct type. Prefer + :rust:`struct InventoryCount(usize)` to :rust:`type InventoryCount = usize`. + +- C programmers will recognize this as similar to a :rust:`typedef`. 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 b34847954..6b516a0a2 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 @@ -1,51 +1,51 @@ -=========== -"const" -=========== - ------------ -"const" ------------ - -Constants are evaluated at compile time and their values are inlined -wherever they are used: - -.. code:: rust - - 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 :rust:`const` can be called at compile time to -generate :rust:`const` values. :rust:`const` functions can however be called at -runtime. - ---------- -Details ---------- - -- Mention that :rust:`const` behaves semantically similar to C++'s - :rust:`constexpr` +=========== +"const" +=========== + +----------- +"const" +----------- + +Constants are evaluated at compile time and their values are inlined +wherever they are used: + +.. code:: rust + + 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 :rust:`const` can be called at compile time to +generate :rust:`const` values. :rust:`const` functions can however be called at +runtime. + +--------- +Details +--------- + +- Mention that :rust:`const` behaves semantically similar to C++'s + :rust:`constexpr` 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 ea7f2efac..3c12488eb 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 @@ -1,49 +1,49 @@ -============ -"static" -============ - ------------- -"static" ------------- - -Static variables will live during the whole execution of the program, -and therefore will not move: - -.. code:: rust - - 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, -:rust:`const` is generally preferred. - ---------- -Details ---------- - -- :rust:`static` is similar to mutable global variables in C++. -- :rust:`static` provides object identity: an address in memory and state - as required by types with interior mutability such as :rust:`Mutex`. - ------------------ -More to Explore ------------------ - -Because :rust:`static` variables are accessible from any thread, they must -be :rust:`Sync`. Interior mutability is possible through a -`Mutex `__, -atomic or similar. - -It is common to use :rust:`OnceLock` in a static as a way to support -initialization on first use. :rust:`OnceCell` is not :rust:`Sync` and thus -cannot be used in this context. - -Thread-local data can be created with the macro :rust:`std::thread_local`. +============ +"static" +============ + +------------ +"static" +------------ + +Static variables will live during the whole execution of the program, +and therefore will not move: + +.. code:: rust + + 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, +:rust:`const` is generally preferred. + +--------- +Details +--------- + +- :rust:`static` is similar to mutable global variables in C++. +- :rust:`static` provides object identity: an address in memory and state + as required by types with interior mutability such as :rust:`Mutex`. + +----------------- +More to Explore +----------------- + +Because :rust:`static` variables are accessible from any thread, they must +be :rust:`Sync`. Interior mutability is possible through a +`Mutex `__, +atomic or similar. + +It is common to use :rust:`OnceLock` in a static as a way to support +initialization on first use. :rust:`OnceCell` is not :rust:`Sync` and thus +cannot be used in this context. + +Thread-local data can be created with the macro :rust:`std::thread_local`. 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 index 0f8759018..2dd9d48e3 100644 --- a/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst +++ b/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst @@ -1,46 +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 :rust:`#[derive(Debug)]` to allow the types to -be formatted with :rust:`{:?}`. - -This exercise only requires creating and populating data structures so -that :rust:`main` runs without errors. The next part of the course will -cover getting data out of these structures. - -:: - - {{#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}} +=========================== +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 :rust:`#[derive(Debug)]` to allow the types to +be formatted with :rust:`{:?}`. + +This exercise only requires creating and populating data structures so +that :rust:`main` runs without errors. The next part of the course will +cover getting data out of these structures. + +:: + + {{#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 index f2268f09f..6104446e0 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching.rst @@ -1,43 +1,43 @@ -****************** -Pattern Matching -****************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +****************** +Pattern Matching +****************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 383c2ddbe..c146670be 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst @@ -1,81 +1,81 @@ -================= -Matching Values -================= - ------------------ -Matching Values ------------------ - -The :rust:`match` keyword lets you match a value against one or more -*patterns*. The patterns can be simple values, similarly to :rust:`switch` -in C and C++, but they can also be used to express more complex -conditions: - -.. code:: rust - - #[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 (:rust:`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. - ---------- -Details ---------- - -Key Points: - -- You might point out how some specific characters are being used when - in a pattern - - - :rust:`|` as an :rust:`or` - - :rust:`..` can expand as much as it needs to be - - :rust:`1..=5` represents an inclusive range - - :rust:`_` 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 :rust:`if` expression inside of the - match arm. An :rust:`if` expression inside of the branch block (after - :rust:`=>`) happens after the match arm is selected. Failing the :rust:`if` - condition inside of that block won't result in other arms of the - original :rust:`match` expression being considered. -- The condition defined in the guard applies to every expression in a - pattern with an :rust:`|`. - ------------------ -More To Explore ------------------ - -- Another piece of pattern syntax you can show students is the :rust:`@` - 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 :rust:`inner` has the value 123 which it pulled from the - :rust:`Option` via destructuring, :rust:`outer` captures the entire - :rust:`Some(inner)` expression, so it contains the full - :rust:`Option::Some(123)`. This is rarely used but can be useful in more - complex patterns. +================= +Matching Values +================= + +----------------- +Matching Values +----------------- + +The :rust:`match` keyword lets you match a value against one or more +*patterns*. The patterns can be simple values, similarly to :rust:`switch` +in C and C++, but they can also be used to express more complex +conditions: + +.. code:: rust + + #[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 (:rust:`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. + +--------- +Details +--------- + +Key Points: + +- You might point out how some specific characters are being used when + in a pattern + + - :rust:`|` as an :rust:`or` + - :rust:`..` can expand as much as it needs to be + - :rust:`1..=5` represents an inclusive range + - :rust:`_` 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 :rust:`if` expression inside of the + match arm. An :rust:`if` expression inside of the branch block (after + :rust:`=>`) happens after the match arm is selected. Failing the :rust:`if` + condition inside of that block won't result in other arms of the + original :rust:`match` expression being considered. +- The condition defined in the guard applies to every expression in a + pattern with an :rust:`|`. + +----------------- +More To Explore +----------------- + +- Another piece of pattern syntax you can show students is the :rust:`@` + 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 :rust:`inner` has the value 123 which it pulled from the + :rust:`Option` via destructuring, :rust:`outer` captures the entire + :rust:`Some(inner)` expression, so it contains the full + :rust:`Option::Some(123)`. This is rarely used but can be useful in more + complex patterns. 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 index 2708b9f76..7f6ba5a8a 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst @@ -1,25 +1,25 @@ -========= -Structs -========= - ---------- -Structs ---------- - -Like tuples, Struct can also be destructured by matching: - -.. code:: rust - - {{#include ../../third_party/rust-by-example/destructuring-structs.rs}} - ---------- -Details ---------- - -- Change the literal values in :rust:`foo` to match with the other - patterns. -- Add a new field to :rust:`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 :rust:`2` in the second arm to a variable, - and see that it subtly doesn't work. Change it to a :rust:`const` and see - it working again. +========= +Structs +========= + +--------- +Structs +--------- + +Like tuples, Struct can also be destructured by matching: + +.. code:: rust + + {{#include ../../third_party/rust-by-example/destructuring-structs.rs}} + +--------- +Details +--------- + +- Change the literal values in :rust:`foo` to match with the other + patterns. +- Add a new field to :rust:`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 :rust:`2` in the second arm to a variable, + and see that it subtly doesn't work. Change it to a :rust:`const` and see + it working again. 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 index 9278b54ba..29d0ba1b9 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst @@ -1,56 +1,56 @@ -======= -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 :rust:`enum` type: - -.. code:: rust - - 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 :rust:`Result` value. In the -first arm, :rust:`half` is bound to the value inside the :rust:`Ok` variant. In -the second arm, :rust:`msg` is bound to the error message. - ---------- -Details ---------- - -- The :rust:`if`/:rust:`else` expression is returning an enum that is later - unpacked with a :rust:`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. +======= +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 :rust:`enum` type: + +.. code:: rust + + 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 :rust:`Result` value. In the +first arm, :rust:`half` is bound to the value inside the :rust:`Ok` variant. In +the second arm, :rust:`msg` is bound to the error message. + +--------- +Details +--------- + +- The :rust:`if`/:rust:`else` expression is returning an enum that is later + unpacked with a :rust:`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. 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 c95abc155..0158eed67 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 @@ -1,142 +1,142 @@ -================== -Let Control Flow -================== - ------------------- -Let Control Flow ------------------- - -Rust has a few control flow constructs which differ from other -languages. They are used for pattern matching: - -- :rust:`if let` expressions -- :rust:`let else` expressions -- :rust:`while let` expressions - ------------------------- -"if let" expressions ------------------------- - -The -`if let expression `__ -lets you execute different code depending on whether a value matches a -pattern: - -.. code:: rust - - 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 --------------------------- - -For the common case of matching a pattern and returning from the -function, use -`let else `__. -The "else" case must diverge (:rust:`return`, :rust:`break`, or panic - anything -but falling off the end of the block). - -.. code:: rust - - 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 :rust:`if let`, there is a -`while let `__ -variant which repeatedly tests a value against a pattern: - -.. code:: rust - - fn main() { - let mut name = String::from("Comprehensive Rust"); - while let Some(c) = name.pop() { - println!("character: {c}"); - } - // (There are more efficient ways to reverse a string!) - } - -Here -`String::pop `__ -returns :rust:`Some(c)` until the string is empty, after which it will -return :rust:`None`. The :rust:`while let` lets us keep iterating through all -items. - --------- -if-let --------- - -- Unlike :rust:`match`, :rust:`if let` does not have to cover all branches. - This can make it more concise than :rust:`match`. -- A common usage is handling :rust:`Some` values when working with - :rust:`Option`. -- Unlike :rust:`match`, :rust:`if let` does not support guard clauses for - pattern matching. - ----------- -let-else ----------- - -:rust:`if-let` can pile up, as shown. The :rust:`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 ------------ - -- Point out that the :rust:`while let` loop will keep going as long as the - value matches the pattern. -- You could rewrite the :rust:`while let` loop as an infinite loop with an - if statement that breaks when there is no value to unwrap for - :rust:`name.pop()`. The :rust:`while let` provides syntactic sugar for the - above scenario. +================== +Let Control Flow +================== + +------------------ +Let Control Flow +------------------ + +Rust has a few control flow constructs which differ from other +languages. They are used for pattern matching: + +- :rust:`if let` expressions +- :rust:`let else` expressions +- :rust:`while let` expressions + +------------------------ +"if let" expressions +------------------------ + +The +`if let expression `__ +lets you execute different code depending on whether a value matches a +pattern: + +.. code:: rust + + 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 +-------------------------- + +For the common case of matching a pattern and returning from the +function, use +`let else `__. +The "else" case must diverge (:rust:`return`, :rust:`break`, or panic - anything +but falling off the end of the block). + +.. code:: rust + + 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 :rust:`if let`, there is a +`while let `__ +variant which repeatedly tests a value against a pattern: + +.. code:: rust + + fn main() { + let mut name = String::from("Comprehensive Rust"); + while let Some(c) = name.pop() { + println!("character: {c}"); + } + // (There are more efficient ways to reverse a string!) + } + +Here +`String::pop `__ +returns :rust:`Some(c)` until the string is empty, after which it will +return :rust:`None`. The :rust:`while let` lets us keep iterating through all +items. + +-------- +if-let +-------- + +- Unlike :rust:`match`, :rust:`if let` does not have to cover all branches. + This can make it more concise than :rust:`match`. +- A common usage is handling :rust:`Some` values when working with + :rust:`Option`. +- Unlike :rust:`match`, :rust:`if let` does not support guard clauses for + pattern matching. + +---------- +let-else +---------- + +:rust:`if-let` can pile up, as shown. The :rust:`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 +----------- + +- Point out that the :rust:`while let` loop will keep going as long as the + value matches the pattern. +- You could rewrite the :rust:`while let` loop as an infinite loop with an + if statement that breaks when there is no value to unwrap for + :rust:`name.pop()`. The :rust:`while let` provides syntactic sugar for the + above scenario. diff --git a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst index c8ce0a4c0..536f6be95 100644 --- a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst +++ b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst @@ -1,80 +1,80 @@ -================================= -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 :rust:`10 + 20`, which -evaluates to :rust:`30`. We can represent the expression as a tree: - -.. code:: bob - - .-------. - .------ | + | ------. - | '-------' | - v v - .--------. .--------. - | 10 | | 20 | - '--------' '--------' - -A bigger and more complex expression would be -:rust:`(10 * 9) + ((3 - 4) * 5)`, which evaluate to :rust:`85`. We represent -this as a much bigger tree: - -.. 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: - -:: - - {{#include exercise.rs:Operation}} - - {{#include exercise.rs:Expression}} - -The :rust:`Box` type here is a smart pointer, and will be covered in detail -later in the course. An expression can be "boxed" with :rust:`Box::new` as -seen in the tests. To evaluate a boxed expression, use the deref -operator (:rust:`*`) to "unbox" it: :rust:`eval(*boxed_expr)`. - -Copy and paste the code into the Rust playground, and begin implementing -:rust:`eval`. The final product should pass the tests. It may be helpful to -use :rust:`todo!()` and get the tests to pass one-by-one. You can also skip -a test temporarily with :rust:`#[ignore]`: - -.. code:: none - - #[test] - #[ignore] - fn test_value() { .. } - -:: - - {{#include exercise.rs:Operation}} - - {{#include exercise.rs:Expression}} - - {{#include exercise.rs:eval}} - todo!() - } - - {{#include exercise.rs:tests}} +================================= +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 :rust:`10 + 20`, which +evaluates to :rust:`30`. We can represent the expression as a tree: + +.. code:: bob + + .-------. + .------ | + | ------. + | '-------' | + v v + .--------. .--------. + | 10 | | 20 | + '--------' '--------' + +A bigger and more complex expression would be +:rust:`(10 * 9) + ((3 - 4) * 5)`, which evaluate to :rust:`85`. We represent +this as a much bigger tree: + +.. 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: + +:: + + {{#include exercise.rs:Operation}} + + {{#include exercise.rs:Expression}} + +The :rust:`Box` type here is a smart pointer, and will be covered in detail +later in the course. An expression can be "boxed" with :rust:`Box::new` as +seen in the tests. To evaluate a boxed expression, use the deref +operator (:rust:`*`) to "unbox" it: :rust:`eval(*boxed_expr)`. + +Copy and paste the code into the Rust playground, and begin implementing +:rust:`eval`. The final product should pass the tests. It may be helpful to +use :rust:`todo!()` and get the tests to pass one-by-one. You can also skip +a test temporarily with :rust:`#[ignore]`: + +.. code:: none + + #[test] + #[ignore] + fn test_value() { .. } + +:: + + {{#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 index 3c5496e3e..d2ae4c12f 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits.rst @@ -1,42 +1,42 @@ -******************** -Methods And Traits -******************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +******************** +Methods And Traits +******************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 05c2875da..d32de0718 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 @@ -1,101 +1,101 @@ -========= -Methods -========= - ---------- -Methods ---------- - -Rust allows you to associate functions with your new types. You do this -with an :rust:`impl` block: - -.. code:: rust - - #[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 :rust:`self` arguments specify the "receiver" - the object the method -acts on. There are several common receivers for a method: - -- :rust:`&self`: borrows the object from the caller using a shared and - immutable reference. The object can be used again afterwards. -- :rust:`&mut self`: borrows the object from the caller using a unique and - mutable reference. The object can be used again afterwards. -- :rust:`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. -- :rust:`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 :rust:`new` by convention. - ---------- -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 :rust:`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 :rust:`self`, a method receiver. - - - Show that it is an abbreviated term for :rust:`self: Self` and perhaps - show how the struct name could also be used. - - Explain that :rust:`Self` is a type alias for the type the :rust:`impl` - block is in and can be used elsewhere in the block. - - Note how :rust:`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 :rust:`&self` differs - from :rust:`self` by trying to run :rust:`finish` twice. - - Beyond variants on :rust:`self`, there are also - `special wrapper types `__ - allowed to be receiver types, such as :rust:`Box`. +========= +Methods +========= + +--------- +Methods +--------- + +Rust allows you to associate functions with your new types. You do this +with an :rust:`impl` block: + +.. code:: rust + + #[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 :rust:`self` arguments specify the "receiver" - the object the method +acts on. There are several common receivers for a method: + +- :rust:`&self`: borrows the object from the caller using a shared and + immutable reference. The object can be used again afterwards. +- :rust:`&mut self`: borrows the object from the caller using a unique and + mutable reference. The object can be used again afterwards. +- :rust:`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. +- :rust:`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 :rust:`new` by convention. + +--------- +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 :rust:`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 :rust:`self`, a method receiver. + + - Show that it is an abbreviated term for :rust:`self: Self` and perhaps + show how the struct name could also be used. + - Explain that :rust:`Self` is a type alias for the type the :rust:`impl` + block is in and can be used elsewhere in the block. + - Note how :rust:`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 :rust:`&self` differs + from :rust:`self` by trying to run :rust:`finish` twice. + - Beyond variants on :rust:`self`, there are also + `special wrapper types `__ + allowed to be receiver types, such as :rust:`Box`. 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 index adbd03e96..ac09b597e 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst @@ -1,161 +1,161 @@ -======== -Traits -======== - --------- -Traits --------- - -Rust lets you abstract over types with traits. They're similar to -interfaces: - -.. code:: rust - - trait Pet { - /// Return a sentence from this pet. - fn talk(&self) -> String; - - /// Print a string to the terminal greeting this pet. - fn greet(&self); - } - ----------------- -Traits 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. - ---------------------- -Implementing Traits ---------------------- - -.. code:: rust - - trait Pet { - fn talk(&self) -> String; - - fn greet(&self) { - println!("Oh you're a cutie! What's your name? {}", self.talk()); - } - } - - struct Dog { - name: String, - age: i8, - } - - impl Pet for Dog { - fn talk(&self) -> String { - format!("Woof, my name is {}!", self.name) - } - } - - fn main() { - let fido = Dog { name: String::from("Fido"), age: 5 }; - fido.greet(); - } - ----------------------- -Implementing Details ----------------------- - -- To implement :rust:`Trait` for :rust:`Type`, you use an - :rust:`impl Trait for Type { .. }` block. - -- Unlike Go interfaces, just having matching methods is not enough: a - :rust:`Cat` type with a :rust:`talk()` method would not automatically satisfy - :rust:`Pet` unless it is in an :rust:`impl Pet` block. - -- Traits may provide default implementations of some methods. Default - implementations can rely on all the methods of the trait. In this - case, :rust:`greet` is provided, and relies on :rust:`talk`. - -------------- -Supertraits -------------- - -A trait can require that types implementing it also implement other -traits, called *supertraits*. Here, any type implementing :rust:`Pet` must -implement :rust:`Animal`. - -.. code:: rust - - trait Animal { - fn leg_count(&self) -> u32; - } - - trait Pet: Animal { - fn name(&self) -> String; - } - - struct Dog(String); - - impl Animal for Dog { - fn leg_count(&self) -> u32 { - 4 - } - } - - impl Pet for Dog { - fn name(&self) -> String { - self.0.clone() - } - } - - fn main() { - let puppy = Dog(String::from("Rex")); - println!("{} has {} legs", puppy.name(), puppy.leg_count()); - } - ---------------------- -Supertraits Details ---------------------- - -This is sometimes called *trait inheritance* but students should not -expect this to behave like OO inheritance. It just specifies an -additional requirement on implementations of a trait. - ------------------- -Associated Types ------------------- - -Associated types are placeholder types which are supplied by the trait -implementation. - -.. code:: rust - - #[derive(Debug)] - struct Meters(i32); - #[derive(Debug)] - struct MetersSquared(i32); - - trait Multiply { - type Output; - fn multiply(&self, other: &Self) -> Self::Output; - } - - impl Multiply for Meters { - type Output = MetersSquared; - fn multiply(&self, other: &Self) -> Self::Output { - MetersSquared(self.0 * other.0) - } - } - - fn main() { - println!("{:?}", Meters(10).multiply(&Meters(20))); - } - ---------- -Details ---------- - -- Associated types are sometimes also called *output types*. The key - observation is that the implementer, not the caller, chooses this - type. - -- Many standard library traits have associated types, including - arithmetic operators and :rust:`Iterator`. +======== +Traits +======== + +-------- +Traits +-------- + +Rust lets you abstract over types with traits. They're similar to +interfaces: + +.. code:: rust + + trait Pet { + /// Return a sentence from this pet. + fn talk(&self) -> String; + + /// Print a string to the terminal greeting this pet. + fn greet(&self); + } + +---------------- +Traits 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. + +--------------------- +Implementing Traits +--------------------- + +.. code:: rust + + trait Pet { + fn talk(&self) -> String; + + fn greet(&self) { + println!("Oh you're a cutie! What's your name? {}", self.talk()); + } + } + + struct Dog { + name: String, + age: i8, + } + + impl Pet for Dog { + fn talk(&self) -> String { + format!("Woof, my name is {}!", self.name) + } + } + + fn main() { + let fido = Dog { name: String::from("Fido"), age: 5 }; + fido.greet(); + } + +---------------------- +Implementing Details +---------------------- + +- To implement :rust:`Trait` for :rust:`Type`, you use an + :rust:`impl Trait for Type { .. }` block. + +- Unlike Go interfaces, just having matching methods is not enough: a + :rust:`Cat` type with a :rust:`talk()` method would not automatically satisfy + :rust:`Pet` unless it is in an :rust:`impl Pet` block. + +- Traits may provide default implementations of some methods. Default + implementations can rely on all the methods of the trait. In this + case, :rust:`greet` is provided, and relies on :rust:`talk`. + +------------- +Supertraits +------------- + +A trait can require that types implementing it also implement other +traits, called *supertraits*. Here, any type implementing :rust:`Pet` must +implement :rust:`Animal`. + +.. code:: rust + + trait Animal { + fn leg_count(&self) -> u32; + } + + trait Pet: Animal { + fn name(&self) -> String; + } + + struct Dog(String); + + impl Animal for Dog { + fn leg_count(&self) -> u32 { + 4 + } + } + + impl Pet for Dog { + fn name(&self) -> String { + self.0.clone() + } + } + + fn main() { + let puppy = Dog(String::from("Rex")); + println!("{} has {} legs", puppy.name(), puppy.leg_count()); + } + +--------------------- +Supertraits Details +--------------------- + +This is sometimes called *trait inheritance* but students should not +expect this to behave like OO inheritance. It just specifies an +additional requirement on implementations of a trait. + +------------------ +Associated Types +------------------ + +Associated types are placeholder types which are supplied by the trait +implementation. + +.. code:: rust + + #[derive(Debug)] + struct Meters(i32); + #[derive(Debug)] + struct MetersSquared(i32); + + trait Multiply { + type Output; + fn multiply(&self, other: &Self) -> Self::Output; + } + + impl Multiply for Meters { + type Output = MetersSquared; + fn multiply(&self, other: &Self) -> Self::Output { + MetersSquared(self.0 * other.0) + } + } + + fn main() { + println!("{:?}", Meters(10).multiply(&Meters(20))); + } + +--------- +Details +--------- + +- Associated types are sometimes also called *output types*. The key + observation is that the implementer, not the caller, chooses this + type. + +- Many standard library traits have associated types, including + arithmetic operators and :rust:`Iterator`. 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 index d34dcbb57..10a5b472f 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst @@ -1,36 +1,36 @@ -========== -Deriving -========== - ----------- -Deriving ----------- - -Supported traits can be automatically implemented for your custom types, -as follows: - -.. code:: rust - - #[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:?}"); - } - ---------- -Details ---------- - -Derivation is implemented with macros, and many crates provide useful -derive macros to add useful functionality. For example, :rust:`serde` can -derive serialization support for a struct using -:rust:`#[derive(Serialize)]`. +========== +Deriving +========== + +---------- +Deriving +---------- + +Supported traits can be automatically implemented for your custom types, +as follows: + +.. code:: rust + + #[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:?}"); + } + +--------- +Details +--------- + +Derivation is implemented with macros, and many crates provide useful +derive macros to add useful functionality. For example, :rust:`serde` can +derive serialization support for a struct using +:rust:`#[derive(Serialize)]`. 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 index d9eeda122..41900af46 100644 --- a/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst +++ b/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst @@ -1,29 +1,29 @@ -======================== -Exercise: Logger Trait -======================== - ------------------------- -Exercise: Logger Trait ------------------------- - -Let's design a simple logging utility, using a trait :rust:`Logger` with a -:rust:`log` method. Code which might log its progress can then take an -:rust:`&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 :rust:`StderrLogger` given below logs all messages, regardless -of verbosity. Your task is to write a :rust:`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? - -:: - - {{#include exercise.rs:setup}} - - // TODO: Define and implement `VerbosityFilter`. - - {{#include exercise.rs:main}} +======================== +Exercise: Logger Trait +======================== + +------------------------ +Exercise: Logger Trait +------------------------ + +Let's design a simple logging utility, using a trait :rust:`Logger` with a +:rust:`log` method. Code which might log its progress can then take an +:rust:`&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 :rust:`StderrLogger` given below logs all messages, regardless +of verbosity. Your task is to write a :rust:`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? + +:: + + {{#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 index 2b6d6b838..a08b63e3f 100644 --- a/courses/comprehensive_rust_training/100_generics.rst +++ b/courses/comprehensive_rust_training/100_generics.rst @@ -1,45 +1,45 @@ -********** -Generics -********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +********** +Generics +********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index fa999d5dc..127ae8ad3 100644 --- a/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst +++ b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst @@ -1,57 +1,57 @@ -=================== -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 - - /// 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")); - } - ---------- -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 :rust:`i32` and :rust:`&str` - for :rust:`T`, but we can use any type here, including user-defined - types: - - .. code:: rust - - 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 :rust:`pick` - to return :rust:`even + odd` if :rust:`n == 0`. Even if only the :rust:`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. +=================== +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 + + /// 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")); + } + +--------- +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 :rust:`i32` and :rust:`&str` + for :rust:`T`, but we can use any type here, including user-defined + types: + + .. code:: rust + + 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 :rust:`pick` + to return :rust:`even + odd` if :rust:`n == 0`. Even if only the :rust:`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. diff --git a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst index fe6147c06..d34b387aa 100644 --- a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst +++ b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst @@ -1,54 +1,54 @@ -==================== -Generic Data Types -==================== - --------------------- -Generic Data Types --------------------- - -You can use generics to abstract over the concrete field type: - -.. code:: rust - - #[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()); - } - ---------- -Details ---------- - -- *Q:* Why :rust:`T` is specified twice in :rust:`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 :rust:`T`. - - It is possible to write :rust:`impl Point { .. }`. - - - :rust:`Point` is still generic and you can use :rust:`Point`, but - methods in this block will only be available for - :rust:`Point`. - -- Try declaring a new variable :rust:`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., :rust:`T` and :rust:`U`. +==================== +Generic Data Types +==================== + +-------------------- +Generic Data Types +-------------------- + +You can use generics to abstract over the concrete field type: + +.. code:: rust + + #[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()); + } + +--------- +Details +--------- + +- *Q:* Why :rust:`T` is specified twice in :rust:`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 :rust:`T`. + - It is possible to write :rust:`impl Point { .. }`. + + - :rust:`Point` is still generic and you can use :rust:`Point`, but + methods in this block will only be available for + :rust:`Point`. + +- Try declaring a new variable :rust:`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., :rust:`T` and :rust:`U`. 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 c7381fd1b..032d8fd9c 100644 --- a/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst +++ b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst @@ -1,55 +1,55 @@ -================ -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 - - #[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:?}"); - } - ---------- -Details ---------- - -- The :rust:`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, :rust:`Foo::from("hello")` would not compile because - there is no :rust:`From<&str>` implementation for :rust:`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 `__. +================ +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 + + #[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:?}"); + } + +--------- +Details +--------- + +- The :rust:`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, :rust:`Foo::from("hello")` would not compile because + there is no :rust:`From<&str>` implementation for :rust:`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 `__. diff --git a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst index 5b11e69f4..724dfe5f2 100644 --- a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst +++ b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst @@ -1,56 +1,56 @@ -============== -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 :rust:`T: Trait`: - -.. code:: rust - - 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:?}"); - } - ---------- -Details ---------- - -- Try making a :rust:`NonCloneable` and passing it to :rust:`duplicate`. - -- When multiple traits are necessary, use :rust:`+` to join them. - -- Show a :rust:`where` clause, students will encounter it when reading - code. - - .. code:: rust - - 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 :rust:`Option`. - -- Note that Rust does not (yet) support specialization. For example, - given the original :rust:`duplicate`, it is invalid to add a specialized - :rust:`duplicate(a: u32)`. +============== +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 :rust:`T: Trait`: + +.. code:: rust + + 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:?}"); + } + +--------- +Details +--------- + +- Try making a :rust:`NonCloneable` and passing it to :rust:`duplicate`. + +- When multiple traits are necessary, use :rust:`+` to join them. + +- Show a :rust:`where` clause, students will encounter it when reading + code. + + .. code:: rust + + 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 :rust:`Option`. + +- Note that Rust does not (yet) support specialization. For example, + given the original :rust:`duplicate`, it is invalid to add a specialized + :rust:`duplicate(a: u32)`. diff --git a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst index a0c547eae..ad19c6c54 100644 --- a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst @@ -1,57 +1,57 @@ -================ -"impl Trait" -================ - ----------------- -"impl Trait" ----------------- - -Similar to trait bounds, an :rust:`impl Trait` syntax can be used in -function arguments and return values: - -.. code:: rust - - // 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:?}"); - } - ---------- -Details ---------- - -:rust:`impl Trait` allows you to work with types which you cannot name. The -meaning of :rust:`impl Trait` is a bit different in the different positions. - -- For a parameter, :rust:`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 - :rust:`impl Foo` picks the concrete type it returns, without writing it - out in the source. A function returning a generic type like - :rust:`collect() -> B` can return any type satisfying :rust:`B`, and the - caller may need to choose one, such as with - :rust:`let x: Vec<_> = foo.collect()` or with the turbofish, - :rust:`foo.collect::>()`. - -What is the type of :rust:`debuggable`? Try :rust:`let debuggable: () = ..` to -see what the error message shows. +================ +"impl Trait" +================ + +---------------- +"impl Trait" +---------------- + +Similar to trait bounds, an :rust:`impl Trait` syntax can be used in +function arguments and return values: + +.. code:: rust + + // 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:?}"); + } + +--------- +Details +--------- + +:rust:`impl Trait` allows you to work with types which you cannot name. The +meaning of :rust:`impl Trait` is a bit different in the different positions. + +- For a parameter, :rust:`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 + :rust:`impl Foo` picks the concrete type it returns, without writing it + out in the source. A function returning a generic type like + :rust:`collect() -> B` can return any type satisfying :rust:`B`, and the + caller may need to choose one, such as with + :rust:`let x: Vec<_> = foo.collect()` or with the turbofish, + :rust:`foo.collect::>()`. + +What is the type of :rust:`debuggable`? Try :rust:`let debuggable: () = ..` to +see what the error message shows. 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 4964817b9..b12fe8402 100644 --- a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst +++ b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst @@ -1,90 +1,90 @@ -=============== -"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 - - 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); - } - ---------- -Details ---------- - -- Generics, including :rust:`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 :rust:`dyn Trait`, it instead uses dynamic dispatch through a - `virtual method table `__ - (vtable). This means that there's a single version of :rust:`fn dynamic` - that is used regardless of what type of :rust:`Pet` is passed in. - -- When using :rust:`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 :rust:`Box` can also be used (this will be - demonstrated on day 3). - -- At runtime, a :rust:`&dyn Pet` is represented as a "fat pointer", i.e. a - pair of two pointers: One pointer points to the concrete object that - implements :rust:`Pet`, and the other points to the vtable for the trait - implementation for that type. When calling the :rust:`talk` method on - :rust:`&dyn Pet` the compiler looks up the function pointer for :rust:`talk` - in the vtable and then invokes the function, passing the pointer to - the :rust:`Dog` or :rust:`Cat` into that function. The compiler doesn't need - to know the concrete type of the :rust:`Pet` in order to do this. - -- A :rust:`dyn Trait` is considered to be "type-erased", because we no - longer have compile-time knowledge of what the concrete type is. +=============== +"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 + + 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); + } + +--------- +Details +--------- + +- Generics, including :rust:`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 :rust:`dyn Trait`, it instead uses dynamic dispatch through a + `virtual method table `__ + (vtable). This means that there's a single version of :rust:`fn dynamic` + that is used regardless of what type of :rust:`Pet` is passed in. + +- When using :rust:`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 :rust:`Box` can also be used (this will be + demonstrated on day 3). + +- At runtime, a :rust:`&dyn Pet` is represented as a "fat pointer", i.e. a + pair of two pointers: One pointer points to the concrete object that + implements :rust:`Pet`, and the other points to the vtable for the trait + implementation for that type. When calling the :rust:`talk` method on + :rust:`&dyn Pet` the compiler looks up the function pointer for :rust:`talk` + in the vtable and then invokes the function, passing the pointer to + the :rust:`Dog` or :rust:`Cat` into that function. The compiler doesn't need + to know the concrete type of the :rust:`Pet` in order to do this. + +- A :rust:`dyn Trait` is considered to be "type-erased", because we no + longer have compile-time knowledge of what the concrete type is. diff --git a/courses/comprehensive_rust_training/100_generics/07_exercise.rst b/courses/comprehensive_rust_training/100_generics/07_exercise.rst index 37c889566..e0887ca0e 100644 --- a/courses/comprehensive_rust_training/100_generics/07_exercise.rst +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -1,30 +1,30 @@ -=========================== -Exercise: Generic "min" -=========================== - ---------------------------- -Exercise: Generic "min" ---------------------------- - -In this short exercise, you will implement a generic :rust:`min` function -that determines the minimum of two values, using the -`Ord `__ -trait. - -:: - - use std::cmp::Ordering; - - // TODO: implement the `min` function used in `main`. - - {{#include exercise.rs:main}} - ---------- -Details ---------- - -- Show students the - `Ord `__ - trait and - `Ordering `__ - enum. +=========================== +Exercise: Generic "min" +=========================== + +--------------------------- +Exercise: Generic "min" +--------------------------- + +In this short exercise, you will implement a generic :rust:`min` function +that determines the minimum of two values, using the +`Ord `__ +trait. + +:: + + use std::cmp::Ordering; + + // TODO: implement the `min` function used in `main`. + + {{#include exercise.rs:main}} + +--------- +Details +--------- + +- Show students the + `Ord `__ + trait and + `Ordering `__ + enum. diff --git a/courses/comprehensive_rust_training/110_std_types.rst b/courses/comprehensive_rust_training/110_std_types.rst index 71b29dae5..56e4b1186 100644 --- a/courses/comprehensive_rust_training/110_std_types.rst +++ b/courses/comprehensive_rust_training/110_std_types.rst @@ -1,46 +1,46 @@ -*********** -Std Types -*********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +*********** +Std Types +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 5bdc4c2ed..5901ea3ae 100644 --- a/courses/comprehensive_rust_training/110_std_types/01_std.rst +++ b/courses/comprehensive_rust_training/110_std_types/01_std.rst @@ -1,22 +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 :rust:`String` type. - -In fact, Rust contains several layers of the Standard Library: :rust:`core`, -:rust:`alloc` and :rust:`std`. - -- :rust:`core` includes the most basic types and functions that don't - depend on :rust:`libc`, allocator or even the presence of an operating - system. -- :rust:`alloc` includes types which require a global heap allocator, such - as :rust:`Vec`, :rust:`Box` and :rust:`Arc`. -- Embedded Rust applications often only use :rust:`core`, and sometimes - :rust:`alloc`. +================== +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 :rust:`String` type. + +In fact, Rust contains several layers of the Standard Library: :rust:`core`, +:rust:`alloc` and :rust:`std`. + +- :rust:`core` includes the most basic types and functions that don't + depend on :rust:`libc`, allocator or even the presence of an operating + system. +- :rust:`alloc` includes types which require a global heap allocator, such + as :rust:`Vec`, :rust:`Box` and :rust:`Arc`. +- Embedded Rust applications often only use :rust:`core`, and sometimes + :rust:`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 index a39a35247..a21b034e8 100644 --- a/courses/comprehensive_rust_training/110_std_types/02_docs.rst +++ b/courses/comprehensive_rust_training/110_std_types/02_docs.rst @@ -1,54 +1,54 @@ -=============== -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 :rust:`rustup doc --std` or https://std.rs to view the documentation. - -In fact, you can document your own code: - -.. code:: rust - - /// 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 -:rust:`//!` or :rust:`/*! .. */`, called "inner doc comments": - -.. code:: rust - - //! This module contains functionality relating to divisibility of integers. - ---------- -Details ---------- - -- Show students the generated docs for the :rust:`rand` crate at - https://docs.rs/rand. +=============== +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 :rust:`rustup doc --std` or https://std.rs to view the documentation. + +In fact, you can document your own code: + +.. code:: rust + + /// 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 +:rust:`//!` or :rust:`/*! .. */`, called "inner doc comments": + +.. code:: rust + + //! This module contains functionality relating to divisibility of integers. + +--------- +Details +--------- + +- Show students the generated docs for the :rust:`rand` crate at + https://docs.rs/rand. 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 e2705a0fe..a8e584b17 100644 --- a/courses/comprehensive_rust_training/110_std_types/03_option.rst +++ b/courses/comprehensive_rust_training/110_std_types/03_option.rst @@ -1,44 +1,44 @@ -======== -Option -======== - --------- -Option --------- - -We have already seen some use of :rust:`Option`. It stores either a value -of type :rust:`T` or nothing. For example, -`String::find `__ -returns an :rust:`Option`. - -.. code:: rust - - fn main() { - 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'); - println!("find returned {position:?}"); - assert_eq!(position.expect("Character not found"), 0); - } - ---------- -Details ---------- - -- :rust:`Option` is widely used, not just in the standard library. -- :rust:`unwrap` will return the value in an :rust:`Option`, or panic. - :rust:`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 :rust:`unwrap`/:rust:`expect` all over the place when - hacking something together, but production code typically handles - :rust:`None` in a nicer fashion. - -- The "niche optimization" means that :rust:`Option` often has the same - size in memory as :rust:`T`, if there is some representation that is not - a valid value of T. For example, a reference cannot be NULL, so - :rust:`Option<&T>` automatically uses NULL to represent the :rust:`None` - variant, and thus can be stored in the same memory as :rust:`&T`. +======== +Option +======== + +-------- +Option +-------- + +We have already seen some use of :rust:`Option`. It stores either a value +of type :rust:`T` or nothing. For example, +`String::find `__ +returns an :rust:`Option`. + +.. code:: rust + + fn main() { + 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'); + println!("find returned {position:?}"); + assert_eq!(position.expect("Character not found"), 0); + } + +--------- +Details +--------- + +- :rust:`Option` is widely used, not just in the standard library. +- :rust:`unwrap` will return the value in an :rust:`Option`, or panic. + :rust:`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 :rust:`unwrap`/:rust:`expect` all over the place when + hacking something together, but production code typically handles + :rust:`None` in a nicer fashion. + +- The "niche optimization" means that :rust:`Option` often has the same + size in memory as :rust:`T`, if there is some representation that is not + a valid value of T. For example, a reference cannot be NULL, so + :rust:`Option<&T>` automatically uses NULL to represent the :rust:`None` + variant, and thus can be stored in the same memory as :rust:`&T`. diff --git a/courses/comprehensive_rust_training/110_std_types/04_result.rst b/courses/comprehensive_rust_training/110_std_types/04_result.rst index a8206af2c..4046cd4fc 100644 --- a/courses/comprehensive_rust_training/110_std_types/04_result.rst +++ b/courses/comprehensive_rust_training/110_std_types/04_result.rst @@ -1,49 +1,49 @@ -======== -Result -======== - --------- -Result --------- - -:rust:`Result` is similar to :rust:`Option`, but indicates the success or -failure of an operation, each with a different enum variant. It is -generic: :rust:`Result` where :rust:`T` is used in the :rust:`Ok` variant and -:rust:`E` appears in the :rust:`Err` variant. - -.. code:: rust - - 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}"); - } - } - } - ---------- -Details ---------- - -- As with :rust:`Option`, the successful value sits inside of :rust:`Result`, - forcing the developer to explicitly extract it. This encourages error - checking. In the case where an error should never happen, - :rust:`unwrap()` or :rust:`expect()` can be called, and this is a signal of - the developer intent too. -- :rust:`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. -- :rust:`Result` is the standard type to implement error handling as we - will see on Day 4. +======== +Result +======== + +-------- +Result +-------- + +:rust:`Result` is similar to :rust:`Option`, but indicates the success or +failure of an operation, each with a different enum variant. It is +generic: :rust:`Result` where :rust:`T` is used in the :rust:`Ok` variant and +:rust:`E` appears in the :rust:`Err` variant. + +.. code:: rust + + 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}"); + } + } + } + +--------- +Details +--------- + +- As with :rust:`Option`, the successful value sits inside of :rust:`Result`, + forcing the developer to explicitly extract it. This encourages error + checking. In the case where an error should never happen, + :rust:`unwrap()` or :rust:`expect()` can be called, and this is a signal of + the developer intent too. +- :rust:`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. +- :rust:`Result` is the standard type to implement error handling as we + will see on Day 4. 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 c03ad5b84..33db396f5 100644 --- a/courses/comprehensive_rust_training/110_std_types/05_string.rst +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -1,75 +1,75 @@ -======== -String -======== - --------- -String --------- - -`String `__ -is a growable UTF-8 encoded string: - -.. code:: rust - - 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("a z"); - println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count()); - } - -:rust:`String` implements -:rust:`Deref` - -.. - https://doc.rust-lang.org/std/string/struct.String.html#deref-methods-str - -which means that you can call all :rust:`str` methods on a :rust:`String`. - ---------- -Details ---------- - -- :rust:`String::new` returns a new empty string, use - :rust:`String::with_capacity` when you know how much data you want to - push to the string. -- :rust:`String::len` returns the size of the :rust:`String` in bytes (which - can be different from its length in characters). -- :rust:`String::chars` returns an iterator over the actual characters. - Note that a :rust:`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 - :rust:`&str` or :rust:`String`. -- When a type implements :rust:`Deref`, the compiler will let - you transparently call methods from :rust:`T`. - - - We haven't discussed the :rust:`Deref` trait yet, so at this point - this mostly explains the structure of the sidebar in the - documentation. - - :rust:`String` implements :rust:`Deref` which transparently - gives it access to :rust:`str` methods. - - Write and compare :rust:`let s3 = s1.deref();` and :rust:`let s3 = &*s1;`. - -- :rust:`String` is implemented as a wrapper around a vector of bytes, many - of the operations you see supported on vectors are also supported on - :rust:`String`, but with some extra guarantees. -- Compare the different ways to index a :rust:`String`: - - - To a character by using :rust:`s3.chars().nth(i).unwrap()` where :rust:`i` - is in-bound, out-of-bounds. - - To a substring by using :rust:`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 :rust:`Display`, so anything that can be formatted can also be - converted to a string. +======== +String +======== + +-------- +String +-------- + +`String `__ +is a growable UTF-8 encoded string: + +.. code:: rust + + 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("a z"); + println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count()); + } + +:rust:`String` implements +:rust:`Deref` + +.. + https://doc.rust-lang.org/std/string/struct.String.html#deref-methods-str + +which means that you can call all :rust:`str` methods on a :rust:`String`. + +--------- +Details +--------- + +- :rust:`String::new` returns a new empty string, use + :rust:`String::with_capacity` when you know how much data you want to + push to the string. +- :rust:`String::len` returns the size of the :rust:`String` in bytes (which + can be different from its length in characters). +- :rust:`String::chars` returns an iterator over the actual characters. + Note that a :rust:`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 + :rust:`&str` or :rust:`String`. +- When a type implements :rust:`Deref`, the compiler will let + you transparently call methods from :rust:`T`. + + - We haven't discussed the :rust:`Deref` trait yet, so at this point + this mostly explains the structure of the sidebar in the + documentation. + - :rust:`String` implements :rust:`Deref` which transparently + gives it access to :rust:`str` methods. + - Write and compare :rust:`let s3 = s1.deref();` and :rust:`let s3 = &*s1;`. + +- :rust:`String` is implemented as a wrapper around a vector of bytes, many + of the operations you see supported on vectors are also supported on + :rust:`String`, but with some extra guarantees. +- Compare the different ways to index a :rust:`String`: + + - To a character by using :rust:`s3.chars().nth(i).unwrap()` where :rust:`i` + is in-bound, out-of-bounds. + - To a substring by using :rust:`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 :rust:`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 c82554155..024c32a38 100644 --- a/courses/comprehensive_rust_training/110_std_types/06_vec.rst +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -1,62 +1,62 @@ -========= -"Vec" -========= - ---------- -"Vec" ---------- - -`Vec `__ is the -standard resizable heap-allocated buffer: - -.. code:: rust - - 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:?}"); - } - -:rust:`Vec` implements -:rust:`Deref` - -.. - https://doc.rust-lang.org/std/vec/struct.Vec.html#deref-methods-%5BT%5D - -which means that you can call slice methods on a :rust:`Vec`. - ---------- -Details ---------- - -- :rust:`Vec` is a type of collection, along with :rust:`String` and - :rust:`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 :rust:`Vec` is a generic type too, but you don't have to - specify :rust:`T` explicitly. As always with Rust type inference, the - :rust:`T` was established during the first :rust:`push` call. -- :rust:`vec![...]` is a canonical macro to use instead of :rust:`Vec::new()` - and it supports adding initial elements to the vector. -- To index the vector you use :rust:`[` :rust:`]`, but they will panic if out - of bounds. Alternatively, using :rust:`get` will return an :rust:`Option`. - The :rust:`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 :rust:`Vec` gives access to all of the documented slice - methods, too. +========= +"Vec" +========= + +--------- +"Vec" +--------- + +`Vec `__ is the +standard resizable heap-allocated buffer: + +.. code:: rust + + 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:?}"); + } + +:rust:`Vec` implements +:rust:`Deref` + +.. + https://doc.rust-lang.org/std/vec/struct.Vec.html#deref-methods-%5BT%5D + +which means that you can call slice methods on a :rust:`Vec`. + +--------- +Details +--------- + +- :rust:`Vec` is a type of collection, along with :rust:`String` and + :rust:`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 :rust:`Vec` is a generic type too, but you don't have to + specify :rust:`T` explicitly. As always with Rust type inference, the + :rust:`T` was established during the first :rust:`push` call. +- :rust:`vec![...]` is a canonical macro to use instead of :rust:`Vec::new()` + and it supports adding initial elements to the vector. +- To index the vector you use :rust:`[` :rust:`]`, but they will panic if out + of bounds. Alternatively, using :rust:`get` will return an :rust:`Option`. + The :rust:`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 :rust:`Vec` gives access to all of the documented slice + methods, too. 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 3a550502a..38e70c58b 100644 --- a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -1,90 +1,90 @@ -============= -"HashMap" -============= - -------------- -"HashMap" -------------- - -Standard hash map with protection against HashDoS attacks: - -.. code:: rust - - 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 Miserables") { - println!( - "We know about {} books, but not Les Miserables.", - 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:#?}"); - } - ---------- -Details ---------- - -- :rust:`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 - - 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 :rust:`vec!`, there is unfortunately no standard :rust:`hashmap!` - macro. - - - Although, since Rust 1.56, HashMap implements - rust:`From<[(K, V); N]>` - - .. - https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.html#impl-From%3C%5B(K,+V);+N%5D%3E-for-HashMap%3CK,+V,+RandomState%3E - - which allows us to easily initialize a hash map from a literal - array: - - .. code:: rust - - 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 :rust:`Iterator` which yields - key-value tuples. - -- This type has several "method-specific" return types, such as - :rust:`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 :rust:`keys` method. +============= +"HashMap" +============= + +------------- +"HashMap" +------------- + +Standard hash map with protection against HashDoS attacks: + +.. code:: rust + + 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 Miserables") { + println!( + "We know about {} books, but not Les Miserables.", + 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:#?}"); + } + +--------- +Details +--------- + +- :rust:`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 + + 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 :rust:`vec!`, there is unfortunately no standard :rust:`hashmap!` + macro. + + - Although, since Rust 1.56, HashMap implements + rust:`From<[(K, V); N]>` + + .. + https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.html#impl-From%3C%5B(K,+V);+N%5D%3E-for-HashMap%3CK,+V,+RandomState%3E + + which allows us to easily initialize a hash map from a literal + array: + + .. code:: rust + + 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 :rust:`Iterator` which yields + key-value tuples. + +- This type has several "method-specific" return types, such as + :rust:`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 :rust:`keys` method. 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 aa8ca503d..bfcd1142c 100644 --- a/courses/comprehensive_rust_training/110_std_types/08_exercise.rst +++ b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst @@ -1,56 +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 :rust:`Counter` is hard coded to only work for -:rust:`u32` values. Make the struct and its methods generic over the type of -value being tracked, that way :rust:`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 -:rust:`count` method. - -:: - - 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}} +=================== +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 :rust:`Counter` is hard coded to only work for +:rust:`u32` values. Make the struct and its methods generic over the type of +value being tracked, that way :rust:`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 +:rust:`count` method. + +:: + + 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 index 17cfaca79..7025ac99a 100644 --- a/courses/comprehensive_rust_training/120_std_traits.rst +++ b/courses/comprehensive_rust_training/120_std_traits.rst @@ -1,46 +1,46 @@ -************ -Std Traits -************ - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************ +Std Traits +************ + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 139c274d7..0354ac9c0 100644 --- a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst +++ b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst @@ -1,83 +1,83 @@ -============= -Comparisons -============= - -------------- -Comparisons -------------- - -These traits support comparisons between values. All traits can be -derived for types containing fields that implement these traits. - --------------------------- -"PartialEq" and "Eq" --------------------------- - -:rust:`PartialEq` is a partial equivalence relation, with required method -:rust:`eq` and provided method :rust:`ne`. The :rust:`==` and :rust:`!=` operators will -call these methods. - -.. code:: rust - - struct Key { - id: u32, - metadata: Option, - } - impl PartialEq for Key { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } - } - -:rust:`Eq` is a full equivalence relation (reflexive, symmetric, and -transitive) and implies :rust:`PartialEq`. Functions that require full -equivalence will use :rust:`Eq` as a trait bound. - ----------------------------- -"PartialOrd" and "Ord" ----------------------------- - -:rust:`PartialOrd` defines a partial ordering, with a :rust:`partial_cmp` -method. It is used to implement the :rust:`<`, :rust:`<=`, :rust:`>=`, and :rust:`>` -operators. - -.. code:: rust - - 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, - } - } - } - -:rust:`Ord` is a total ordering, with :rust:`cmp` returning :rust:`Ordering`. - ---------- -Details ---------- - -:rust:`PartialEq` can be implemented between different types, but :rust:`Eq` -cannot, because it is reflexive: - -.. code:: rust - - 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. +============= +Comparisons +============= + +------------- +Comparisons +------------- + +These traits support comparisons between values. All traits can be +derived for types containing fields that implement these traits. + +-------------------------- +"PartialEq" and "Eq" +-------------------------- + +:rust:`PartialEq` is a partial equivalence relation, with required method +:rust:`eq` and provided method :rust:`ne`. The :rust:`==` and :rust:`!=` operators will +call these methods. + +.. code:: rust + + struct Key { + id: u32, + metadata: Option, + } + impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } + } + +:rust:`Eq` is a full equivalence relation (reflexive, symmetric, and +transitive) and implies :rust:`PartialEq`. Functions that require full +equivalence will use :rust:`Eq` as a trait bound. + +---------------------------- +"PartialOrd" and "Ord" +---------------------------- + +:rust:`PartialOrd` defines a partial ordering, with a :rust:`partial_cmp` +method. It is used to implement the :rust:`<`, :rust:`<=`, :rust:`>=`, and :rust:`>` +operators. + +.. code:: rust + + 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, + } + } + } + +:rust:`Ord` is a total ordering, with :rust:`cmp` returning :rust:`Ordering`. + +--------- +Details +--------- + +:rust:`PartialEq` can be implemented between different types, but :rust:`Eq` +cannot, because it is reflexive: + +.. code:: rust + + 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. 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 4279411f2..53c974308 100644 --- a/courses/comprehensive_rust_training/120_std_traits/02_operators.rst +++ b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst @@ -1,61 +1,61 @@ -=========== -Operators -=========== - ------------ -Operators ------------ - -Operator overloading is implemented via traits in -`std::ops `__: - -.. code:: rust - - #[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); - } - ---------- -Details ---------- - -Discussion points: - -- You could implement :rust:`Add` for :rust:`&Point`. In which situations is - that useful? - - - Answer: :rust:`Add:add` consumes :rust:`self`. If type :rust:`T` for which you - are overloading the operator is not :rust:`Copy`, you should consider - overloading the operator for :rust:`&T` as well. This avoids - unnecessary cloning on the call site. - -- Why is :rust:`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 :rust:`Output`) are controlled by - the implementer of a trait. - -- You could implement :rust:`Add` for two different types, e.g. - :rust:`impl Add<(i32, i32)> for Point` would add a tuple to a :rust:`Point`. - -The :rust:`Not` trait (:rust:`!` 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: :rust:`!5 == -6`. +=========== +Operators +=========== + +----------- +Operators +----------- + +Operator overloading is implemented via traits in +`std::ops `__: + +.. code:: rust + + #[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); + } + +--------- +Details +--------- + +Discussion points: + +- You could implement :rust:`Add` for :rust:`&Point`. In which situations is + that useful? + + - Answer: :rust:`Add:add` consumes :rust:`self`. If type :rust:`T` for which you + are overloading the operator is not :rust:`Copy`, you should consider + overloading the operator for :rust:`&T` as well. This avoids + unnecessary cloning on the call site. + +- Why is :rust:`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 :rust:`Output`) are controlled by + the implementer of a trait. + +- You could implement :rust:`Add` for two different types, e.g. + :rust:`impl Add<(i32, i32)> for Point` would add a tuple to a :rust:`Point`. + +The :rust:`Not` trait (:rust:`!` 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: :rust:`!5 == -6`. 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 9f6bfe662..f11728265 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 @@ -1,49 +1,49 @@ -======================= -"From" and "Into" -======================= - ------------------------ -"From" and "Into" ------------------------ - -Types implement -`From `__ and -`Into `__ to -facilitate type conversions. Unlike :rust:`as`, these traits correspond to -lossless, infallible conversions. - -.. code:: rust - - 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 - - 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}"); - } - ---------- -Details ---------- - -- That's why it is common to only implement :rust:`From`, as your type will - get :rust:`Into` implementation too. -- When declaring a function argument input type like "anything that can - be converted into a :rust:`String`", the rule is opposite, you should use - :rust:`Into`. Your function will accept types that implement :rust:`From` and - those that *only* implement :rust:`Into`. +======================= +"From" and "Into" +======================= + +----------------------- +"From" and "Into" +----------------------- + +Types implement +`From `__ and +`Into `__ to +facilitate type conversions. Unlike :rust:`as`, these traits correspond to +lossless, infallible conversions. + +.. code:: rust + + 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 + + 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}"); + } + +--------- +Details +--------- + +- That's why it is common to only implement :rust:`From`, as your type will + get :rust:`Into` implementation too. +- When declaring a function argument input type like "anything that can + be converted into a :rust:`String`", the rule is opposite, you should use + :rust:`Into`. Your function will accept types that implement :rust:`From` and + those that *only* implement :rust:`Into`. 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 ac28d9039..aaa76e273 100644 --- a/courses/comprehensive_rust_training/120_std_traits/04_casting.rst +++ b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst @@ -1,47 +1,47 @@ -========= -Casting -========= - ---------- -Casting ---------- - -Rust has no *implicit* type conversions, but does support explicit casts -with :rust:`as`. These generally follow C semantics where those are defined. - -.. code:: rust - - 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 :rust:`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 - check the docs, and comment for clarity. - -Casting with :rust:`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 :rust:`u64` with -:rust:`as u32`, regardless of what was in the high bits). - -For infallible casts (e.g. :rust:`u32` to :rust:`u64`), prefer using :rust:`From` or -:rust:`Into` over :rust:`as` to confirm that the cast is in fact infallible. For -fallible casts, :rust:`TryFrom` and :rust:`TryInto` are available when you want -to handle casts that fit differently from those that don't. - ---------- -Details ---------- - -Consider taking a break after this slide. - -:rust:`as` is similar to a C++ static cast. Use of :rust:`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 :rust:`usize` for use as an index. +========= +Casting +========= + +--------- +Casting +--------- + +Rust has no *implicit* type conversions, but does support explicit casts +with :rust:`as`. These generally follow C semantics where those are defined. + +.. code:: rust + + 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 :rust:`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 - check the docs, and comment for clarity. + +Casting with :rust:`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 :rust:`u64` with +:rust:`as u32`, regardless of what was in the high bits). + +For infallible casts (e.g. :rust:`u32` to :rust:`u64`), prefer using :rust:`From` or +:rust:`Into` over :rust:`as` to confirm that the cast is in fact infallible. For +fallible casts, :rust:`TryFrom` and :rust:`TryInto` are available when you want +to handle casts that fit differently from those that don't. + +--------- +Details +--------- + +Consider taking a break after this slide. + +:rust:`as` is similar to a C++ static cast. Use of :rust:`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 :rust:`usize` for use as an index. 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 39626bffd..c39d2b4ca 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 @@ -1,51 +1,51 @@ -======================== -"Read" and "Write" -======================== - ------------------------- -"Read" and "Write" ------------------------- - -Using `Read `__ -and -`BufRead `__, -you can abstract over :rust:`u8` sources: - -.. code:: rust - - 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 :rust:`u8` sinks: - -.. code:: rust - - 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(()) - } +======================== +"Read" and "Write" +======================== + +------------------------ +"Read" and "Write" +------------------------ + +Using `Read `__ +and +`BufRead `__, +you can abstract over :rust:`u8` sources: + +.. code:: rust + + 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 :rust:`u8` sinks: + +.. code:: rust + + 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 index b029c6767..520647705 100644 --- a/courses/comprehensive_rust_training/120_std_traits/06_default.rst +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -1,59 +1,59 @@ -======================= -The "Default" Trait -======================= - ------------------------ -The "Default" Trait ------------------------ - -`Default `__ -trait produces a default value for a type. - -.. code:: rust - - #[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()); - } - ---------- -Details ---------- - -- It can be implemented directly or it can be derived via - :rust:`#[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 :rust:`Default` too. - -- Standard Rust types often implement :rust:`Default` with reasonable - values (e.g. :rust:`0`, :rust:`""`, etc). -- The partial struct initialization works nicely with default. -- The Rust standard library is aware that types can implement - :rust:`Default` and provides convenience methods that use it. -- The :rust:`..` syntax is called - `struct update syntax `__. +======================= +The "Default" Trait +======================= + +----------------------- +The "Default" Trait +----------------------- + +`Default `__ +trait produces a default value for a type. + +.. code:: rust + + #[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()); + } + +--------- +Details +--------- + +- It can be implemented directly or it can be derived via + :rust:`#[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 :rust:`Default` too. + +- Standard Rust types often implement :rust:`Default` with reasonable + values (e.g. :rust:`0`, :rust:`""`, etc). +- The partial struct initialization works nicely with default. +- The Rust standard library is aware that types can implement + :rust:`Default` and provides convenience methods that use it. +- The :rust:`..` syntax is called + `struct update syntax `__. 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 05335fb7c..8e77d95fb 100644 --- a/courses/comprehensive_rust_training/120_std_traits/07_closures.rst +++ b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst @@ -1,87 +1,87 @@ -========== -Closures -========== - ----------- -Closures ----------- - -Closures or lambda expressions have types which cannot be named. -However, they implement special -`Fn `__, -`FnMut `__, and -`FnOnce `__ -traits: - -.. code:: rust - - 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); - } - ---------- -Details ---------- - -An :rust:`Fn` (e.g. :rust:`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 :rust:`FnMut` (e.g. :rust:`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 :rust:`FnOnce` (e.g. :rust:`multiply_sum`), you may only call it -once. Doing so consumes the closure and any values captured by move. - -:rust:`FnMut` is a subtype of :rust:`FnOnce`. :rust:`Fn` is a subtype of :rust:`FnMut` -and :rust:`FnOnce`. I.e. you can use an :rust:`FnMut` wherever an :rust:`FnOnce` is -called for, and you can use an :rust:`Fn` wherever an :rust:`FnMut` or -:rust:`FnOnce` is called for. - -When you define a function that takes a closure, you should take -:rust:`FnOnce` if you can (i.e. you call it once), or :rust:`FnMut` else, and -last :rust:`Fn`. This allows the most flexibility for the caller. - -In contrast, when you have a closure, the most flexible you can have is -:rust:`Fn` (which can be passed to a consumer of any of the 3 closure -traits), then :rust:`FnMut`, and lastly :rust:`FnOnce`. - -The compiler also infers :rust:`Copy` (e.g. for :rust:`add_3`) and :rust:`Clone` -(e.g. :rust:`multiply_sum`), depending on what the closure captures. -Function pointers (references to :rust:`fn` items) implement :rust:`Copy` and -:rust:`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 :rust:`move` keyword -forces capture by value. - -.. code:: rust - - 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"); - } +========== +Closures +========== + +---------- +Closures +---------- + +Closures or lambda expressions have types which cannot be named. +However, they implement special +`Fn `__, +`FnMut `__, and +`FnOnce `__ +traits: + +.. code:: rust + + 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); + } + +--------- +Details +--------- + +An :rust:`Fn` (e.g. :rust:`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 :rust:`FnMut` (e.g. :rust:`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 :rust:`FnOnce` (e.g. :rust:`multiply_sum`), you may only call it +once. Doing so consumes the closure and any values captured by move. + +:rust:`FnMut` is a subtype of :rust:`FnOnce`. :rust:`Fn` is a subtype of :rust:`FnMut` +and :rust:`FnOnce`. I.e. you can use an :rust:`FnMut` wherever an :rust:`FnOnce` is +called for, and you can use an :rust:`Fn` wherever an :rust:`FnMut` or +:rust:`FnOnce` is called for. + +When you define a function that takes a closure, you should take +:rust:`FnOnce` if you can (i.e. you call it once), or :rust:`FnMut` else, and +last :rust:`Fn`. This allows the most flexibility for the caller. + +In contrast, when you have a closure, the most flexible you can have is +:rust:`Fn` (which can be passed to a consumer of any of the 3 closure +traits), then :rust:`FnMut`, and lastly :rust:`FnOnce`. + +The compiler also infers :rust:`Copy` (e.g. for :rust:`add_3`) and :rust:`Clone` +(e.g. :rust:`multiply_sum`), depending on what the closure captures. +Function pointers (references to :rust:`fn` items) implement :rust:`Copy` and +:rust:`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 :rust:`move` keyword +forces capture by value. + +.. code:: rust + + 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"); + } 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 98ded3cdb..81e247ea5 100644 --- a/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst +++ b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst @@ -1,23 +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. - -:: - - {{#include exercise.rs:head }} - - // Implement the `Read` trait for `RotDecoder`. - - {{#include exercise.rs:main }} - -What happens if you chain two :rust:`RotDecoder` instances together, each -rotating by 13 characters? +================= +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. + +:: + + {{#include exercise.rs:head }} + + // Implement the `Read` trait for `RotDecoder`. + + {{#include exercise.rs:main }} + +What happens if you chain two :rust:`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 index 129b8fbc6..328a173c9 100644 --- a/courses/comprehensive_rust_training/130_memory_management.rst +++ b/courses/comprehensive_rust_training/130_memory_management.rst @@ -1,46 +1,46 @@ -******************* -Memory Management -******************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +******************* +Memory Management +******************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 0498bf993..d8928edb0 100644 --- a/courses/comprehensive_rust_training/130_memory_management/01_review.rst +++ b/courses/comprehensive_rust_training/130_memory_management/01_review.rst @@ -1,85 +1,85 @@ -========================== -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 :rust:`String` puts fixed-sized metadata on the stack and -dynamically sized data, the actual string, on the heap: - -.. code:: rust - - fn main() { - let s1 = String::from("Hello"); - } - -.. code:: bob - - Stack - .- - - - - - - - - - - - - -. Heap - : : .- - - - - - - - - - - - - - - -. - : s1 : : : - : +-----------+-------+ : : : - : | capacity | 5 | : : +----+----+----+----+----+ : - : | ptr | o-+---+-----+-->| H | e | l | l | o | : - : | len | 5 | : : +----+----+----+----+----+ : - : +-----------+-------+ : : : - : : : : - `- - - - - - - - - - - - - -' `- - - - - - - - - - - - - - - -' - ---------- -Details ---------- - -- Mention that a :rust:`String` is backed by a :rust:`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 :rust:`unsafe` Rust. However, you -should point out that this is rightfully unsafe! - -.. code:: rust - - 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}"); - } - } +========================== +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 :rust:`String` puts fixed-sized metadata on the stack and +dynamically sized data, the actual string, on the heap: + +.. code:: rust + + fn main() { + let s1 = String::from("Hello"); + } + +.. code:: bob + + Stack + .- - - - - - - - - - - - - -. Heap + : : .- - - - - - - - - - - - - - - -. + : s1 : : : + : +-----------+-------+ : : : + : | capacity | 5 | : : +----+----+----+----+----+ : + : | ptr | o-+---+-----+-->| H | e | l | l | o | : + : | len | 5 | : : +----+----+----+----+----+ : + : +-----------+-------+ : : : + : : : : + `- - - - - - - - - - - - - -' `- - - - - - - - - - - - - - - -' + +--------- +Details +--------- + +- Mention that a :rust:`String` is backed by a :rust:`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 :rust:`unsafe` Rust. However, you +should point out that this is rightfully unsafe! + +.. code:: rust + + 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}"); + } + } 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 0096ca210..9ddeede66 100644 --- a/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst +++ b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst @@ -1,61 +1,61 @@ -================================= -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. - ---------- -Details ---------- - -This slide is intended to help students coming from other languages to -put Rust in context. - -- C must manage heap manually with :rust:`malloc` and :rust:`free`. Common - errors include forgetting to call :rust:`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 (:rust:`unique_ptr`, :rust:`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 - 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). +================================= +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. + +--------- +Details +--------- + +This slide is intended to help students coming from other languages to +put Rust in context. + +- C must manage heap manually with :rust:`malloc` and :rust:`free`. Common + errors include forgetting to call :rust:`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 (:rust:`unique_ptr`, :rust:`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 - 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/130_memory_management/03_ownership.rst b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst index 2aad9aa22..976548aff 100644 --- a/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst +++ b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst @@ -1,36 +1,36 @@ -=========== -Ownership -=========== - ------------ -Ownership ------------ - -All variable bindings have a *scope* where they are valid and it is an -error to use a variable outside its scope: - -.. code:: rust - - 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. - ---------- -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. +=========== +Ownership +=========== + +----------- +Ownership +----------- + +All variable bindings have a *scope* where they are valid and it is an +error to use a variable outside its scope: + +.. code:: rust + + 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. + +--------- +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. 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 102f3b8ff..8e20b7a5a 100644 --- a/courses/comprehensive_rust_training/130_memory_management/04_move.rst +++ b/courses/comprehensive_rust_training/130_memory_management/04_move.rst @@ -1,182 +1,182 @@ -================ -Move Semantics -================ - ----------------- -Move Semantics ----------------- - -An assignment will transfer *ownership* between variables: - -.. code:: rust - - fn main() { - let s1: String = String::from("Hello!"); - let s2: String = s1; - println!("s2: {s2}"); - // println!("s1: {s1}"); - } - -- The assignment of :rust:`s1` to :rust:`s2` transfers ownership. -- When :rust:`s1` goes out of scope, nothing happens: it does not own - anything. -- When :rust:`s2` goes out of scope, the string data is freed. - -Before move to :rust:`s2`: - -.. code:: bob - - Stack Heap - .- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -. - : : : : - : s1 : : : - : +-----------+-------+ : : +----+----+----+----+----+----+ : - : | ptr | o---+---+-----+-->| H | e | l | l | o | ! | : - : | len | 6 | : : +----+----+----+----+----+----+ : - : | capacity | 6 | : : : - : +-----------+-------+ : : : - : : `- - - - - - - - - - - - - - - - - - -' - : : - `- - - - - - - - - - - - - -' - -After move to :rust:`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 - - fn say_hello(name: String) { - println!("Hello {name}") - } - - fn main() { - let name = String::from("Alice"); - say_hello(name); - // say_hello(name); - } - ---------- -Details ---------- - -- Mention that this is the opposite of the defaults in C++, which - copies by value unless you use :rust:`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 :rust:`Copy` (see later - slides). - -- In Rust, clones are explicit (by using :rust:`clone`). - -In the :rust:`say_hello` example: - -- With the first call to :rust:`say_hello`, :rust:`main` gives up ownership of - :rust:`name`. Afterwards, :rust:`name` cannot be used anymore within - :rust:`main`. -- The heap memory allocated for :rust:`name` will be freed at the end of - the :rust:`say_hello` function. -- :rust:`main` can retain ownership if it passes :rust:`name` as a reference - (:rust:`&name`) and if :rust:`say_hello` accepts a reference as a parameter. -- Alternatively, :rust:`main` can pass a clone of :rust:`name` in the first - call (:rust:`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. - --------------------------------- -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 :rust:`s1` is duplicated and :rust:`s2` gets its own - independent copy. -- When :rust:`s1` and :rust:`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 :rust:`=` - 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 :rust:`s2 = std::move(s1)`, no heap allocation would - take place. After the move, :rust:`s1` would be in a valid but - unspecified state. Unlike Rust, the programmer is allowed to keep - using :rust:`s1`. - -- Unlike Rust, :rust:`=` in C++ can run arbitrary code as determined by the - type which is being copied or moved. +================ +Move Semantics +================ + +---------------- +Move Semantics +---------------- + +An assignment will transfer *ownership* between variables: + +.. code:: rust + + fn main() { + let s1: String = String::from("Hello!"); + let s2: String = s1; + println!("s2: {s2}"); + // println!("s1: {s1}"); + } + +- The assignment of :rust:`s1` to :rust:`s2` transfers ownership. +- When :rust:`s1` goes out of scope, nothing happens: it does not own + anything. +- When :rust:`s2` goes out of scope, the string data is freed. + +Before move to :rust:`s2`: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -. + : : : : + : s1 : : : + : +-----------+-------+ : : +----+----+----+----+----+----+ : + : | ptr | o---+---+-----+-->| H | e | l | l | o | ! | : + : | len | 6 | : : +----+----+----+----+----+----+ : + : | capacity | 6 | : : : + : +-----------+-------+ : : : + : : `- - - - - - - - - - - - - - - - - - -' + : : + `- - - - - - - - - - - - - -' + +After move to :rust:`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 + + fn say_hello(name: String) { + println!("Hello {name}") + } + + fn main() { + let name = String::from("Alice"); + say_hello(name); + // say_hello(name); + } + +--------- +Details +--------- + +- Mention that this is the opposite of the defaults in C++, which + copies by value unless you use :rust:`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 :rust:`Copy` (see later + slides). + +- In Rust, clones are explicit (by using :rust:`clone`). + +In the :rust:`say_hello` example: + +- With the first call to :rust:`say_hello`, :rust:`main` gives up ownership of + :rust:`name`. Afterwards, :rust:`name` cannot be used anymore within + :rust:`main`. +- The heap memory allocated for :rust:`name` will be freed at the end of + the :rust:`say_hello` function. +- :rust:`main` can retain ownership if it passes :rust:`name` as a reference + (:rust:`&name`) and if :rust:`say_hello` accepts a reference as a parameter. +- Alternatively, :rust:`main` can pass a clone of :rust:`name` in the first + call (:rust:`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. + +-------------------------------- +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 :rust:`s1` is duplicated and :rust:`s2` gets its own + independent copy. +- When :rust:`s1` and :rust:`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 :rust:`=` + 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 :rust:`s2 = std::move(s1)`, no heap allocation would + take place. After the move, :rust:`s1` would be in a valid but + unspecified state. Unlike Rust, the programmer is allowed to keep + using :rust:`s1`. + +- Unlike Rust, :rust:`=` in C++ can run arbitrary code as determined by the + type which is being copied or moved. diff --git a/courses/comprehensive_rust_training/130_memory_management/05_clone.rst b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst index 6f6484b7d..ca61caddf 100644 --- a/courses/comprehensive_rust_training/130_memory_management/05_clone.rst +++ b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst @@ -1,40 +1,40 @@ -======= -Clone -======= - -------- -Clone -------- - -Sometimes you *want* to make a copy of a value. The :rust:`Clone` trait -accomplishes this. - -.. code:: rust - - fn say_hello(name: String) { - println!("Hello {name}") - } - - fn main() { - let name = String::from("Alice"); - say_hello(name.clone()); - say_hello(name); - } - ---------- -Details ---------- - -- The idea of :rust:`Clone` is to make it easy to spot where heap - allocations are occurring. Look for :rust:`.clone()` and a few others - like :rust:`vec!` or :rust:`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. - -- :rust:`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 :rust:`clone` is user-defined, so it can perform custom - cloning logic if needed. +======= +Clone +======= + +------- +Clone +------- + +Sometimes you *want* to make a copy of a value. The :rust:`Clone` trait +accomplishes this. + +.. code:: rust + + fn say_hello(name: String) { + println!("Hello {name}") + } + + fn main() { + let name = String::from("Alice"); + say_hello(name.clone()); + say_hello(name); + } + +--------- +Details +--------- + +- The idea of :rust:`Clone` is to make it easy to spot where heap + allocations are occurring. Look for :rust:`.clone()` and a few others + like :rust:`vec!` or :rust:`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. + +- :rust:`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 :rust:`clone` is user-defined, so it can perform custom + cloning logic if needed. 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 index 81d95cffc..18c45f14e 100644 --- a/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst +++ b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst @@ -1,69 +1,69 @@ -============ -Copy Types -============ - ------------- -Copy Types ------------- - -While move semantics are the default, certain types are copied by -default: - -.. code:: rust - - 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 :rust:`Copy` trait. - -You can opt-in your own types to use copy semantics: - -.. code:: rust - - #[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 :rust:`p1` and :rust:`p2` own their own data. -- We can also use :rust:`p1.clone()` to explicitly copy the data. - ---------- -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 :rust:`Clone` trait. -- Copying does not work on types that implement the :rust:`Drop` trait. - -In the above example, try the following: - -- Add a :rust:`String` field to :rust:`struct Point`. It will not compile - because :rust:`String` is not a :rust:`Copy` type. -- Remove :rust:`Copy` from the :rust:`derive` attribute. The compiler error is - now in the :rust:`println!` for :rust:`p1`. -- Show that it works if you clone :rust:`p1` instead. - ------------------ -More to Explore ------------------ - -- Shared references are :rust:`Copy`/:rust:`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. +============ +Copy Types +============ + +------------ +Copy Types +------------ + +While move semantics are the default, certain types are copied by +default: + +.. code:: rust + + 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 :rust:`Copy` trait. + +You can opt-in your own types to use copy semantics: + +.. code:: rust + + #[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 :rust:`p1` and :rust:`p2` own their own data. +- We can also use :rust:`p1.clone()` to explicitly copy the data. + +--------- +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 :rust:`Clone` trait. +- Copying does not work on types that implement the :rust:`Drop` trait. + +In the above example, try the following: + +- Add a :rust:`String` field to :rust:`struct Point`. It will not compile + because :rust:`String` is not a :rust:`Copy` type. +- Remove :rust:`Copy` from the :rust:`derive` attribute. The compiler error is + now in the :rust:`println!` for :rust:`p1`. +- Show that it works if you clone :rust:`p1` instead. + +----------------- +More to Explore +----------------- + +- Shared references are :rust:`Copy`/:rust:`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. 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 1cc2b0409..40e58fc60 100644 --- a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst +++ b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst @@ -1,68 +1,68 @@ -==================== -The "Drop" Trait -==================== - --------------------- -The "Drop" Trait --------------------- - -Values which implement -`Drop `__ can -specify code to run when they go out of scope: - -.. code:: rust - - 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"); - } - ---------- -Details ---------- - -- Note that :rust:`std::mem::drop` is not the same as - :rust:`std::ops::Drop::drop`. -- Values are automatically dropped when they go out of scope. -- When a value is dropped, if it implements :rust:`std::ops::Drop` then its - :rust:`Drop::drop` implementation will be called. -- All its fields will then be dropped too, whether or not it implements - :rust:`Drop`. -- :rust:`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 :rust:`drop`: - releasing locks, closing files, etc. - -Discussion points: - -- Why doesn't :rust:`Drop::drop` take :rust:`self`? - - - Short-answer: If it did, :rust:`std::mem::drop` would be called at the - end of the block, resulting in another call to :rust:`Drop::drop`, and - a stack overflow! - -- Try replacing :rust:`drop(a)` with :rust:`a.drop()`. +==================== +The "Drop" Trait +==================== + +-------------------- +The "Drop" Trait +-------------------- + +Values which implement +`Drop `__ can +specify code to run when they go out of scope: + +.. code:: rust + + 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"); + } + +--------- +Details +--------- + +- Note that :rust:`std::mem::drop` is not the same as + :rust:`std::ops::Drop::drop`. +- Values are automatically dropped when they go out of scope. +- When a value is dropped, if it implements :rust:`std::ops::Drop` then its + :rust:`Drop::drop` implementation will be called. +- All its fields will then be dropped too, whether or not it implements + :rust:`Drop`. +- :rust:`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 :rust:`drop`: + releasing locks, closing files, etc. + +Discussion points: + +- Why doesn't :rust:`Drop::drop` take :rust:`self`? + + - Short-answer: If it did, :rust:`std::mem::drop` would be called at the + end of the block, resulting in another call to :rust:`Drop::drop`, and + a stack overflow! + +- Try replacing :rust:`drop(a)` with :rust:`a.drop()`. diff --git a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst index d6a9345f5..0c2838624 100644 --- a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst +++ b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst @@ -1,44 +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. - -:: - - {{#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}} +======================== +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. + +:: + + {{#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 index 2ca203d4e..9ff353a0b 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -1,42 +1,42 @@ -**************** -Smart Pointers -**************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +**************** +Smart Pointers +**************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index c79acfb7c..f6f26c04f 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -1,101 +1,101 @@ -============ -"Box" -============ - ------------- -"Box" ------------- - -`Box `__ is an -owned pointer to data on the heap: - -.. code:: rust - - fn main() { - let five = Box::new(5); - println!("five: {}", *five); - } - -.. code:: bob - - Stack Heap - .- - - - - - -. .- - - - - - -. - : : : : - : five : : : - : +-----+ : : +-----+ : - : | o---|---+-----+-->| 5 | : - : +-----+ : : +-----+ : - : : : : - : : : : - `- - - - - - -' `- - - - - - -' - -:rust:`Box` implements :rust:`Deref`, which means that you can -`call methods from T directly `__. -on a :rust:`Box` - -Recursive data types or data types with dynamic sizes cannot be stored -inline without a pointer indirection. :rust:`Box` accomplishes that -indirection: - -.. code:: rust - - #[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 | // | // | : - : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : - : : : : - : : : : - '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' - ---------- -Details ---------- - -- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's - guaranteed to be not null. - -- A :rust:`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 :rust:`Box` so only the pointer is moved. - -- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly - into the :rust:`List`, the compiler would not be able to compute a fixed - size for the struct in memory (the :rust:`List` would be of infinite - size). - -- :rust:`Box` solves this problem as it has the same size as a regular - pointer and just points at the next element of the :rust:`List` in the - heap. - -- Remove the :rust:`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 :rust:`Box` or - reference of some kind, instead of storing the value directly. - -- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be - empty/null. This makes :rust:`Box` one of the types that allow the - compiler to optimize storage of some enums (the "niche - optimization"). +============ +"Box" +============ + +------------ +"Box" +------------ + +`Box `__ is an +owned pointer to data on the heap: + +.. code:: rust + + fn main() { + let five = Box::new(5); + println!("five: {}", *five); + } + +.. code:: bob + + Stack Heap + .- - - - - - -. .- - - - - - -. + : : : : + : five : : : + : +-----+ : : +-----+ : + : | o---|---+-----+-->| 5 | : + : +-----+ : : +-----+ : + : : : : + : : : : + `- - - - - - -' `- - - - - - -' + +:rust:`Box` implements :rust:`Deref`, which means that you can +`call methods from T directly `__. +on a :rust:`Box` + +Recursive data types or data types with dynamic sizes cannot be stored +inline without a pointer indirection. :rust:`Box` accomplishes that +indirection: + +.. code:: rust + + #[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 | // | // | : + : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : + : : : : + : : : : + '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' + +--------- +Details +--------- + +- :rust:`Box` is like :rust:`std::unique_ptr` in C++, except that it's + guaranteed to be not null. + +- A :rust:`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 :rust:`Box` so only the pointer is moved. + +- If :rust:`Box` was not used and we attempted to embed a :rust:`List` directly + into the :rust:`List`, the compiler would not be able to compute a fixed + size for the struct in memory (the :rust:`List` would be of infinite + size). + +- :rust:`Box` solves this problem as it has the same size as a regular + pointer and just points at the next element of the :rust:`List` in the + heap. + +- Remove the :rust:`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 :rust:`Box` or + reference of some kind, instead of storing the value directly. + +- Though :rust:`Box` looks like :rust:`std::unique_ptr` in C++, it cannot be + empty/null. This makes :rust:`Box` one of the types that allow the + compiler to optimize storage of some enums (the "niche + optimization"). 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 d79a35179..c7e258f1e 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -1,47 +1,47 @@ -======== -"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 - - use std::rc::Rc; - - fn main() { - let a = Rc::new(10); - let b = Rc::clone(&a); - - println!("a: {a}"); - println!("b: {b}"); - } - -- See :rust:`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. - ---------- -Details ---------- - -- :rust:`Rc` count ensures that its contained value is valid for as - long as there are references. -- :rust:`Rc` in Rust is like :rust:`std::shared_ptr` in C++. -- :rust:`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. -- :rust:`make_mut` actually clones the inner value if necessary - ("clone-on-write") and returns a mutable reference. -- Use :rust:`Rc::strong_count` to check the reference count. -- :rust:`Rc::downgrade` gives you a *weakly reference-counted* object to - create cycles that will be dropped properly (likely in combination - with :rust:`RefCell`). +======== +"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 + + use std::rc::Rc; + + fn main() { + let a = Rc::new(10); + let b = Rc::clone(&a); + + println!("a: {a}"); + println!("b: {b}"); + } + +- See :rust:`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. + +--------- +Details +--------- + +- :rust:`Rc` count ensures that its contained value is valid for as + long as there are references. +- :rust:`Rc` in Rust is like :rust:`std::shared_ptr` in C++. +- :rust:`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. +- :rust:`make_mut` actually clones the inner value if necessary + ("clone-on-write") and returns a mutable reference. +- Use :rust:`Rc::strong_count` to check the reference count. +- :rust:`Rc::downgrade` gives you a *weakly reference-counted* object to + create cycles that will be dropped properly (likely in combination + with :rust:`RefCell`). 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 0efdc5715..a1c418b07 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 @@ -1,111 +1,111 @@ -===================== -Owned Trait Objects -===================== - ---------------------- -Owned Trait Objects ---------------------- - -We previously saw how trait objects can be used with references, e.g -:rust:`&dyn Pet`. However, we can also use trait objects with smart pointers -like :rust:`Box` to create an owned trait object: :rust:`Box`. - -.. code:: rust - - 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 :rust:`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" | : - : +----------------------+ : - : : - '- - - - - - - - - - - - - - - - - - - - - - -' - ---------- -Details ---------- - -- Types that implement a given trait may be of different sizes. This - makes it impossible to have things like :rust:`Vec` in the - example above. - -- :rust:`dyn Pet` is a way to tell the compiler about a dynamically sized - type that implements :rust:`Pet`. - -- In the example, :rust:`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 :rust:`Pet` implementation of that particular object. - - The data for the :rust:`Dog` named Fido is the :rust:`name` and :rust:`age` - fields. The :rust:`Cat` has a :rust:`lives` field. - -- Compare these outputs in the above example: - - .. code:: rust - - 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::>()); +===================== +Owned Trait Objects +===================== + +--------------------- +Owned Trait Objects +--------------------- + +We previously saw how trait objects can be used with references, e.g +:rust:`&dyn Pet`. However, we can also use trait objects with smart pointers +like :rust:`Box` to create an owned trait object: :rust:`Box`. + +.. code:: rust + + 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 :rust:`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" | : + : +----------------------+ : + : : + '- - - - - - - - - - - - - - - - - - - - - - -' + +--------- +Details +--------- + +- Types that implement a given trait may be of different sizes. This + makes it impossible to have things like :rust:`Vec` in the + example above. + +- :rust:`dyn Pet` is a way to tell the compiler about a dynamically sized + type that implements :rust:`Pet`. + +- In the example, :rust:`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 :rust:`Pet` implementation of that particular object. + - The data for the :rust:`Dog` named Fido is the :rust:`name` and :rust:`age` + fields. The :rust:`Cat` has a :rust:`lives` field. + +- Compare these outputs in the above example: + + .. code:: rust + + 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::>()); diff --git a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst index 0b6ea22d9..b9fbe4f1f 100644 --- a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst +++ b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst @@ -1,26 +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. - -:: - - include exercise.rs:types - - Implement new, insert, len, and has for Subtree. - - include exercise.rs:tests +======================= +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. + +:: + + 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 index 5283d993a..17b9f5f84 100644 --- a/courses/comprehensive_rust_training/150_borrowing.rst +++ b/courses/comprehensive_rust_training/150_borrowing.rst @@ -1,43 +1,43 @@ -*********** -Borrowing -*********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +*********** +Borrowing +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 1ef34b9e0..ecff35252 100644 --- a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst +++ b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst @@ -1,79 +1,79 @@ -=================== -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: - -.. code:: rust - - #[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 :rust:`add` function *borrows* two points and returns a new point. -- The caller retains ownership of the inputs. - ---------- -Details ---------- - -This slide is a review of the material on references from day 1, -expanding slightly to include function arguments and return values. - ------------------ -More to Explore ------------------ - -Notes on stack returns and inlining: - -- Demonstrate that the return from :rust:`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: - - .. code:: rust - - #[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 :rust:`#[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). +=================== +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: + +.. code:: rust + + #[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 :rust:`add` function *borrows* two points and returns a new point. +- The caller retains ownership of the inputs. + +--------- +Details +--------- + +This slide is a review of the material on references from day 1, +expanding slightly to include function arguments and return values. + +----------------- +More to Explore +----------------- + +Notes on stack returns and inlining: + +- Demonstrate that the return from :rust:`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: + + .. code:: rust + + #[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 :rust:`#[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). diff --git a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst index 9255467e4..ff1526a51 100644 --- a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst +++ b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst @@ -1,70 +1,70 @@ -================= -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: - -.. code:: rust - - 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. - -.. code:: rust - - fn main() { - let mut a: i32 = 10; - let b: &i32 = &a; - - { - let c: &mut i32 = &mut a; - *c = 20; - } - - println!("a: {a}"); - println!("b: {b}"); - } - ---------- -Details ---------- - -- 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 :rust:`a` is borrowed as mutable - (through :rust:`c`) and as immutable (through :rust:`b`) at the same time. -- Move the :rust:`println!` statement for :rust:`b` before the scope that - introduces :rust:`c` to make the code compile. -- After that change, the compiler realizes that :rust:`b` is only ever used - before the new mutable borrow of :rust:`a` through :rust:`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." +================= +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: + +.. code:: rust + + 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. + +.. code:: rust + + fn main() { + let mut a: i32 = 10; + let b: &i32 = &a; + + { + let c: &mut i32 = &mut a; + *c = 20; + } + + println!("a: {a}"); + println!("b: {b}"); + } + +--------- +Details +--------- + +- 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 :rust:`a` is borrowed as mutable + (through :rust:`c`) and as immutable (through :rust:`b`) at the same time. +- Move the :rust:`println!` statement for :rust:`b` before the scope that + introduces :rust:`c` to make the code compile. +- After that change, the compiler realizes that :rust:`b` is only ever used + before the new mutable borrow of :rust:`a` through :rust:`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." diff --git a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst index 950134b3e..a5f0944d3 100644 --- a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst +++ b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst @@ -1,39 +1,39 @@ -=============== -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 - - 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 - - fn main() { - let mut vec = vec![1, 2, 3, 4, 5]; - for elem in &vec { - vec.push(elem * 2); - } - } - ---------- -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. +=============== +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 + + 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 + + fn main() { + let mut vec = vec![1, 2, 3, 4, 5]; + for elem in &vec { + vec.push(elem * 2); + } + } + +--------- +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. diff --git a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst index b867a094a..07a237814 100644 --- a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst +++ b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst @@ -1,113 +1,113 @@ -===================== -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. - ---------- -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. - --------------- -"Cell" --------------- - -:rust:`Cell` wraps a value and allows getting or setting the value using -only a shared reference to the :rust:`Cell`. However, it does not allow any -references to the inner value. Since there are no references, borrowing -rules cannot be broken. - -.. code:: rust - - use std::cell::Cell; - - fn main() { - // Note that `cell` is NOT declared as mutable. - let cell = Cell::new(5); - - cell.set(123); - println!("{}", cell.get()); - } - ----------------- -"Cell" Details ----------------- - -- :rust:`Cell` is a simple means to ensure safety: it has a :rust:`set` method - that takes :rust:`&self`. This needs no runtime check, but requires - moving values, which can have its own cost. - ------------------ -"RefCell" ------------------ - -:rust:`RefCell` allows accessing and mutating a wrapped value by providing -alternative types :rust:`Ref` and :rust:`RefMut` that emulate :rust:`&T`/:rust:`&mut T` -without actually being Rust references. - -These types perform dynamic checks using a counter in the :rust:`RefCell` to -prevent existence of a :rust:`RefMut` alongside another :rust:`Ref`/:rust:`RefMut`. - -By implementing :rust:`Deref` (and :rust:`DerefMut` for :rust:`RefMut`), these types -allow calling methods on the inner value without allowing references to -escape. - -.. code:: rust - - use std::cell::RefCell; - - fn main() { - // Note that `cell` is NOT declared as mutable. - let cell = RefCell::new(5); - - { - let mut cell_ref = cell.borrow_mut(); - *cell_ref = 123; - - // This triggers an error at runtime. - // let other = cell.borrow(); - // println!("{}", *other); - } - - println!("{cell:?}"); - } - -.. raw:: html - -------------------- -"RefCell" Details -------------------- - -- :rust:`RefCell` enforces Rust's usual borrowing rules (either multiple - shared references or a single exclusive reference) with a runtime - check. In this case, all borrows are very short and never overlap, so - the checks always succeed. - -- The extra block in the example is to end the borrow created by the - call to :rust:`borrow_mut` before we print the cell. Trying to print a - borrowed :rust:`RefCell` just shows the message :rust:`"{borrowed}"`. - ------------------ -More to Explore ------------------ - -There are also :rust:`OnceCell` and :rust:`OnceLock`, which allow initialization -on first use. Making these useful requires some more knowledge than -students have at this time. - +===================== +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. + +--------- +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. + +-------------- +"Cell" +-------------- + +:rust:`Cell` wraps a value and allows getting or setting the value using +only a shared reference to the :rust:`Cell`. However, it does not allow any +references to the inner value. Since there are no references, borrowing +rules cannot be broken. + +.. code:: rust + + use std::cell::Cell; + + fn main() { + // Note that `cell` is NOT declared as mutable. + let cell = Cell::new(5); + + cell.set(123); + println!("{}", cell.get()); + } + +---------------- +"Cell" Details +---------------- + +- :rust:`Cell` is a simple means to ensure safety: it has a :rust:`set` method + that takes :rust:`&self`. This needs no runtime check, but requires + moving values, which can have its own cost. + +----------------- +"RefCell" +----------------- + +:rust:`RefCell` allows accessing and mutating a wrapped value by providing +alternative types :rust:`Ref` and :rust:`RefMut` that emulate :rust:`&T`/:rust:`&mut T` +without actually being Rust references. + +These types perform dynamic checks using a counter in the :rust:`RefCell` to +prevent existence of a :rust:`RefMut` alongside another :rust:`Ref`/:rust:`RefMut`. + +By implementing :rust:`Deref` (and :rust:`DerefMut` for :rust:`RefMut`), these types +allow calling methods on the inner value without allowing references to +escape. + +.. code:: rust + + use std::cell::RefCell; + + fn main() { + // Note that `cell` is NOT declared as mutable. + let cell = RefCell::new(5); + + { + let mut cell_ref = cell.borrow_mut(); + *cell_ref = 123; + + // This triggers an error at runtime. + // let other = cell.borrow(); + // println!("{}", *other); + } + + println!("{cell:?}"); + } + +.. raw:: html + +------------------- +"RefCell" Details +------------------- + +- :rust:`RefCell` enforces Rust's usual borrowing rules (either multiple + shared references or a single exclusive reference) with a runtime + check. In this case, all borrows are very short and never overlap, so + the checks always succeed. + +- The extra block in the example is to end the borrow created by the + call to :rust:`borrow_mut` before we print the cell. Trying to print a + borrowed :rust:`RefCell` just shows the message :rust:`"{borrowed}"`. + +----------------- +More to Explore +----------------- + +There are also :rust:`OnceCell` and :rust:`OnceLock`, which allow initialization +on first use. Making these useful requires some more knowledge than +students have at this time. + diff --git a/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst index 8c7e8bbba..9a2417914 100644 --- a/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst +++ b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst @@ -1,28 +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: - -:: - - // 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}} +============================= +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: + +:: + + // 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 index cd1906794..e2fc4be60 100644 --- a/courses/comprehensive_rust_training/160_lifetimes.rst +++ b/courses/comprehensive_rust_training/160_lifetimes.rst @@ -1,42 +1,42 @@ -*********** -Lifetimes -*********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +*********** +Lifetimes +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 05c01f338..22fea7ef0 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst @@ -1,66 +1,66 @@ -====================== -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: :rust:`&'a Point`, :rust:`&'document str`. -Lifetimes start with :rust:`'` and :rust:`'a` is a typical default name. Read -:rust:`&'a Point` as "a borrowed :rust:`Point` which is valid for at least the -lifetime :rust:`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. - -.. code:: rust - - #[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:?}"); - } - ---------- -Details ---------- - -In this example, the compiler does not know what lifetime to infer for -:rust:`p3`. Looking inside the function body shows that it can only safely -assume that :rust:`p3` lifetime is the shorter of :rust:`p1` and :rust:`p2`. But -just like types, Rust requires explicit annotations of lifetimes on -function arguments and return values. - -Add :rust:`'a` appropriately to :rust:`left_most`: - -.. code:: rust - - fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { - -This says, "given p1 and p2 which both outlive :rust:`'a`, the return value -lives for at least :rust:`'a`. - -In common cases, lifetimes can be elided, as described on the next -slide. +====================== +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: :rust:`&'a Point`, :rust:`&'document str`. +Lifetimes start with :rust:`'` and :rust:`'a` is a typical default name. Read +:rust:`&'a Point` as "a borrowed :rust:`Point` which is valid for at least the +lifetime :rust:`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. + +.. code:: rust + + #[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:?}"); + } + +--------- +Details +--------- + +In this example, the compiler does not know what lifetime to infer for +:rust:`p3`. Looking inside the function body shows that it can only safely +assume that :rust:`p3` lifetime is the shorter of :rust:`p1` and :rust:`p2`. But +just like types, Rust requires explicit annotations of lifetimes on +function arguments and return values. + +Add :rust:`'a` appropriately to :rust:`left_most`: + +.. code:: rust + + fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { + +This says, "given p1 and p2 which both outlive :rust:`'a`, the return value +lives for at least :rust:`'a`. + +In common cases, lifetimes can be elided, as described on the next +slide. 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 764e527e0..9b7807547 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst @@ -1,73 +1,73 @@ -============================= -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 - 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 - :rust:`self`, that lifetime is given to all un-annotated return values. - -.. code:: rust - - #[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))); - } - ---------- -Details ---------- - -In this example, :rust:`cab_distance` is trivially elided. - -The :rust:`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 - - 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. +============================= +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 - 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 + :rust:`self`, that lifetime is given to all un-annotated return values. + +.. code:: rust + + #[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))); + } + +--------- +Details +--------- + +In this example, :rust:`cab_distance` is trivially elided. + +The :rust:`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 + + 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. diff --git a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst index fda8ba4f5..c0e4b68d6 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst @@ -1,47 +1,47 @@ -============================== -Lifetimes in Data Structures -============================== - ------------------------------- -Lifetimes in Data Structures ------------------------------- - -If a data type stores borrowed data, it must be annotated with a -lifetime: - -.. code:: rust - - #[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:?}"); - } - ---------- -Details ---------- - -- In the above example, the annotation on :rust:`Highlight` enforces that - the data underlying the contained :rust:`&str` lives at least as long as - any instance of :rust:`Highlight` that uses that data. -- If :rust:`text` is consumed before the end of the lifetime of :rust:`fox` (or - :rust:`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. +============================== +Lifetimes in Data Structures +============================== + +------------------------------ +Lifetimes in Data Structures +------------------------------ + +If a data type stores borrowed data, it must be annotated with a +lifetime: + +.. code:: rust + + #[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:?}"); + } + +--------- +Details +--------- + +- In the above example, the annotation on :rust:`Highlight` enforces that + the data underlying the contained :rust:`&str` lives at least as long as + any instance of :rust:`Highlight` that uses that data. +- If :rust:`text` is consumed before the end of the lifetime of :rust:`fox` (or + :rust:`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. diff --git a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst index 180b8368e..f8c8cf75f 100644 --- a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst +++ b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst @@ -1,111 +1,111 @@ -============================ -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 -:rust:`proto` file. In this exercise, we'll encode that information into -:rust:`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., :rust:`2` for the :rust:`id` field of a :rust:`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 -:rust:`unpack_tag` below. - --------- -Varint --------- - -Integers, including the tag, are represented with a variable-length -encoding called VARINT. Luckily, :rust:`parse_varint` is defined for you -below. - ------------- -Wire Types ------------- - -Proto defines several wire types, only two of which are used in this -exercise. - -The :rust:`Varint` wire type contains a single varint, and is used to encode -proto values of type :rust:`int32` such as :rust:`Person.id`. - -The :rust:`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 :rust:`string` such as :rust:`Person.name`. It is also used to -encode proto values containing sub-messages such as :rust:`Person.phones`, -where the payload contains an encoding of the sub-message. - ----------- -Exercise ----------- - -The given code also defines callbacks to handle :rust:`Person` and -:rust:`PhoneNumber` fields, and to parse a message into a series of calls to -those callbacks. - -What remains for you is to implement the :rust:`parse_field` function and -the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. - -:: - - {{#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 }} - ---------- -Details ---------- - -- In this exercise there are various cases where protobuf parsing might - fail, e.g. if you try to parse an :rust:`i32` when there are fewer than 4 - bytes left in the data buffer. In normal Rust code we'd handle this - with the :rust:`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. +============================ +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 +:rust:`proto` file. In this exercise, we'll encode that information into +:rust:`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., :rust:`2` for the :rust:`id` field of a :rust:`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 +:rust:`unpack_tag` below. + +-------- +Varint +-------- + +Integers, including the tag, are represented with a variable-length +encoding called VARINT. Luckily, :rust:`parse_varint` is defined for you +below. + +------------ +Wire Types +------------ + +Proto defines several wire types, only two of which are used in this +exercise. + +The :rust:`Varint` wire type contains a single varint, and is used to encode +proto values of type :rust:`int32` such as :rust:`Person.id`. + +The :rust:`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 :rust:`string` such as :rust:`Person.name`. It is also used to +encode proto values containing sub-messages such as :rust:`Person.phones`, +where the payload contains an encoding of the sub-message. + +---------- +Exercise +---------- + +The given code also defines callbacks to handle :rust:`Person` and +:rust:`PhoneNumber` fields, and to parse a message into a series of calls to +those callbacks. + +What remains for you is to implement the :rust:`parse_field` function and +the :rust:`ProtoMessage` trait for :rust:`Person` and :rust:`PhoneNumber`. + +:: + + {{#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 }} + +--------- +Details +--------- + +- In this exercise there are various cases where protobuf parsing might + fail, e.g. if you try to parse an :rust:`i32` when there are fewer than 4 + bytes left in the data buffer. In normal Rust code we'd handle this + with the :rust:`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. diff --git a/courses/comprehensive_rust_training/170_iterators.rst b/courses/comprehensive_rust_training/170_iterators.rst index b57980e20..b3c922f17 100644 --- a/courses/comprehensive_rust_training/170_iterators.rst +++ b/courses/comprehensive_rust_training/170_iterators.rst @@ -1,44 +1,44 @@ -*********** -Iterators -*********** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +*********** +Iterators +*********** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 59ab12638..1facf9371 100644 --- a/courses/comprehensive_rust_training/170_iterators/01_motivation.rst +++ b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst @@ -1,68 +1,68 @@ -====================== -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 - - 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". - ---------- -Details ---------- - -- This slide provides context for what Rust iterators do under the - hood. We use the (hopefully) familiar construct of a C-style :rust:`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 :rust:`for` loop, but we can express the same - thing with :rust:`while`: - - .. code:: rust - - 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 :rust:`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 - - 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). +====================== +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 + + 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". + +--------- +Details +--------- + +- This slide provides context for what Rust iterators do under the + hood. We use the (hopefully) familiar construct of a C-style :rust:`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 :rust:`for` loop, but we can express the same + thing with :rust:`while`: + + .. code:: rust + + 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 :rust:`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 + + 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). diff --git a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst index 67dfc217e..d2020da18 100644 --- a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst @@ -1,73 +1,73 @@ -==================== -"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 - - 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}"); - } - } - ---------- -Details ---------- - -- The :rust:`SliceIter` example implements the same logic as the C-style - :rust:`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 :rust:`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 :rust:`0..` will keep going until integer overflow occurs. - ------------------ -More to Explore ------------------ - -- The "real" version of :rust:`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 :rust:`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 :rust:`SliceIter` - to allow it to work with any kind of slice (not just :rust:`&[i32]`). +==================== +"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 + + 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}"); + } + } + +--------- +Details +--------- + +- The :rust:`SliceIter` example implements the same logic as the C-style + :rust:`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 :rust:`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 :rust:`0..` will keep going until integer overflow occurs. + +----------------- +More to Explore +----------------- + +- The "real" version of :rust:`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 :rust:`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 :rust:`SliceIter` + to allow it to work with any kind of slice (not just :rust:`&[i32]`). diff --git a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst index bbf3462fe..b57fe1c1b 100644 --- a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst +++ b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst @@ -1,47 +1,47 @@ -============================= -"Iterator" Helper Methods -============================= - ------------------------------ -"Iterator" Helper Methods ------------------------------ - -In addition to the :rust:`next` method that defines how an iterator behaves, -the :rust:`Iterator` trait provides 70+ helper methods that can be used to -build customized iterators. - -.. code:: rust - - 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); - ---------- -Details ---------- - -- The :rust:`Iterator` trait implements many common functional programming - operations over collections (e.g. :rust:`map`, :rust:`filter`, :rust:`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 :rust:`sum` and :rust:`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. +============================= +"Iterator" Helper Methods +============================= + +----------------------------- +"Iterator" Helper Methods +----------------------------- + +In addition to the :rust:`next` method that defines how an iterator behaves, +the :rust:`Iterator` trait provides 70+ helper methods that can be used to +build customized iterators. + +.. code:: rust + + 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); + +--------- +Details +--------- + +- The :rust:`Iterator` trait implements many common functional programming + operations over collections (e.g. :rust:`map`, :rust:`filter`, :rust:`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 :rust:`sum` and :rust:`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. diff --git a/courses/comprehensive_rust_training/170_iterators/04_collect.rst b/courses/comprehensive_rust_training/170_iterators/04_collect.rst index 246a92639..2c7f26747 100644 --- a/courses/comprehensive_rust_training/170_iterators/04_collect.rst +++ b/courses/comprehensive_rust_training/170_iterators/04_collect.rst @@ -1,56 +1,56 @@ -============= -"collect" -============= - -------------- -"collect" -------------- - -The -`collect `__ -method lets you build a collection from an -`Iterator `__. - -.. code:: rust - - 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:?}"); - } - ---------- -Details ---------- - -- Any iterator can be collected in to a :rust:`Vec`, :rust:`VecDeque`, or - :rust:`HashSet`. Iterators that produce key-value pairs (i.e. a - two-element tuple) can also be collected into :rust:`HashMap` and - :rust:`BTreeMap`. - -Show the students the definition for :rust:`collect` in the standard library -docs. There are two ways to specify the generic type :rust:`B` for this -method: - -- With the "turbofish": :rust:`some_iterator.collect::()`, - as shown. The :rust:`_` shorthand used here lets Rust infer the type of - the :rust:`Vec` elements. -- With type inference: - :rust:`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 :rust:`FromIterator` for - :rust:`Vec`, :rust:`HashMap`, etc., there are also more specialized - implementations which let you do cool things like convert an - :rust:`Iterator>` into a :rust:`Result, E>`. -- The reason type annotations are often needed with :rust:`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. +============= +"collect" +============= + +------------- +"collect" +------------- + +The +`collect `__ +method lets you build a collection from an +`Iterator `__. + +.. code:: rust + + 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:?}"); + } + +--------- +Details +--------- + +- Any iterator can be collected in to a :rust:`Vec`, :rust:`VecDeque`, or + :rust:`HashSet`. Iterators that produce key-value pairs (i.e. a + two-element tuple) can also be collected into :rust:`HashMap` and + :rust:`BTreeMap`. + +Show the students the definition for :rust:`collect` in the standard library +docs. There are two ways to specify the generic type :rust:`B` for this +method: + +- With the "turbofish": :rust:`some_iterator.collect::()`, + as shown. The :rust:`_` shorthand used here lets Rust infer the type of + the :rust:`Vec` elements. +- With type inference: + :rust:`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 :rust:`FromIterator` for + :rust:`Vec`, :rust:`HashMap`, etc., there are also more specialized + implementations which let you do cool things like convert an + :rust:`Iterator>` into a :rust:`Result, E>`. +- The reason type annotations are often needed with :rust:`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. diff --git a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst index 67acee511..15838928f 100644 --- a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst +++ b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst @@ -1,92 +1,92 @@ -================== -"IntoIterator" -================== - ------------------- -"IntoIterator" ------------------- - -The :rust:`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 :rust:`for` loop. - -.. code:: rust - - 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}"); - } - } - ---------- -Details ---------- - -- :rust:`IntoIterator` is the trait that makes for loops work. It is - implemented by collection types such as :rust:`Vec` and references to - them such as :rust:`&Vec` and :rust:`&[T]`. Ranges also implement it. This - is why you can iterate over a vector with - :rust:`for i in some_vec { .. }` but :rust:`some_vec.next()` doesn't exist. - -Click through to the docs for :rust:`IntoIterator`. Every implementation of -:rust:`IntoIterator` must declare two types: - -- :rust:`Item`: the type to iterate over, such as :rust:`i8`, -- :rust:`IntoIter`: the :rust:`Iterator` type returned by the :rust:`into_iter` - method. - -Note that :rust:`IntoIter` and :rust:`Item` are linked: the iterator must have -the same :rust:`Item` type, which means that it returns :rust:`Option` - -The example iterates over all combinations of x and y coordinates. - -Try iterating over the grid twice in :rust:`main`. Why does this fail? Note -that :rust:`IntoIterator::into_iter` takes ownership of :rust:`self`. - -Fix this issue by implementing :rust:`IntoIterator` for :rust:`&Grid` and -storing a reference to the :rust:`Grid` in :rust:`GridIter`. - -The same problem can occur for standard library types: -:rust:`for e in some_vector` will take ownership of :rust:`some_vector` and -iterate over owned elements from that vector. Use -:rust:`for e in &some_vector` instead, to iterate over references to -elements of :rust:`some_vector`. +================== +"IntoIterator" +================== + +------------------ +"IntoIterator" +------------------ + +The :rust:`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 :rust:`for` loop. + +.. code:: rust + + 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}"); + } + } + +--------- +Details +--------- + +- :rust:`IntoIterator` is the trait that makes for loops work. It is + implemented by collection types such as :rust:`Vec` and references to + them such as :rust:`&Vec` and :rust:`&[T]`. Ranges also implement it. This + is why you can iterate over a vector with + :rust:`for i in some_vec { .. }` but :rust:`some_vec.next()` doesn't exist. + +Click through to the docs for :rust:`IntoIterator`. Every implementation of +:rust:`IntoIterator` must declare two types: + +- :rust:`Item`: the type to iterate over, such as :rust:`i8`, +- :rust:`IntoIter`: the :rust:`Iterator` type returned by the :rust:`into_iter` + method. + +Note that :rust:`IntoIter` and :rust:`Item` are linked: the iterator must have +the same :rust:`Item` type, which means that it returns :rust:`Option` + +The example iterates over all combinations of x and y coordinates. + +Try iterating over the grid twice in :rust:`main`. Why does this fail? Note +that :rust:`IntoIterator::into_iter` takes ownership of :rust:`self`. + +Fix this issue by implementing :rust:`IntoIterator` for :rust:`&Grid` and +storing a reference to the :rust:`Grid` in :rust:`GridIter`. + +The same problem can occur for standard library types: +:rust:`for e in some_vector` will take ownership of :rust:`some_vector` and +iterate over owned elements from that vector. Use +:rust:`for e in &some_vector` instead, to iterate over references to +elements of :rust:`some_vector`. diff --git a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst index b93cc5777..887994697 100644 --- a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst +++ b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst @@ -1,24 +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 :rust:`collect` the result to -construct the return value. - -:: - - {{#include exercise.rs:offset_differences}} - unimplemented!() - } - - {{#include exercise.rs:unit-tests}} +==================================== +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 :rust:`collect` the result to +construct the return value. + +:: + + {{#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 index 57d38f913..beda480e2 100644 --- a/courses/comprehensive_rust_training/180_modules.rst +++ b/courses/comprehensive_rust_training/180_modules.rst @@ -1,44 +1,44 @@ -********* -Modules -********* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +********* +Modules +********* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index 8b06f08bd..6cef97d19 100644 --- a/courses/comprehensive_rust_training/180_modules/01_modules.rst +++ b/courses/comprehensive_rust_training/180_modules/01_modules.rst @@ -1,41 +1,41 @@ -========= -Modules -========= - ---------- -Modules ---------- - -We have seen how :rust:`impl` blocks let us namespace functions to a type. - -Similarly, :rust:`mod` lets us namespace types and functions: - -.. code:: rust - - 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(); - } - ---------- -Details ---------- - -- Packages provide functionality and include a :rust:`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. +========= +Modules +========= + +--------- +Modules +--------- + +We have seen how :rust:`impl` blocks let us namespace functions to a type. + +Similarly, :rust:`mod` lets us namespace types and functions: + +.. code:: rust + + 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(); + } + +--------- +Details +--------- + +- Packages provide functionality and include a :rust:`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. diff --git a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst index 8f990b702..f0627aabf 100644 --- a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst +++ b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst @@ -1,80 +1,80 @@ -====================== -Filesystem Hierarchy -====================== - ----------------------- -Filesystem Hierarchy ----------------------- - -Omitting the module content will tell Rust to look for it in another -file: - -.. code:: rust - - mod garden; - -This tells Rust that the :rust:`garden` module content is found at -:rust:`src/garden.rs`. Similarly, a :rust:`garden::vegetables` module can be -found at :rust:`src/garden/vegetables.rs`. - -The :rust:`crate` root is in: - -- :rust:`src/lib.rs` (for a library crate) -- :rust:`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 - in this case, a -module. - -.. code:: rust - - //! 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!() - } - ---------- -Details ---------- - -- Before Rust 2018, modules needed to be located at :rust:`module/mod.rs` - instead of :rust:`module.rs`, and this is still a working alternative for - editions after 2018. - -- The main reason to introduce :rust:`filename.rs` as alternative to - :rust:`filename/mod.rs` was because many files named :rust:`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/ - |-- main.rs - |-- top_module.rs - |-- top_module/ - |-- sub_module.rs - -- The place rust will look for modules can be changed with a compiler - directive: - - .. code:: rust - - #[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 :rust:`some_module_test.rs`, similar to the - convention in Go. +====================== +Filesystem Hierarchy +====================== + +---------------------- +Filesystem Hierarchy +---------------------- + +Omitting the module content will tell Rust to look for it in another +file: + +.. code:: rust + + mod garden; + +This tells Rust that the :rust:`garden` module content is found at +:rust:`src/garden.rs`. Similarly, a :rust:`garden::vegetables` module can be +found at :rust:`src/garden/vegetables.rs`. + +The :rust:`crate` root is in: + +- :rust:`src/lib.rs` (for a library crate) +- :rust:`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 - in this case, a +module. + +.. code:: rust + + //! 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!() + } + +--------- +Details +--------- + +- Before Rust 2018, modules needed to be located at :rust:`module/mod.rs` + instead of :rust:`module.rs`, and this is still a working alternative for + editions after 2018. + +- The main reason to introduce :rust:`filename.rs` as alternative to + :rust:`filename/mod.rs` was because many files named :rust:`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/ + |-- main.rs + |-- top_module.rs + |-- top_module/ + |-- sub_module.rs + +- The place rust will look for modules can be changed with a compiler + directive: + + .. code:: rust + + #[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 :rust:`some_module_test.rs`, similar to the + convention in Go. diff --git a/courses/comprehensive_rust_training/180_modules/03_visibility.rst b/courses/comprehensive_rust_training/180_modules/03_visibility.rst index f500803cc..3d565c962 100644 --- a/courses/comprehensive_rust_training/180_modules/03_visibility.rst +++ b/courses/comprehensive_rust_training/180_modules/03_visibility.rst @@ -1,57 +1,57 @@ -============ -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 :rust:`foo`, it's visible - in all the descendants of :rust:`foo`. - -.. code:: rust - - 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(); - } - ---------- -Details ---------- - -- Use the :rust:`pub` keyword to make modules public. - -Additionally, there are advanced :rust:`pub(...)` specifiers to restrict the -scope of public visibility. - -- See the - `Rust Reference `__. -- Configuring :rust:`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). +============ +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 :rust:`foo`, it's visible + in all the descendants of :rust:`foo`. + +.. code:: rust + + 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(); + } + +--------- +Details +--------- + +- Use the :rust:`pub` keyword to make modules public. + +Additionally, there are advanced :rust:`pub(...)` specifiers to restrict the +scope of public visibility. + +- See the + `Rust Reference `__. +- Configuring :rust:`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). diff --git a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst index 52d2d6073..6fbfd3821 100644 --- a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst +++ b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst @@ -1,85 +1,85 @@ -============================== -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 - - 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); - } - ---------- -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 :rust:`is_big` field is fully controlled by :rust:`Foo`, - allowing :rust:`Foo` to control how it's initialized and enforce any - invariants it needs to (e.g. that :rust:`is_big` is only :rust:`true` if - :rust:`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 :rust:`#[doc_hidden]` and :rust:`#[non_exhaustive]` - and show how they're used to limit what can be done with an enum. - -- Module privacy still applies when there are :rust:`impl` blocks in other - modules - `(example in the playground) `__. +============================== +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 + + 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); + } + +--------- +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 :rust:`is_big` field is fully controlled by :rust:`Foo`, + allowing :rust:`Foo` to control how it's initialized and enforce any + invariants it needs to (e.g. that :rust:`is_big` is only :rust:`true` if + :rust:`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 :rust:`#[doc_hidden]` and :rust:`#[non_exhaustive]` + and show how they're used to limit what can be done with an enum. + +- Module privacy still applies when there are :rust:`impl` blocks in other + modules + `(example in the playground) `__. diff --git a/courses/comprehensive_rust_training/180_modules/05_paths.rst b/courses/comprehensive_rust_training/180_modules/05_paths.rst index cba432b6c..766be8c14 100644 --- a/courses/comprehensive_rust_training/180_modules/05_paths.rst +++ b/courses/comprehensive_rust_training/180_modules/05_paths.rst @@ -1,58 +1,58 @@ -================== -use, super, self -================== - ------------------- -use, super, self ------------------- - -A module can bring symbols from another module into scope with :rust:`use`. -You will typically see something like this at the top of each module: - -.. code:: rust - - use std::collections::HashSet; - use std::process::abort; - -------- -Paths -------- - -Paths are resolved as follows: - -1. As a relative path: - - - :rust:`foo` or :rust:`self::foo` refers to :rust:`foo` in the current module, - - :rust:`super::foo` refers to :rust:`foo` in the parent module. - -2. As an absolute path: - - - :rust:`crate::foo` refers to :rust:`foo` in the root of the current crate, - - :rust:`bar::foo` refers to :rust:`foo` in the :rust:`bar` crate. - ---------- -Details ---------- - -- It is common to "re-export" symbols at a shorter path. For example, - the top-level :rust:`lib.rs` in a crate might have - - .. code:: rust - - mod storage; - - pub use storage::disk::DiskStorage; - pub use storage::network::NetworkStorage; - - making :rust:`DiskStorage` and :rust:`NetworkStorage` available to other - crates with a convenient, short path. - -- For the most part, only items that appear in a module need to be - :rust:`use`. 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 :rust:`read_to_string` method on a type - implementing the :rust:`Read` trait, you need to :rust:`use std::io::Read`. - -- The :rust:`use` statement can have a wildcard: :rust:`use std::io::*`. This - is discouraged because it is not clear which items are imported, and - those might change over time. +================== +use, super, self +================== + +------------------ +use, super, self +------------------ + +A module can bring symbols from another module into scope with :rust:`use`. +You will typically see something like this at the top of each module: + +.. code:: rust + + use std::collections::HashSet; + use std::process::abort; + +------- +Paths +------- + +Paths are resolved as follows: + +1. As a relative path: + + - :rust:`foo` or :rust:`self::foo` refers to :rust:`foo` in the current module, + - :rust:`super::foo` refers to :rust:`foo` in the parent module. + +2. As an absolute path: + + - :rust:`crate::foo` refers to :rust:`foo` in the root of the current crate, + - :rust:`bar::foo` refers to :rust:`foo` in the :rust:`bar` crate. + +--------- +Details +--------- + +- It is common to "re-export" symbols at a shorter path. For example, + the top-level :rust:`lib.rs` in a crate might have + + .. code:: rust + + mod storage; + + pub use storage::disk::DiskStorage; + pub use storage::network::NetworkStorage; + + making :rust:`DiskStorage` and :rust:`NetworkStorage` available to other + crates with a convenient, short path. + +- For the most part, only items that appear in a module need to be + :rust:`use`. 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 :rust:`read_to_string` method on a type + implementing the :rust:`Read` trait, you need to :rust:`use std::io::Read`. + +- The :rust:`use` statement can have a wildcard: :rust:`use std::io::*`. This + is discouraged because it is not clear which items are imported, and + those might change over time. diff --git a/courses/comprehensive_rust_training/180_modules/06_exercise.rst b/courses/comprehensive_rust_training/180_modules/06_exercise.rst index b9844d937..627e41f1c 100644 --- a/courses/comprehensive_rust_training/180_modules/06_exercise.rst +++ b/courses/comprehensive_rust_training/180_modules/06_exercise.rst @@ -1,48 +1,48 @@ -===================================== -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 :rust:`Widget` trait and a few -implementations of that trait, as well as a :rust:`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 :rust:`src/main.rs` to add :rust:`mod` statements, and add -additional files in the :rust:`src` directory. - --------- -Source --------- - -Here's the single-module implementation of the GUI library: - -:: - - {{#include exercise.rs:single-module}} - ---------- -Details ---------- - -Encourage students to divide the code in a way that feels natural for -them, and get accustomed to the required :rust:`mod`, :rust:`use`, and :rust:`pub` -declarations. Afterward, discuss what organizations are most idiomatic. +===================================== +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 :rust:`Widget` trait and a few +implementations of that trait, as well as a :rust:`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 :rust:`src/main.rs` to add :rust:`mod` statements, and add +additional files in the :rust:`src` directory. + +-------- +Source +-------- + +Here's the single-module implementation of the GUI library: + +:: + + {{#include exercise.rs:single-module}} + +--------- +Details +--------- + +Encourage students to divide the code in a way that feels natural for +them, and get accustomed to the required :rust:`mod`, :rust:`use`, and :rust:`pub` +declarations. Afterward, discuss what organizations are most idiomatic. diff --git a/courses/comprehensive_rust_training/190_testing.rst b/courses/comprehensive_rust_training/190_testing.rst index 888079c78..68b64b594 100644 --- a/courses/comprehensive_rust_training/190_testing.rst +++ b/courses/comprehensive_rust_training/190_testing.rst @@ -1,42 +1,42 @@ -********* -Testing -********* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +********* +Testing +********* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index ac36eccc9..742279e56 100644 --- a/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst +++ b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst @@ -1,51 +1,51 @@ -============ -Unit Tests -============ - ------------- -Unit Tests ------------- - -Rust and Cargo come with a simple unit test framework. Tests are marked -with :rust:`#[test]`. Unit tests are often put in a nested :rust:`tests` module, -using :rust:`#[cfg(test)]` to conditionally compile them only when building -tests. - -.. code:: rust - - 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 :rust:`#[cfg(test)]` attribute is only active when you run - :rust:`cargo test`. - ---------- -Details ---------- - -Run the tests in the playground in order to show their results. +============ +Unit Tests +============ + +------------ +Unit Tests +------------ + +Rust and Cargo come with a simple unit test framework. Tests are marked +with :rust:`#[test]`. Unit tests are often put in a nested :rust:`tests` module, +using :rust:`#[cfg(test)]` to conditionally compile them only when building +tests. + +.. code:: rust + + 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 :rust:`#[cfg(test)]` attribute is only active when you run + :rust:`cargo test`. + +--------- +Details +--------- + +Run the tests in the playground in order to show their results. diff --git a/courses/comprehensive_rust_training/190_testing/02_other.rst b/courses/comprehensive_rust_training/190_testing/02_other.rst index 177917e23..685a8cc73 100644 --- a/courses/comprehensive_rust_training/190_testing/02_other.rst +++ b/courses/comprehensive_rust_training/190_testing/02_other.rst @@ -1,53 +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 :rust:`.rs` file under :rust:`tests/`: - -.. code:: rust - - // 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. - /// - /// :rust:` - /// # use playground::shorten_string; - /// assert_eq!(shorten_string("Hello World", 5), "Hello"); - /// assert_eq!(shorten_string("Hello World", 20), "Hello World"); - /// :rust:` - pub fn shorten_string(s: &str, length: usize) -> &str { - &s[..std::cmp::min(length, s.len())] - } - -- Code blocks in :rust:`///` comments are automatically seen as Rust code. -- The code will be compiled and executed as part of :rust:`cargo test`. -- Adding :rust:`#` in the code will hide it from the docs, but will still - compile/run it. -- Test the above code on the - `Rust Playground `__. +====================== +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 :rust:`.rs` file under :rust:`tests/`: + +.. code:: rust + + // 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. + /// + /// :rust:` + /// # use playground::shorten_string; + /// assert_eq!(shorten_string("Hello World", 5), "Hello"); + /// assert_eq!(shorten_string("Hello World", 20), "Hello World"); + /// :rust:` + pub fn shorten_string(s: &str, length: usize) -> &str { + &s[..std::cmp::min(length, s.len())] + } + +- Code blocks in :rust:`///` comments are automatically seen as Rust code. +- The code will be compiled and executed as part of :rust:`cargo test`. +- Adding :rust:`#` 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 index 443015390..6955b590d 100644 --- a/courses/comprehensive_rust_training/190_testing/03_lints.rst +++ b/courses/comprehensive_rust_training/190_testing/03_lints.rst @@ -1,34 +1,34 @@ -=========================== -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 - - #[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); - } - ---------- -Details ---------- - -There are compiler lints visible here, but not clippy lints. Run -:rust:`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 :rust:`help: ...` can be fixed with -:rust:`cargo fix` or via your editor. +=========================== +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 + + #[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); + } + +--------- +Details +--------- + +There are compiler lints visible here, but not clippy lints. Run +:rust:`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 :rust:`help: ...` can be fixed with +:rust:`cargo fix` or via your editor. diff --git a/courses/comprehensive_rust_training/190_testing/04_exercise.rst b/courses/comprehensive_rust_training/190_testing/04_exercise.rst index fac188323..582ec127b 100644 --- a/courses/comprehensive_rust_training/190_testing/04_exercise.rst +++ b/courses/comprehensive_rust_training/190_testing/04_exercise.rst @@ -1,39 +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 :rust:`1234`, we double :rust:`3` and :rust:`1`. For the number :rust:`98765`, - we double :rust:`6` and :rust:`8`. - -- After doubling a digit, sum the digits if the result is greater than - 9. So doubling :rust:`7` becomes :rust:`14` which becomes :rust:`1 + 4 = 5`. - -- Sum all the undoubled and doubled digits. - -- The credit card number is valid if the sum ends with :rust:`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. - -:: - - {{#include exercise.rs:luhn}} - - {{#include exercise.rs:unit-tests}} - } +========================== +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 :rust:`1234`, we double :rust:`3` and :rust:`1`. For the number :rust:`98765`, + we double :rust:`6` and :rust:`8`. + +- After doubling a digit, sum the digits if the result is greater than + 9. So doubling :rust:`7` becomes :rust:`14` which becomes :rust:`1 + 4 = 5`. + +- Sum all the undoubled and doubled digits. + +- The credit card number is valid if the sum ends with :rust:`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. + +:: + + {{#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 index d3a871ca9..e8a88c20b 100644 --- a/courses/comprehensive_rust_training/200_error_handling.rst +++ b/courses/comprehensive_rust_training/200_error_handling.rst @@ -1,46 +1,46 @@ -**************** -Error Handling -**************** - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +**************** +Error Handling +**************** + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index a79e122ea..a1d0dc7d1 100644 --- a/courses/comprehensive_rust_training/200_error_handling/01_panics.rst +++ b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst @@ -1,58 +1,58 @@ -======== -Panics -======== - --------- -Panics --------- - -Rust handles fatal errors with a "panic". - -Rust will trigger a panic if a fatal error happens at runtime: - -.. code:: rust - - 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 :rust:`assert!`) panic on failure - - Purpose-specific panics can use the :rust:`panic!` macro. - -- A panic will "unwind" the stack, dropping values just as if the - functions had returned. -- Use non-panicking APIs (such as :rust:`Vec::get`) if crashing is not - acceptable. - ---------- -Details ---------- - -By default, a panic will cause the stack to unwind. The unwinding can be -caught: - -.. code:: rust - - 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 - :rust:`catch_unwind`! -- This can be useful in servers which should keep running even if a - single request crashes. -- This does not work if :rust:`panic = 'abort'` is set in your - :rust:`Cargo.toml`. +======== +Panics +======== + +-------- +Panics +-------- + +Rust handles fatal errors with a "panic". + +Rust will trigger a panic if a fatal error happens at runtime: + +.. code:: rust + + 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 :rust:`assert!`) panic on failure + - Purpose-specific panics can use the :rust:`panic!` macro. + +- A panic will "unwind" the stack, dropping values just as if the + functions had returned. +- Use non-panicking APIs (such as :rust:`Vec::get`) if crashing is not + acceptable. + +--------- +Details +--------- + +By default, a panic will cause the stack to unwind. The unwinding can be +caught: + +.. code:: rust + + 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 + :rust:`catch_unwind`! +- This can be useful in servers which should keep running even if a + single request crashes. +- This does not work if :rust:`panic = 'abort'` is set in your + :rust:`Cargo.toml`. 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 c417097b9..6e85049e1 100644 --- a/courses/comprehensive_rust_training/200_error_handling/02_result.rst +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -1,87 +1,87 @@ -============ -"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 - - 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}"); - } - } - } - ---------- -Details ---------- - -- :rust:`Result` has two variants: :rust:`Ok` which contains the success value, - and :rust:`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 :rust:`Result` - value. - -- Like with :rust:`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 :rust:`Result` to check which variant you - have. Methods like :rust:`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 ------------------ - -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 :rust:`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. +============ +"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 + + 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}"); + } + } + } + +--------- +Details +--------- + +- :rust:`Result` has two variants: :rust:`Ok` which contains the success value, + and :rust:`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 :rust:`Result` + value. + +- Like with :rust:`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 :rust:`Result` to check which variant you + have. Methods like :rust:`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 +----------------- + +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 :rust:`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. diff --git a/courses/comprehensive_rust_training/200_error_handling/03_try.rst b/courses/comprehensive_rust_training/200_error_handling/03_try.rst index 46678e563..615c73710 100644 --- a/courses/comprehensive_rust_training/200_error_handling/03_try.rst +++ b/courses/comprehensive_rust_training/200_error_handling/03_try.rst @@ -1,69 +1,69 @@ -============== -Try Operator -============== - --------------- -Try Operator --------------- - -Runtime errors like connection-refused or file-not-found are handled -with the :rust:`Result` type, but matching this type on every call can be -cumbersome. The try-operator :rust:`?` is used to return errors to the -caller. It lets you turn the common - -.. code:: rust - - match some_expression { - Ok(value) => value, - Err(err) => return Err(err), - } - -into the much simpler - -.. code:: rust - - some_expression? - -We can use this to simplify our error handling code: - -.. code:: rust - - 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:?}"); - } - ---------- -Details ---------- - -Simplify the :rust:`read_username` function to use :rust:`?`. - -Key points: - -- The :rust:`username` variable can be either :rust:`Ok(string)` or - :rust:`Err(error)`. -- Use the :rust:`fs::write` call to test out the different scenarios: no - file, empty file, file with username. -- Note that :rust:`main` can return a :rust:`Result<(), E>` as long as it - implements :rust:`std::process::Termination`. In practice, this means - that :rust:`E` implements :rust:`Debug`. The executable will print the - :rust:`Err` variant and return a nonzero exit status on error. +============== +Try Operator +============== + +-------------- +Try Operator +-------------- + +Runtime errors like connection-refused or file-not-found are handled +with the :rust:`Result` type, but matching this type on every call can be +cumbersome. The try-operator :rust:`?` is used to return errors to the +caller. It lets you turn the common + +.. code:: rust + + match some_expression { + Ok(value) => value, + Err(err) => return Err(err), + } + +into the much simpler + +.. code:: rust + + some_expression? + +We can use this to simplify our error handling code: + +.. code:: rust + + 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:?}"); + } + +--------- +Details +--------- + +Simplify the :rust:`read_username` function to use :rust:`?`. + +Key points: + +- The :rust:`username` variable can be either :rust:`Ok(string)` or + :rust:`Err(error)`. +- Use the :rust:`fs::write` call to test out the different scenarios: no + file, empty file, file with username. +- Note that :rust:`main` can return a :rust:`Result<(), E>` as long as it + implements :rust:`std::process::Termination`. In practice, this means + that :rust:`E` implements :rust:`Debug`. The executable will print the + :rust:`Err` variant and return a nonzero exit status on error. 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 index 667b8793a..cdba7b705 100644 --- a/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst +++ b/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst @@ -1,98 +1,98 @@ -================= -Try Conversions -================= - ------------------ -Try Conversions ------------------ - -The effective expansion of :rust:`?` is a little more complicated than -previously indicated: - -.. code:: rust - - expression? - -works the same as - -.. code:: rust - - match expression { - Ok(value) => value, - Err(err) => return Err(From::from(err)), - } - -The :rust:`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 - - 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:?}"); - } - ---------- -Details ---------- - -The :rust:`?` operator must return a value compatible with the return type -of the function. For :rust:`Result`, it means that the error types have to -be compatible. A function that returns :rust:`Result` can -only use :rust:`?` on a value of type :rust:`Result` if -:rust:`ErrorOuter` and :rust:`ErrorInner` are the same type or if :rust:`ErrorOuter` -implements :rust:`From`. - -A common alternative to a :rust:`From` implementation is -:rust:`Result::map_err`, especially when the conversion only happens in one -place. - -There is no compatibility requirement for :rust:`Option`. A function -returning :rust:`Option` can use the :rust:`?` operator on :rust:`Option` for -arbitrary :rust:`T` and :rust:`U` types. - -A function that returns :rust:`Result` cannot use :rust:`?` on :rust:`Option` and -vice versa. However, :rust:`Option::ok_or` converts :rust:`Option` to :rust:`Result` -whereas :rust:`Result::ok` turns :rust:`Result` into :rust:`Option`. +================= +Try Conversions +================= + +----------------- +Try Conversions +----------------- + +The effective expansion of :rust:`?` is a little more complicated than +previously indicated: + +.. code:: rust + + expression? + +works the same as + +.. code:: rust + + match expression { + Ok(value) => value, + Err(err) => return Err(From::from(err)), + } + +The :rust:`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 + + 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:?}"); + } + +--------- +Details +--------- + +The :rust:`?` operator must return a value compatible with the return type +of the function. For :rust:`Result`, it means that the error types have to +be compatible. A function that returns :rust:`Result` can +only use :rust:`?` on a value of type :rust:`Result` if +:rust:`ErrorOuter` and :rust:`ErrorInner` are the same type or if :rust:`ErrorOuter` +implements :rust:`From`. + +A common alternative to a :rust:`From` implementation is +:rust:`Result::map_err`, especially when the conversion only happens in one +place. + +There is no compatibility requirement for :rust:`Option`. A function +returning :rust:`Option` can use the :rust:`?` operator on :rust:`Option` for +arbitrary :rust:`T` and :rust:`U` types. + +A function that returns :rust:`Result` cannot use :rust:`?` on :rust:`Option` and +vice versa. However, :rust:`Option::ok_or` converts :rust:`Option` to :rust:`Result` +whereas :rust:`Result::ok` turns :rust:`Result` into :rust:`Option`. diff --git a/courses/comprehensive_rust_training/200_error_handling/05_error.rst b/courses/comprehensive_rust_training/200_error_handling/05_error.rst index 06aa97a50..7efb00f66 100644 --- a/courses/comprehensive_rust_training/200_error_handling/05_error.rst +++ b/courses/comprehensive_rust_training/200_error_handling/05_error.rst @@ -1,49 +1,49 @@ -===================== -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 -:rust:`std::error::Error` trait makes it easy to create a trait object that -can contain any error. - -.. code:: rust - - 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}"), - } - } - ---------- -Details ---------- - -The :rust:`read_count` function can return :rust:`std::io::Error` (from file -operations) or :rust:`std::num::ParseIntError` (from :rust:`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 :rust:`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 :rust:`std::error::Error` trait when defining a -custom error type so it can be boxed. +===================== +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 +:rust:`std::error::Error` trait makes it easy to create a trait object that +can contain any error. + +.. code:: rust + + 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}"), + } + } + +--------- +Details +--------- + +The :rust:`read_count` function can return :rust:`std::io::Error` (from file +operations) or :rust:`std::num::ParseIntError` (from :rust:`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 :rust:`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 :rust:`std::error::Error` trait when defining a +custom error type so it can be boxed. 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 b72d5b28e..6e5212608 100644 --- a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -1,55 +1,55 @@ -=============== -"thiserror" -=============== - ---------------- -"thiserror" ---------------- - -The `thiserror `__ crate provides macros -to help avoid boilerplate when defining error types. It provides derive -macros that assist in implementing :rust:`From`, :rust:`Display`, and the -:rust:`Error` trait. - -.. code:: rust - - 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:?}"), - } - } - ---------- -Details ---------- - -- The :rust:`Error` derive macro is provided by :rust:`thiserror`, and has lots - of useful attributes to help define error types in a compact way. -- The message from :rust:`#[error]` is used to derive the :rust:`Display` - trait. -- Note that the (:rust:`thiserror::`) :rust:`Error` derive macro, while it has - the effect of implementing the (:rust:`std::error::`) :rust:`Error` trait, - is not the same this; traits and macros do not share a namespace. +=============== +"thiserror" +=============== + +--------------- +"thiserror" +--------------- + +The `thiserror `__ crate provides macros +to help avoid boilerplate when defining error types. It provides derive +macros that assist in implementing :rust:`From`, :rust:`Display`, and the +:rust:`Error` trait. + +.. code:: rust + + 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:?}"), + } + } + +--------- +Details +--------- + +- The :rust:`Error` derive macro is provided by :rust:`thiserror`, and has lots + of useful attributes to help define error types in a compact way. +- The message from :rust:`#[error]` is used to derive the :rust:`Display` + trait. +- Note that the (:rust:`thiserror::`) :rust:`Error` derive macro, while it has + the effect of implementing the (:rust:`std::error::`) :rust:`Error` trait, + is not the same this; traits and macros do not share a namespace. 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 40939c94e..d4e1c8f26 100644 --- a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -1,74 +1,74 @@ -============ -"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 - - 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:?}"), - } - } - ---------- -Details ---------- - -- :rust:`anyhow::Error` is essentially a wrapper around :rust:`Box`. - As such it's again generally not a good choice for the public API of - a library, but is widely used in applications. -- :rust:`anyhow::Result` is a type alias for - :rust:`Result`. -- Functionality provided by :rust:`anyhow::Error` may be familiar to Go - developers, as it provides similar behavior to the Go :rust:`error` type - and :rust:`Result` is much like a Go :rust:`(T, error)` - (with the convention that only one element of the pair is - meaningful). -- :rust:`anyhow::Context` is a trait implemented for the standard - :rust:`Result` and :rust:`Option` types. :rust:`use anyhow::Context` is necessary - to enable :rust:`.context()` and :rust:`.with_context()` on those types. - ------------------ -More to Explore ------------------ - -- :rust:`anyhow::Error` has support for downcasting, much like - :rust:`std::any::Any`; the specific error type stored inside can be - extracted for examination if desired with - `Error::downcast `__. +============ +"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 + + 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:?}"), + } + } + +--------- +Details +--------- + +- :rust:`anyhow::Error` is essentially a wrapper around :rust:`Box`. + As such it's again generally not a good choice for the public API of + a library, but is widely used in applications. +- :rust:`anyhow::Result` is a type alias for + :rust:`Result`. +- Functionality provided by :rust:`anyhow::Error` may be familiar to Go + developers, as it provides similar behavior to the Go :rust:`error` type + and :rust:`Result` is much like a Go :rust:`(T, error)` + (with the convention that only one element of the pair is + meaningful). +- :rust:`anyhow::Context` is a trait implemented for the standard + :rust:`Result` and :rust:`Option` types. :rust:`use anyhow::Context` is necessary + to enable :rust:`.context()` and :rust:`.with_context()` on those types. + +----------------- +More to Explore +----------------- + +- :rust:`anyhow::Error` has support for downcasting, much like + :rust:`std::any::Any`; the specific error type stored inside can be + extracted for examination if desired with + `Error::downcast `__. diff --git a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst index 95540d07e..aa20c3f2c 100644 --- a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst +++ b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst @@ -1,27 +1,27 @@ -================================= -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 :rust:`eval` to instead use idiomatic error -handling to handle this error case and return an error when it occurs. -We provide a simple :rust:`DivideByZeroError` type to use as the error type -for :rust:`eval`. - -:: - - #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. +================================= +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 :rust:`eval` to instead use idiomatic error +handling to handle this error case and return an error when it occurs. +We provide a simple :rust:`DivideByZeroError` type to use as the error type +for :rust:`eval`. + +:: + + #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. diff --git a/courses/comprehensive_rust_training/210_unsafe_rust.rst b/courses/comprehensive_rust_training/210_unsafe_rust.rst index e14d7e0f2..5dcfeb734 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust.rst @@ -1,45 +1,45 @@ -************* -Unsafe Rust -************* - -.. container:: PRELUDE BEGIN - -.. container:: PRELUDE ROLES - -.. role:: ada(code) - :language: Ada - -.. role:: C(code) - :language: C - -.. role:: cpp(code) - :language: C++ - -.. role:: rust(code) - :language: Rust - -.. 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 +************* +Unsafe Rust +************* + +.. container:: PRELUDE BEGIN + +.. container:: PRELUDE ROLES + +.. role:: ada(code) + :language: Ada + +.. role:: C(code) + :language: C + +.. role:: cpp(code) + :language: C++ + +.. role:: rust(code) + :language: Rust + +.. 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 index e4723f218..4c8b58885 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst @@ -1,41 +1,41 @@ -============= -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 :rust:`union` fields. -- Call :rust:`unsafe` functions, including :rust:`extern` functions. -- Implement :rust:`unsafe` traits. - -We will briefly cover unsafe capabilities next. For full details, please -see -`Chapter 19.1 in the Rust Book `__ and -the `Rustonomicon `__. - ---------- -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. +============= +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 :rust:`union` fields. +- Call :rust:`unsafe` functions, including :rust:`extern` functions. +- Implement :rust:`unsafe` traits. + +We will briefly cover unsafe capabilities next. For full details, please +see +`Chapter 19.1 in the Rust Book `__ and +the `Rustonomicon `__. + +--------- +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. diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst index 9ff9db122..5958a9f45 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst @@ -1,65 +1,65 @@ -============================ -Dereferencing Raw Pointers -============================ - ----------------------------- -Dereferencing Raw Pointers ----------------------------- - -Creating pointers is safe, but dereferencing them requires :rust:`unsafe`: - -.. code:: rust - - 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); - */ - } - ---------- -Details ---------- - -It is good practice (and required by the Android Rust style guide) to -write a comment for each :rust:`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: -:rust:`*r1` has the :rust:`'static` lifetime, so :rust:`r3` has type -:rust:`&'static String`, and thus outlives :rust:`s`. Creating a reference from -a pointer requires *great care*. +============================ +Dereferencing Raw Pointers +============================ + +---------------------------- +Dereferencing Raw Pointers +---------------------------- + +Creating pointers is safe, but dereferencing them requires :rust:`unsafe`: + +.. code:: rust + + 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); + */ + } + +--------- +Details +--------- + +It is good practice (and required by the Android Rust style guide) to +write a comment for each :rust:`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: +:rust:`*r1` has the :rust:`'static` lifetime, so :rust:`r3` has type +:rust:`&'static String`, and thus outlives :rust:`s`. Creating a reference from +a pointer requires *great care*. 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 index 21000afe9..5b7f2376f 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst @@ -1,53 +1,53 @@ -========================== -Mutable Static Variables -========================== - --------------------------- -Mutable Static Variables --------------------------- - -It is safe to read an immutable static variable: - -.. code:: rust - - 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 - - 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}"); - } - } - ---------- -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 :rust:`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 :rust:`no_std` code, such as - implementing a heap allocator or working with some C APIs. +========================== +Mutable Static Variables +========================== + +-------------------------- +Mutable Static Variables +-------------------------- + +It is safe to read an immutable static variable: + +.. code:: rust + + 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 + + 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}"); + } + } + +--------- +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 :rust:`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 :rust:`no_std` code, such as + implementing a heap allocator or working with some C APIs. 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 7cce7f320..b18d3de2a 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst @@ -1,36 +1,36 @@ -======== -Unions -======== - --------- -Unions --------- - -Unions are like enums, but you need to track the active field yourself: - -.. code:: rust - - #[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! - } - ---------- -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. +======== +Unions +======== + +-------- +Unions +-------- + +Unions are like enums, but you need to track the active field yourself: + +.. code:: rust + + #[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! + } + +--------- +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. 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 7a3d11469..953c045b4 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 @@ -1,106 +1,106 @@ -================== -Unsafe Functions -================== - --------------------------- -Calling Unsafe Functions --------------------------- - -A function or method can be marked :rust:`unsafe` if it has extra -preconditions you must uphold to avoid undefined behaviour: - -.. code:: rust - - 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 :rust:`unsafe` if they require particular -conditions to avoid undefined behaviour. - -.. code:: rust - - /// 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); - } - --------------------------- -Calling Unsafe Functions --------------------------- - -:rust:`get_unchecked`, like most :rust:`_unchecked` functions, is unsafe, -because it can create UB if the range is incorrect. :rust:`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 :rust:`"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 :rust:`swap` function - it can be -done safely with references. - -Note that unsafe code is allowed within an unsafe function without an -:rust:`unsafe` block. We can prohibit this with -:rust:`#[deny(unsafe_op_in_unsafe_fn)]`. Try adding it and see what happens. -This will likely change in a future Rust edition. +================== +Unsafe Functions +================== + +-------------------------- +Calling Unsafe Functions +-------------------------- + +A function or method can be marked :rust:`unsafe` if it has extra +preconditions you must uphold to avoid undefined behaviour: + +.. code:: rust + + 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 :rust:`unsafe` if they require particular +conditions to avoid undefined behaviour. + +.. code:: rust + + /// 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); + } + +-------------------------- +Calling Unsafe Functions +-------------------------- + +:rust:`get_unchecked`, like most :rust:`_unchecked` functions, is unsafe, +because it can create UB if the range is incorrect. :rust:`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 :rust:`"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 :rust:`swap` function - it can be +done safely with references. + +Note that unsafe code is allowed within an unsafe function without an +:rust:`unsafe` block. We can prohibit this with +:rust:`#[deny(unsafe_op_in_unsafe_fn)]`. Try adding it and see what happens. +This will likely change in a future Rust edition. 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 9a9fbd0ef..75e90d129 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 @@ -1,44 +1,44 @@ -============================ -Implementing Unsafe Traits -============================ - ----------------------------- -Implementing Unsafe Traits ----------------------------- - -Like with functions, you can mark a trait as :rust:`unsafe` if the -implementation must guarantee particular conditions to avoid undefined -behaviour. - -For example, the :rust:`zerocopy` crate has an unsafe trait that looks -`something like this `__: - -.. code:: rust - - 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 {} - ---------- -Details ---------- - -There should be a :rust:`# Safety` section on the Rustdoc for the trait -explaining the requirements for the trait to be safely implemented. - -The actual safety section for :rust:`IntoBytes` is rather longer and more -complicated. - -The built-in :rust:`Send` and :rust:`Sync` traits are unsafe. +============================ +Implementing Unsafe Traits +============================ + +---------------------------- +Implementing Unsafe Traits +---------------------------- + +Like with functions, you can mark a trait as :rust:`unsafe` if the +implementation must guarantee particular conditions to avoid undefined +behaviour. + +For example, the :rust:`zerocopy` crate has an unsafe trait that looks +`something like this `__: + +.. code:: rust + + 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 {} + +--------- +Details +--------- + +There should be a :rust:`# Safety` section on the Rustdoc for the trait +explaining the requirements for the trait to be safely implemented. + +The actual safety section for :rust:`IntoBytes` is rather longer and more +complicated. + +The built-in :rust:`Send` and :rust:`Sync` traits are unsafe. 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 5e3e05349..ab42b5405 100644 --- a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst +++ b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst @@ -1,85 +1,85 @@ -================== -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 -:rust:`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: - -.. 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: - -- :rust:`&str` to :rust:`CString`: you need to allocate space for a trailing - :rust:`\0` character, -- :rust:`CString` to :rust:`*const i8`: you need a pointer to call C functions, -- :rust:`*const i8` to :rust:`&CStr`: you need something which can find the - trailing :rust:`\0` character, -- :rust:`&CStr` to :rust:`&[u8]`: a slice of bytes is the universal interface - for "some unknown data", -- :rust:`&[u8]` to :rust:`&OsStr`: :rust:`&OsStr` is a step towards :rust:`OsString`, - use - `OsStrExt `__ - to create it, -- :rust:`&OsStr` to :rust:`OsString`: you need to clone the data in :rust:`&OsStr` - to be able to return it and call :rust:`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: - -:: - - // 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 +================== +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 +:rust:`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: + +.. 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: + +- :rust:`&str` to :rust:`CString`: you need to allocate space for a trailing + :rust:`\0` character, +- :rust:`CString` to :rust:`*const i8`: you need a pointer to call C functions, +- :rust:`*const i8` to :rust:`&CStr`: you need something which can find the + trailing :rust:`\0` character, +- :rust:`&CStr` to :rust:`&[u8]`: a slice of bytes is the universal interface + for "some unknown data", +- :rust:`&[u8]` to :rust:`&OsStr`: :rust:`&OsStr` is a step towards :rust:`OsString`, + use + `OsStrExt `__ + to create it, +- :rust:`&OsStr` to :rust:`OsString`: you need to clone the data in :rust:`&OsStr` + to be able to return it and call :rust:`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: + +:: + + // 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 From cdd83f4b74677fdfb0802d0cf3ea62691d75c391 Mon Sep 17 00:00:00 2001 From: Michael Frank <55284511+frank-at-adacore@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:15:55 -0500 Subject: [PATCH 55/55] Remove last "raw html" line --- .../150_borrowing/04_interior_mutability.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst index 07a237814..bed5a88d5 100644 --- a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst +++ b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst @@ -88,8 +88,6 @@ escape. println!("{cell:?}"); } -.. raw:: html - ------------------- "RefCell" Details -------------------