diff --git a/contrib/rst_files_with_prelude.txt b/contrib/rst_files_with_prelude.txt index ac527f9ea..519ec3958 100644 --- a/contrib/rst_files_with_prelude.txt +++ b/contrib/rst_files_with_prelude.txt @@ -10,3 +10,4 @@ courses/gnattest/*.rst courses/gnat_project_facility/*.rst courses/gnatcoverage/*.rst courses/rust_essentials/*.rst +courses/comprehensive_rust_training/*.rst diff --git a/courses/comprehensive_rust_training/010_introduction.rst b/courses/comprehensive_rust_training/010_introduction.rst new file mode 100644 index 000000000..871d7e9d0 --- /dev/null +++ b/courses/comprehensive_rust_training/010_introduction.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst b/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst new file mode 100644 index 000000000..24d52ae24 --- /dev/null +++ b/courses/comprehensive_rust_training/010_introduction/01-about_adacore.rst @@ -0,0 +1,21 @@ +============= +About AdaCore +============= + +----------- +The Company +----------- + +.. + Taken from https://www.adacore.com/company/about + +* Founded in 1994 +* Centered around helping developers build **safe, secure and reliable** software +* Headquartered in New York and Paris + + - Representatives in countries around the globe + +* Roots in Open Source software movement + + - GNAT compiler is part of GNU Compiler Collection (GCC) + diff --git a/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst b/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst new file mode 100644 index 000000000..81b578774 --- /dev/null +++ b/courses/comprehensive_rust_training/010_introduction/02-about_this_training.rst @@ -0,0 +1,83 @@ +=================== +About This Training +=================== + +-------------------------- +Your Trainer +-------------------------- + +* Experience in software development + + - Languages + - Methodology + +* Experience teaching this class + +----------------------------- +Goals of the training session +----------------------------- + +* What you should know by the end of the training +* Syllabus overview + + - The syllabus is a guide, but we might stray off of it + - ...and that's OK: we're here to cover **your needs** + +---------- +Roundtable +---------- + +* 5 minute exercise + + - Write down your answers to the following + - Then share it with the room + +* Experience in software development + + - Languages + - Methodology + +* Experience and interest with the syllabus + + - Current and upcoming projects + - Curious for something? + +* Your personal goals for this training + + - What do you want to have coming out of this? + +* Anecdotes, stories... feel free to share! + + - Most interesting or funny bug you've encountered? + - Your own programming interests? + +------------------- +Course Presentation +------------------- + +* Slides +* Quizzes +* Labs + + - Hands-on practice + - Recommended setup: latest GNAT Studio + - Class reflection after some labs + +* Demos + + - Depending on the context + +* Daily schedule + +-------- +Styles +-------- + +* :dfn:`This` is a definition +* :filename:`this/is/a.path` +* :ada:`code is highlighted` +* :command:`commands are emphasised --like-this` + +.. warning:: This is a warning +.. note:: This is an important piece of info +.. tip:: This is a tip diff --git a/courses/comprehensive_rust_training/020_hello_world.rst b/courses/comprehensive_rust_training/020_hello_world.rst new file mode 100644 index 000000000..dfebc51e3 --- /dev/null +++ b/courses/comprehensive_rust_training/020_hello_world.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/020_hello_world/01_what_is_rust.rst b/courses/comprehensive_rust_training/020_hello_world/01_what_is_rust.rst new file mode 100644 index 000000000..152c69241 --- /dev/null +++ b/courses/comprehensive_rust_training/020_hello_world/01_what_is_rust.rst @@ -0,0 +1,46 @@ +=============== +What is Rust? +=============== + +--------------- +What is Rust? +--------------- + +Rust is a new programming language which had its `1.0 release in +2015 `__: + +- Rust is a statically compiled language in a similar role as C++ + + - ``rustc`` uses LLVM as its backend. + +- Rust supports many `platforms and + architectures `__: + + - x86, ARM, WebAssembly, ... + - Linux, Mac, Windows, ... + +- Rust is used for a wide range of devices: + + - firmware and boot loaders, + - smart displays, + - mobile phones, + - desktops, + - servers. + +.. raw:: html + +--------- +Details +--------- + +Rust fits in the same area as C++: + +- High flexibility. +- High level of control. +- Can be scaled down to very constrained devices such as + microcontrollers. +- Has no runtime or garbage collection. +- Focuses on reliability and safety without sacrificing performance. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst new file mode 100644 index 000000000..aa7f15f39 --- /dev/null +++ b/courses/comprehensive_rust_training/020_hello_world/02_benefits.rst @@ -0,0 +1,65 @@ +================== +Benefits of Rust +================== + +------------------ +Benefits of Rust +------------------ + +Some unique selling points of Rust: + +- *Compile time memory safety* - whole classes of memory bugs are + prevented at compile time + + - No uninitialized variables. + - No double-frees. + - No use-after-free. + - No ``NULL`` pointers. + - No forgotten locked mutexes. + - No data races between threads. + - No iterator invalidation. + +- *No undefined runtime behavior* - what a Rust statement does is never + left unspecified + + - Array access is bounds checked. + - Integer overflow is defined (panic or wrap-around). + +- *Modern language features* - as expressive and ergonomic as + higher-level languages + + - Enums and pattern matching. + - Generics. + - No overhead FFI. + - Zero-cost abstractions. + - Great compiler errors. + - Built-in dependency manager. + - Built-in support for testing. + - Excellent Language Server Protocol support. + +.. raw:: html + +--------- +Details +--------- + +Do not spend much time here. All of these points will be covered in more +depth later. + +Make sure to ask the class which languages they have experience with. +Depending on the answer you can highlight different features of Rust: + +- Experience with C or C++: Rust eliminates a whole class of *runtime + errors* via the borrow checker. You get performance like in C and + C++, but you don't have the memory unsafety issues. In addition, you + get a modern language with constructs like pattern matching and + built-in dependency management. + +- Experience with Java, Go, Python, JavaScript...: You get the same + memory safety as in those languages, plus a similar high-level + language feeling. In addition you get fast and predictable + performance like C and C++ (no garbage collector) as well as access + to low-level hardware (should you need it). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/020_hello_world/03_playground.rst b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst new file mode 100644 index 000000000..4edb31cec --- /dev/null +++ b/courses/comprehensive_rust_training/020_hello_world/03_playground.rst @@ -0,0 +1,37 @@ +============ +Playground +============ + +------------ +Playground +------------ + +The `Rust Playground `__ provides an easy +way to run short Rust programs, and is the basis for the examples and +exercises in this course. Try running the "hello-world" program it +starts with. It comes with a few handy features: + +- Under "Tools", use the ``rustfmt`` option to format your code in the + "standard" way. + +- Rust has two main "profiles" for generating code: Debug (extra + runtime checks, less optimization) and Release (fewer runtime checks, + lots of optimization). These are accessible under "Debug" at the top. + +- If you're interested, use "ASM" under "..." to see the generated + assembly code. + +.. raw:: html + +--------- +Details +--------- + +As students head into the break, encourage them to open up the +playground and experiment a little. Encourage them to keep the tab open +and try things out during the rest of the course. This is particularly +helpful for advanced students who want to know more about Rust's +optimizations or generated assembly. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values.rst b/courses/comprehensive_rust_training/030_types_and_values.rst new file mode 100644 index 000000000..4444b8c0b --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst b/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst new file mode 100644 index 000000000..f44de4689 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/01_hello_world.rst @@ -0,0 +1,61 @@ +============== +Hello, World +============== + +-------------- +Hello, World +-------------- + +Let us jump into the simplest possible Rust program, a classic Hello +World program: + +.. code:: rust,editable + + fn main() { + println!("Hello World!"); + } + +What you see: + +- Functions are introduced with ``fn``. +- Blocks are delimited by curly braces like in C and C++. +- The ``main`` function is the entry point of the program. +- Rust has hygienic macros, ``println!`` is an example of this. +- Rust strings are UTF-8 encoded and can contain any Unicode character. + +.. raw:: html + +--------- +Details +--------- + +This slide tries to make the students comfortable with Rust code. They +will see a ton of it over the next four days so we start small with +something familiar. + +Key points: + +- Rust is very much like other languages in the C/C++/Java tradition. + It is imperative and it doesn't try to reinvent things unless + absolutely necessary. + +- Rust is modern with full support for things like Unicode. + +- Rust uses macros for situations where you want to have a variable + number of arguments (no function + `overloading <../control-flow-basics/functions.md>`__). + +- Macros being 'hygienic' means they don't accidentally capture + identifiers from the scope they are used in. Rust macros are actually + only `partially + hygienic `__. + +- Rust is multi-paradigm. For example, it has powerful `object-oriented + programming + features `__, and, + while it is not a functional language, it includes a range of + `functional + concepts `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst b/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst new file mode 100644 index 000000000..d61956bc4 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/02_variables.rst @@ -0,0 +1,40 @@ +=========== +Variables +=========== + +----------- +Variables +----------- + +Rust provides type safety via static typing. Variable bindings are made +with ``let``: + +.. code:: rust,editable,warnunused + + fn main() { + let x: i32 = 10; + println!("x: {x}"); + // x = 20; + // println!("x: {x}"); + } + +.. raw:: html + +--------- +Details +--------- + +- Uncomment the ``x = 20`` to demonstrate that variables are immutable + by default. Add the ``mut`` keyword to allow changes. + +- Warnings are enabled for this slide, such as for unused variables or + unnecessary ``mut``. These are omitted in most slides to avoid + distracting warnings. Try removing the mutation but leaving the + ``mut`` keyword in place. + +- The ``i32`` here is the type of the variable. This must be known at + compile time, but type inference (covered later) allows the + programmer to omit it in many cases. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/03_values.rst b/courses/comprehensive_rust_training/030_types_and_values/03_values.rst new file mode 100644 index 000000000..3104ff30f --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/03_values.rst @@ -0,0 +1,53 @@ +======== +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: + +- ``iN``, ``uN``, and ``fN`` are *N* bits wide, +- ``isize`` and ``usize`` are the width of a pointer, +- ``char`` is 32 bits wide, +- ``bool`` is 8 bits wide. + +.. raw:: html + +--------- +Details +--------- + +There are a few syntaxes which are not shown above: + +- All underscores in numbers can be left out, they are for legibility + only. So ``1_000`` can be written as ``1000`` (or ``10_00``), and + ``123_i64`` can be written as ``123i64``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst new file mode 100644 index 000000000..0aaea06f0 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/04_arithmetic.rst @@ -0,0 +1,45 @@ +============ +Arithmetic +============ + +------------ +Arithmetic +------------ + +.. code:: rust,editable + + fn interproduct(a: i32, b: i32, c: i32) -> i32 { + return a * b + b * c + c * a; + } + + fn main() { + println!("result: {}", interproduct(120, 100, 248)); + } + +.. raw:: html + +--------- +Details +--------- + +This is the first time we've seen a function other than ``main``, but +the meaning should be clear: it takes three integers, and returns an +integer. Functions will be covered in more detail later. + +Arithmetic is very similar to other languages, with similar precedence. + +What about integer overflow? In C and C++ overflow of *signed* integers +is actually undefined, and might do unknown things at runtime. In Rust, +it's defined. + +Change the ``i32``\ 's to ``i16`` to see an integer overflow, which +panics (checked) in a debug build and wraps in a release build. There +are other options, such as overflowing, saturating, and carrying. These +are accessed with method syntax, e.g., +``(a * b).saturating_add(b * c).saturating_add(c * a)``. + +In fact, the compiler will detect overflow of constant expressions, +which is why the example requires a separate function. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst b/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst new file mode 100644 index 000000000..01d20908c --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/05_inference.rst @@ -0,0 +1,63 @@ +================ +Type Inference +================ + +---------------- +Type Inference +---------------- + +Rust will look at how the variable is *used* to determine the type: + +.. raw:: html + + + +.. code:: rust,editable + + fn takes_u32(x: u32) { + println!("u32: {x}"); + } + + fn takes_i8(y: i8) { + println!("i8: {y}"); + } + + fn main() { + let x = 10; + let y = 20; + + takes_u32(x); + takes_i8(y); + // takes_u32(y); + } + +.. raw:: html + +--------- +Details +--------- + +This slide demonstrates how the Rust compiler infers types based on +constraints given by variable declarations and usages. + +It is very important to emphasize that variables declared like this are +not of some sort of dynamic "any type" that can hold any data. The +machine code generated by such declaration is identical to the explicit +declaration of a type. The compiler does the job for us and helps us +write more concise code. + +When nothing constrains the type of an integer literal, Rust defaults to +``i32``. This sometimes appears as ``{integer}`` in error messages. +Similarly, floating-point literals default to ``f64``. + +.. code:: rust,compile_fail + + fn main() { + let x = 3.14; + let y = 20; + assert_eq!(x, y); + // ERROR: no implementation for `{float} == {integer}` + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst b/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst new file mode 100644 index 000000000..de6527d43 --- /dev/null +++ b/courses/comprehensive_rust_training/030_types_and_values/06_exercise.rst @@ -0,0 +1,28 @@ +===================== +Exercise: Fibonacci +===================== + +--------------------- +Exercise: Fibonacci +--------------------- + +The Fibonacci sequence begins with ``[0,1]``. For n>1, the n'th +Fibonacci number is calculated recursively as the sum of the n-1'th and +n-2'th Fibonacci numbers. + +Write a function ``fib(n)`` that calculates the n'th Fibonacci number. +When will this function panic? + +.. code:: rust,editable,should_panic + + {{#include exercise.rs:fib}} + if n < 2 { + // The base case. + return todo!("Implement this"); + } else { + // The recursive case. + return todo!("Implement this"); + } + } + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/040_control_flow_basics.rst b/courses/comprehensive_rust_training/040_control_flow_basics.rst new file mode 100644 index 000000000..856f38bac --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst b/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst new file mode 100644 index 000000000..4422f9101 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/01_if.rst @@ -0,0 +1,53 @@ +==================== +``if`` expressions +==================== + +-------------------- +``if`` expressions +-------------------- + +You use +`if expressions `__ +exactly like ``if`` statements in other languages: + +.. code:: rust,editable + + fn main() { + let x = 10; + if x == 0 { + println!("zero!"); + } else if x < 100 { + println!("biggish"); + } else { + println!("huge"); + } + } + +In addition, you can use ``if`` as an expression. The last expression of +each block becomes the value of the ``if`` expression: + +.. code:: rust,editable + + fn main() { + let x = 10; + let size = if x < 20 { "small" } else { "large" }; + println!("number size: {}", size); + } + +.. raw:: html + +--------- +Details +--------- + +Because ``if`` is an expression and must have a particular type, both of +its branch blocks must have the same type. Show what happens if you add +``;`` after ``"small"`` in the second example. + +An ``if`` expression should be used in the same way as the other +expressions. For example, when it is used in a ``let`` statement, the +statement must be terminated with a ``;`` as well. Remove the ``;`` +before ``println!`` to see the compiler error. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst new file mode 100644 index 000000000..d6834e96c --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/02_match.rst @@ -0,0 +1,85 @@ +======================= +``match`` Expressions +======================= + +----------------------- +``match`` Expressions +----------------------- + +``match`` can be used to check a value against one or more options: + +.. code:: rust,editable + + fn main() { + let val = 1; + match val { + 1 => println!("one"), + 10 => println!("ten"), + 100 => println!("one hundred"), + _ => { + println!("something else"); + } + } + } + +Like ``if`` expressions, ``match`` can also return a value; + +.. code:: rust,editable + + fn main() { + let flag = true; + let val = match flag { + true => 1, + false => 0, + }; + println!("The value of {flag} is {val}"); + } + +.. raw:: html + +--------- +Details +--------- + +- ``match`` arms are evaluated from top to bottom, and the first one + that matches has its corresponding body executed. + +- There is no fall-through between cases the way that ``switch`` works + in other languages. + +- The body of a ``match`` arm can be a single expression or a block. + Technically this is the same thing, since blocks are also + expressions, but students may not fully understand that symmetry at + this point. + +- ``match`` expressions need to be exhaustive, meaning they either need + to cover all possible values or they need to have a default case such + as ``_``. Exhaustiveness is easiest to demonstrate with enums, but + enums haven't been introduced yet. Instead we demonstrate matching on + a ``bool``, which is the simplest primitive type. + +- This slide introduces ``match`` without talking about pattern + matching, giving students a chance to get familiar with the syntax + without front-loading too much information. We'll be talking about + pattern matching in more detail tomorrow, so try not to go into too + much detail here. + +----------------- +More to Explore +----------------- + +- To further motivate the usage of ``match``, you can compare the + examples to their equivalents written with ``if``. In the second case + matching on a ``bool`` an ``if {} else {}`` block is pretty similar. + But in the first example that checks multiple cases, a ``match`` + expression can be more concise than + ``if {} else if {} else if {} else``. + +- ``match`` also supports match guards, which allow you to add an + arbitrary logical condition that will get evaluated to determine if + the match arm should be taken. However talking about match guards + requires explaining about pattern matching, which we're trying to + avoid on this slide. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst b/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst new file mode 100644 index 000000000..a8d7508d3 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/03_loops.rst @@ -0,0 +1,29 @@ +======= +Loops +======= + +------- +Loops +------- + +There are three looping keywords in Rust: ``while``, ``loop``, and +``for``: + +----------- +``while`` +----------- + +The +`while keyword `__ +works much like in other languages, executing the loop body as long as +the condition is true. + +.. code:: rust,editable + + fn main() { + let mut x = 200; + while x >= 10 { + x = x / 2; + } + println!("Final x: {x}"); + } diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst b/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst new file mode 100644 index 000000000..51fdfc7a5 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/04_break_continue.rst @@ -0,0 +1,45 @@ +============================ +``break`` and ``continue`` +============================ + +---------------------------- +``break`` and ``continue`` +---------------------------- + +If you want to immediately start the next iteration use +`continue `__. + +If you want to exit any kind of loop early, use +`break `__. +With ``loop``, this can take an optional expression that becomes the +value of the ``loop`` expression. + +.. code:: rust,editable + + fn main() { + let mut i = 0; + loop { + i += 1; + if i > 5 { + break; + } + if i % 2 == 0 { + continue; + } + println!("{}", i); + } + } + +.. raw:: html + +--------- +Details +--------- + +Note that ``loop`` is the only looping construct which can return a +non-trivial value. This is because it's guaranteed to only return at a +``break`` statement (unlike ``while`` and ``for`` loops, which can also +return when the condition fails). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/05_blocks_and_scopes.rst b/courses/comprehensive_rust_training/040_control_flow_basics/05_blocks_and_scopes.rst new file mode 100644 index 000000000..9fbc8024a --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/05_blocks_and_scopes.rst @@ -0,0 +1,43 @@ +=================== +Blocks and Scopes +=================== + +------------------- +Blocks and Scopes +------------------- + +-------- +Blocks +-------- + +A block in Rust contains a sequence of expressions, enclosed by braces +``{}``. Each block has a value and a type, which are those of the last +expression of the block: + +.. code:: rust,editable + + fn main() { + let z = 13; + let x = { + let y = 10; + println!("y: {y}"); + z - y + }; + println!("x: {x}"); + } + +If the last expression ends with ``;``, then the resulting value and +type is ``()``. + +.. raw:: html + +--------- +Details +--------- + +- You can show how the value of the block changes by changing the last + line in the block. For instance, adding/removing a semicolon or using + a ``return``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst b/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst new file mode 100644 index 000000000..46976fac8 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/06_functions.rst @@ -0,0 +1,51 @@ +=========== +Functions +=========== + +----------- +Functions +----------- + +.. raw:: html + + + +.. code:: rust,editable + + fn gcd(a: u32, b: u32) -> u32 { + if b > 0 { + gcd(b, a % b) + } else { + a + } + } + + fn main() { + println!("gcd: {}", gcd(143, 52)); + } + +.. raw:: html + +--------- +Details +--------- + +- Declaration parameters are followed by a type (the reverse of some + programming languages), then a return type. +- The last expression in a function body (or any block) becomes the + return value. Simply omit the ``;`` at the end of the expression. The + ``return`` keyword can be used for early return, but the "bare value" + form is idiomatic at the end of a function (refactor ``gcd`` to use a + ``return``). +- Some functions have no return value, and return the 'unit type', + ``()``. The compiler will infer this if the return type is omitted. +- Overloading is not supported - each function has a single + implementation. + + - Always takes a fixed number of parameters. Default arguments are + not supported. Macros can be used to support variadic functions. + - Always takes a single set of parameter types. These types can be + generic, which will be covered later. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst b/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst new file mode 100644 index 000000000..eccc7b051 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/07_macros.rst @@ -0,0 +1,57 @@ +======== +Macros +======== + +-------- +Macros +-------- + +Macros are expanded into Rust code during compilation, and can take a +variable number of arguments. They are distinguished by a ``!`` at the +end. The Rust standard library includes an assortment of useful macros. + +- ``println!(format, ..)`` prints a line to standard output, applying + formatting described in + `std::fmt `__. +- ``format!(format, ..)`` works just like ``println!`` but returns the + result as a string. +- ``dbg!(expression)`` logs the value of the expression and returns it. +- ``todo!()`` marks a bit of code as not-yet-implemented. If executed, + it will panic. +- ``unreachable!()`` marks a bit of code as unreachable. If executed, + it will panic. + +.. code:: rust,editable + + fn factorial(n: u32) -> u32 { + let mut product = 1; + for i in 1..=n { + product *= dbg!(i); + } + product + } + + fn fizzbuzz(n: u32) -> u32 { + todo!() + } + + fn main() { + let n = 4; + println!("{n}! = {}", factorial(n)); + } + +.. raw:: html + +--------- +Details +--------- + +The takeaway from this section is that these common conveniences exist, +and how to use them. Why they are defined as macros, and what they +expand to, is not especially critical. + +The course does not cover defining macros, but a later section will +describe use of derive macros. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst b/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst new file mode 100644 index 000000000..c2a3f5993 --- /dev/null +++ b/courses/comprehensive_rust_training/040_control_flow_basics/08_exercise.rst @@ -0,0 +1,39 @@ +============================ +Exercise: Collatz Sequence +============================ + +---------------------------- +Exercise: Collatz Sequence +---------------------------- + +The +`Collatz Sequence `__ is +defined as follows, for an arbitrary n1 greater than zero: + +- If *ni* is 1, then the sequence terminates at *ni*. +- If *ni* is even, then *ni+1 = ni / 2*. +- If *ni* is odd, then *ni+1 = 3 \* ni + 1*. + +For example, beginning with *n1* = 3: + +- 3 is odd, so *n2* = 3 \* 3 + 1 = 10; +- 10 is even, so *n3* = 10 / 2 = 5; +- 5 is odd, so *n4* = 3 \* 5 + 1 = 16; +- 16 is even, so *n5* = 16 / 2 = 8; +- 8 is even, so *n6* = 8 / 2 = 4; +- 4 is even, so *n7* = 4 / 2 = 2; +- 2 is even, so *n8* = 1; and +- the sequence terminates. + +Write a function to calculate the length of the collatz sequence for a +given initial ``n``. + +.. code:: rust,editable,should_panic + + {{#include exercise.rs:collatz_length}} + todo!("Implement this") + } + + {{#include exercise.rs:tests}} + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays.rst new file mode 100644 index 000000000..526591d33 --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst new file mode 100644 index 000000000..4230596ef --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/01_arrays.rst @@ -0,0 +1,49 @@ +======== +Arrays +======== + +-------- +Arrays +-------- + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let mut a: [i8; 10] = [42; 10]; + a[5] = 0; + println!("a: {a:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- A value of the array type ``[T; N]`` holds ``N`` (a compile-time + constant) elements of the same type ``T``. Note that the length of + the array is *part of its type*, which means that ``[u8; 3]`` and + ``[u8; 4]`` are considered two different types. Slices, which have a + size determined at runtime, are covered later. + +- Try accessing an out-of-bounds array element. Array accesses are + checked at runtime. Rust can usually optimize these checks away, and + they can be avoided using unsafe Rust. + +- We can use literals to assign values to arrays. + +- The ``println!`` macro asks for the debug implementation with the + ``?`` format parameter: ``{}`` gives the default output, ``{:?}`` + gives the debug output. Types such as integers and strings implement + the default output, but arrays only implement the debug output. This + means that we must use debug output here. + +- Adding ``#``, eg ``{a:#?}``, invokes a "pretty printing" format, + which can be easier to read. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst new file mode 100644 index 000000000..e998ccc99 --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/02_tuples.rst @@ -0,0 +1,39 @@ +======== +Tuples +======== + +-------- +Tuples +-------- + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let t: (i8, bool) = (7, true); + println!("t.0: {}", t.0); + println!("t.1: {}", t.1); + } + +.. raw:: html + +--------- +Details +--------- + +- Like arrays, tuples have a fixed length. + +- Tuples group together values of different types into a compound type. + +- Fields of a tuple can be accessed by the period and the index of the + value, e.g. ``t.0``, ``t.1``. + +- The empty tuple ``()`` is referred to as the "unit type" and + signifies absence of a return value, akin to ``void`` in other + languages. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst new file mode 100644 index 000000000..761da0eb5 --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/03_iteration.rst @@ -0,0 +1,36 @@ +================= +Array Iteration +================= + +----------------- +Array Iteration +----------------- + +The ``for`` statement supports iterating over arrays (but not tuples). + +.. code:: rust,editable + + fn main() { + let primes = [2, 3, 5, 7, 11, 13, 17, 19]; + for prime in primes { + for i in 2..prime { + assert_ne!(prime % i, 0); + } + } + } + +.. raw:: html + +--------- +Details +--------- + +This functionality uses the ``IntoIterator`` trait, but we haven't +covered that yet. + +The ``assert_ne!`` macro is new here. There are also ``assert_eq!`` and +``assert!`` macros. These are always checked, while debug-only variants +like ``debug_assert!`` compile to nothing in release builds. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst new file mode 100644 index 000000000..a670c7525 --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/04_destructuring.rst @@ -0,0 +1,50 @@ +============================ +Patterns and Destructuring +============================ + +---------------------------- +Patterns and Destructuring +---------------------------- + +When working with tuples and other structured values it's common to want +to extract the inner values into local variables. This can be done +manually by directly accessing the inner values: + +.. code:: rust,editable + + fn print_tuple(tuple: (i32, i32)) { + let left = tuple.0; + let right = tuple.1; + println!("left: {left}, right: {right}"); + } + +However, Rust also supports using pattern matching to destructure a +larger value into its constituent parts: + +.. code:: rust,editable + + fn print_tuple(tuple: (i32, i32)) { + let (left, right) = tuple; + println!("left: {left}, right: {right}"); + } + +.. raw:: html + +--------- +Details +--------- + +- The patterns used here are "irrefutable", meaning that the compiler + can statically verify that the value on the right of ``=`` has the + same structure as the pattern. +- A variable name is an irrefutable pattern that always matches any + value, hence why we can also use ``let`` to declare a single + variable. +- Rust also supports using patterns in conditionals, allowing for + equality comparison and destructuring to happen at the same time. + This form of pattern matching will be discussed in more detail later. +- Edit the examples above to show the compiler error when the pattern + doesn't match the value being matched on. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst b/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst new file mode 100644 index 000000000..c7fff5376 --- /dev/null +++ b/courses/comprehensive_rust_training/050_tuples_and_arrays/05_exercise.rst @@ -0,0 +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 ``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. + +.. code:: rust,should_panic + + // TODO: remove this when you're done with your implementation. + #![allow(unused_variables, dead_code)] + + {{#include exercise.rs:transpose}} + unimplemented!() + } + + {{#include exercise.rs:tests}} + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/060_references.rst b/courses/comprehensive_rust_training/060_references.rst new file mode 100644 index 000000000..cf81e3e22 --- /dev/null +++ b/courses/comprehensive_rust_training/060_references.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/060_references/01_shared.rst b/courses/comprehensive_rust_training/060_references/01_shared.rst new file mode 100644 index 000000000..a6274aa2c --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/01_shared.rst @@ -0,0 +1,75 @@ +=================== +Shared References +=================== + +------------------- +Shared References +------------------- + +A reference provides a way to access another value without taking +ownership of the value, and is also called "borrowing". Shared +references are read-only, and the referenced data cannot change. + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let a = 'A'; + let b = 'B'; + let mut r: &char = &a; + println!("r: {}", *r); + r = &b; + println!("r: {}", *r); + } + +A shared reference to a type ``T`` has type ``&T``. A reference value is +made with the ``&`` operator. The ``*`` operator "dereferences" a +reference, yielding its value. + +.. raw:: html + +--------- +Details +--------- + +- References can never be null in Rust, so null checking is not + necessary. + +- A reference is said to "borrow" the value it refers to, and this is a + good model for students not familiar with pointers: code can use the + reference to access the value, but is still "owned" by the original + variable. The course will get into more detail on ownership in day 3. + +- References are implemented as pointers, and a key advantage is that + they can be much smaller than the thing they point to. Students + familiar with C or C++ will recognize references as pointers. Later + parts of the course will cover how Rust prevents the memory-safety + bugs that come from using raw pointers. + +- Rust does not automatically create references for you - the ``&`` is + always required. + +- Rust will auto-dereference in some cases, in particular when invoking + methods (try ``r.is_ascii()``). There is no need for an ``->`` + operator like in C++. + +- In this example, ``r`` is mutable so that it can be reassigned + (``r = &b``). Note that this re-binds ``r``, so that it refers to + something else. This is different from C++, where assignment to a + reference changes the referenced value. + +- A shared reference does not allow modifying the value it refers to, + even if that value was mutable. Try ``*r = 'X'``. + +- Rust is tracking the lifetimes of all references to ensure they live + long enough. Dangling references cannot occur in safe Rust. + ``x_axis`` would return a reference to ``point``, but ``point`` will + be deallocated when the function returns, so this will not compile. + +- We will talk more about borrowing when we get to ownership. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/02_exclusive.rst b/courses/comprehensive_rust_training/060_references/02_exclusive.rst new file mode 100644 index 000000000..b9ec44233 --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/02_exclusive.rst @@ -0,0 +1,45 @@ +====================== +Exclusive References +====================== + +---------------------- +Exclusive References +---------------------- + +Exclusive references, also known as mutable references, allow changing +the value they refer to. They have type ``&mut T``. + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let mut point = (1, 2); + let x_coord = &mut point.0; + *x_coord = 20; + println!("point: {point:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +Key points: + +- "Exclusive" means that only this reference can be used to access the + value. No other references (shared or exclusive) can exist at the + same time, and the referenced value cannot be accessed while the + exclusive reference exists. Try making an ``&point.0`` or changing + ``point.0`` while ``x_coord`` is alive. + +- Be sure to note the difference between ``let mut x_coord: &i32`` and + ``let x_coord: &mut i32``. The first one represents a shared + reference which can be bound to different values, while the second + represents an exclusive reference to a mutable value. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/03_slices.rst b/courses/comprehensive_rust_training/060_references/03_slices.rst new file mode 100644 index 000000000..046fe9e54 --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/03_slices.rst @@ -0,0 +1,55 @@ +======== +Slices +======== + +-------- +Slices +-------- + +A slice gives you a view into a larger collection: + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let a: [i32; 6] = [10, 20, 30, 40, 50, 60]; + println!("a: {a:?}"); + + let s: &[i32] = &a[2..4]; + + println!("s: {s:?}"); + } + +- Slices borrow data from the sliced type. + +.. raw:: html + +--------- +Details +--------- + +- We create a slice by borrowing ``a`` and specifying the starting and + ending indexes in brackets. + +- If the slice starts at index 0, Rust's range syntax allows us to drop + the starting index, meaning that ``&a[0..a.len()]`` and + ``&a[..a.len()]`` are identical. + +- The same is true for the last index, so ``&a[2..a.len()]`` and + ``&a[2..]`` are identical. + +- To easily create a slice of the full array, we can therefore use + ``&a[..]``. + +- ``s`` is a reference to a slice of ``i32``\ s. Notice that the type + of ``s`` (``&[i32]``) no longer mentions the array length. This + allows us to perform computation on slices of different sizes. + +- Slices always borrow from another object. In this example, ``a`` has + to remain 'alive' (in scope) for at least as long as our slice. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/04_strings.rst b/courses/comprehensive_rust_training/060_references/04_strings.rst new file mode 100644 index 000000000..fa2a77c28 --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/04_strings.rst @@ -0,0 +1,104 @@ +.. raw:: html + + + +========= +Strings +========= + +--------- +Strings +--------- + +We can now understand the two string types in Rust: + +- ``&str`` is a slice of UTF-8 encoded bytes, similar to ``&[u8]``. +- ``String`` is an owned buffer of UTF-8 encoded bytes, similar to + ``Vec``. + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let s1: &str = "World"; + println!("s1: {s1}"); + + let mut s2: String = String::from("Hello "); + println!("s2: {s2}"); + s2.push_str(s1); + println!("s2: {s2}"); + + let s3: &str = &s2[s2.len() - s1.len()..]; + println!("s3: {s3}"); + } + +.. raw:: html + +--------- +Details +--------- + +- ``&str`` introduces a string slice, which is an immutable reference + to UTF-8 encoded string data stored in a block of memory. String + literals (``"Hello"``), are stored in the program's binary. + +- Rust's ``String`` type is a wrapper around a vector of bytes. As with + a ``Vec``, it is owned. + +- As with many other types ``String::from()`` creates a string from a + string literal; ``String::new()`` creates a new empty string, to + which string data can be added using the ``push()`` and + ``push_str()`` methods. + +- The ``format!()`` macro is a convenient way to generate an owned + string from dynamic values. It accepts the same format specification + as ``println!()``. + +- You can borrow ``&str`` slices from ``String`` via ``&`` and + optionally range selection. If you select a byte range that is not + aligned to character boundaries, the expression will panic. The + ``chars`` iterator iterates over characters and is preferred over + trying to get character boundaries right. + +- For C++ programmers: think of ``&str`` as ``std::string_view`` from + C++, but the one that always points to a valid string in memory. Rust + ``String`` is a rough equivalent of ``std::string`` from C++ (main + difference: it can only contain UTF-8 encoded bytes and will never + use a small-string optimization). + +- Byte strings literals allow you to create a ``&[u8]`` value directly: + + .. raw:: html + + + + .. code:: rust,editable + + fn main() { + println!("{:?}", b"abc"); + println!("{:?}", &[97, 98, 99]); + } + +- Raw strings allow you to create a ``&str`` value with escapes + disabled: ``r"\n" == "\\n"``. You can embed double-quotes by using an + equal amount of ``#`` on either side of the quotes: + + .. raw:: html + + + + .. code:: rust,editable + + fn main() { + println!(r#"link"#); + println!("link"); + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/05_dangling.rst b/courses/comprehensive_rust_training/060_references/05_dangling.rst new file mode 100644 index 000000000..1e32cc8fa --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/05_dangling.rst @@ -0,0 +1,51 @@ +==================== +Reference Validity +==================== + +-------------------- +Reference Validity +-------------------- + +Rust enforces a number of rules for references that make them always +safe to use. One rule is that references can never be ``null``, making +them safe to use without ``null`` checks. The other rule we'll look at +for now is that references can't *outlive* the data they point to. + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + fn main() { + let x_ref = { + let x = 10; + &x + }; + println!("x: {x_ref}"); + } + +.. raw:: html + +--------- +Details +--------- + +- This slide gets students thinking about references as not simply + being pointers, since Rust has different rules for references than + other languages. + +- We'll look at the rest of Rust's borrowing rules on day 3 when we + talk about Rust's ownership system. + +----------------- +More to Explore +----------------- + +- Rust's equivalent of nullability is the ``Option`` type, which can be + used to make any type "nullable" (not just references/pointers). We + haven't yet introduced enums or pattern matching, though, so try not + to go into too much detail about this here. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/060_references/06_exercise.rst b/courses/comprehensive_rust_training/060_references/06_exercise.rst new file mode 100644 index 000000000..927816f8d --- /dev/null +++ b/courses/comprehensive_rust_training/060_references/06_exercise.rst @@ -0,0 +1,34 @@ +==================== +Exercise: Geometry +==================== + +-------------------- +Exercise: Geometry +-------------------- + +We will create a few utility functions for 3-dimensional geometry, +representing a point as ``[f64;3]``. It is up to you to determine the +function signatures. + +.. code:: rust,compile_fail + + // Calculate the magnitude of a vector by summing the squares of its coordinates + // and taking the square root. Use the `sqrt()` method to calculate the square + // root, like `v.sqrt()`. + + {{#include exercise.rs:magnitude}} + fn magnitude(...) -> f64 { + todo!() + } + + // Normalize a vector by calculating its magnitude and dividing all of its + // coordinates by that magnitude. + + {{#include exercise.rs:normalize}} + fn normalize(...) { + todo!() + } + + // Use the following `main` to test your work. + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/070_user_defined_types.rst b/courses/comprehensive_rust_training/070_user_defined_types.rst new file mode 100644 index 000000000..2823130b1 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/070_user_defined_types/01_named_structs.rst b/courses/comprehensive_rust_training/070_user_defined_types/01_named_structs.rst new file mode 100644 index 000000000..9c9ea7464 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/01_named_structs.rst @@ -0,0 +1,68 @@ +=============== +Named Structs +=============== + +--------------- +Named Structs +--------------- + +Like C and C++, Rust has support for custom structs: + +.. code:: rust,editable + + struct Person { + name: String, + age: u8, + } + + fn describe(person: &Person) { + println!("{} is {} years old", person.name, person.age); + } + + fn main() { + let mut peter = Person { name: String::from("Peter"), age: 27 }; + describe(&peter); + + peter.age = 28; + describe(&peter); + + let name = String::from("Avery"); + let age = 39; + let avery = Person { name, age }; + describe(&avery); + + let jackie = Person { name: String::from("Jackie"), ..avery }; + describe(&jackie); + } + +.. raw:: html + +--------- +Details +--------- + +Key Points: + +- Structs work like in C or C++. + + - Like in C++, and unlike in C, no typedef is needed to define a + type. + - Unlike in C++, there is no inheritance between structs. + +- This may be a good time to let people know there are different types + of structs. + + - Zero-sized structs (e.g. ``struct Foo;``) might be used when + implementing a trait on some type but don't have any data that you + want to store in the value itself. + - The next slide will introduce Tuple structs, used when the field + names are not important. + +- If you already have variables with the right names, then you can + create the struct using a shorthand. +- The syntax ``..avery`` allows us to copy the majority of the fields + from the old struct without having to explicitly type it all out. It + must always be the last element. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst b/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst new file mode 100644 index 000000000..a9b91c994 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/02_tuple_structs.rst @@ -0,0 +1,73 @@ +.. raw:: html + + + +=============== +Tuple Structs +=============== + +--------------- +Tuple Structs +--------------- + +If the field names are unimportant, you can use a tuple struct: + +.. code:: rust,editable + + struct Point(i32, i32); + + fn main() { + let p = Point(17, 23); + println!("({}, {})", p.0, p.1); + } + +This is often used for single-field wrappers (called newtypes): + +.. code:: rust,editable,compile_fail + + struct PoundsOfForce(f64); + struct Newtons(f64); + + fn compute_thruster_force() -> PoundsOfForce { + todo!("Ask a rocket scientist at NASA") + } + + fn set_thruster_force(force: Newtons) { + // ... + } + + fn main() { + let force = compute_thruster_force(); + set_thruster_force(force); + } + +.. raw:: html + +--------- +Details +--------- + +- Newtypes are a great way to encode additional information about the + value in a primitive type, for example: + + - The number is measured in some units: ``Newtons`` in the example + above. + - The value passed some validation when it was created, so you no + longer have to validate it again at every use: + ``PhoneNumber(String)`` or ``OddNumber(u32)``. + +- Demonstrate how to add a ``f64`` value to a ``Newtons`` type by + accessing the single field in the newtype. + + - Rust generally doesn't like inexplicit things, like automatic + unwrapping or for instance using booleans as integers. + - Operator overloading is discussed on Day 3 (generics). + +- The example is a subtle reference to the + `Mars Climate Orbiter `__ + failure. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst b/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst new file mode 100644 index 000000000..721128ec9 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/03_enums.rst @@ -0,0 +1,142 @@ +======= +Enums +======= + +------- +Enums +------- + +The ``enum`` keyword allows the creation of a type which has a few +different variants: + +.. code:: rust,editable + + #[derive(Debug)] + enum Direction { + Left, + Right, + } + + #[derive(Debug)] + enum PlayerMove { + Pass, // Simple variant + Run(Direction), // Tuple variant + Teleport { x: u32, y: u32 }, // Struct variant + } + + fn main() { + let player_move: PlayerMove = PlayerMove::Run(Direction::Left); + println!("On this turn: {player_move:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +Key Points: + +- Enumerations allow you to collect a set of values under one type. +- ``Direction`` is a type with variants. There are two values of + ``Direction``: ``Direction::Left`` and ``Direction::Right``. +- ``PlayerMove`` is a type with three variants. In addition to the + payloads, Rust will store a discriminant so that it knows at runtime + which variant is in a ``PlayerMove`` value. +- This might be a good time to compare structs and enums: + + - In both, you can have a simple version without fields (unit + struct) or one with different types of fields (variant payloads). + - You could even implement the different variants of an enum with + separate structs but then they wouldn't be the same type as they + would if they were all defined in an enum. + +- Rust uses minimal space to store the discriminant. + + - If necessary, it stores an integer of the smallest required size + + - If the allowed variant values do not cover all bit patterns, it + will use invalid bit patterns to encode the discriminant (the + "niche optimization"). For example, ``Option<&u8>`` stores either + a pointer to an integer or ``NULL`` for the ``None`` variant. + + - You can control the discriminant if needed (e.g., for + compatibility with C): + + .. raw:: html + + + + .. code:: rust,editable + + #[repr(u32)] + enum Bar { + A, // 0 + B = 10000, + C, // 10001 + } + + fn main() { + println!("A: {}", Bar::A as u32); + println!("B: {}", Bar::B as u32); + println!("C: {}", Bar::C as u32); + } + + Without ``repr``, the discriminant type takes 2 bytes, because + 10001 fits 2 bytes. + +----------------- +More to Explore +----------------- + +Rust has several optimizations it can employ to make enums take up less +space. + +- Null pointer optimization: For + `some types `__, Rust + guarantees that ``size_of::()`` equals ``size_of::>()``. + + Example code if you want to show how the bitwise representation *may* + look like in practice. It's important to note that the compiler + provides no guarantees regarding this representation, therefore this + is totally unsafe. + + .. raw:: html + + + + .. code:: rust,editable + + use std::mem::transmute; + + macro_rules! dbg_bits { + ($e:expr, $bit_type:ty) => { + println!("- {}: {:#x}", stringify!($e), transmute::<_, $bit_type>($e)); + }; + } + + fn main() { + unsafe { + println!("bool:"); + dbg_bits!(false, u8); + dbg_bits!(true, u8); + + println!("Option:"); + dbg_bits!(None::, u8); + dbg_bits!(Some(false), u8); + dbg_bits!(Some(true), u8); + + println!("Option>:"); + dbg_bits!(Some(Some(false)), u8); + dbg_bits!(Some(Some(true)), u8); + dbg_bits!(Some(None::), u8); + dbg_bits!(None::>, u8); + + println!("Option<&i32>:"); + dbg_bits!(None::<&i32>, usize); + dbg_bits!(Some(&0i32), usize); + } + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst b/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst new file mode 100644 index 000000000..75752ebca --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/04_aliases.rst @@ -0,0 +1,39 @@ +============== +Type Aliases +============== + +-------------- +Type Aliases +-------------- + +A type alias creates a name for another type. The two types can be used +interchangeably. + +.. code:: rust,editable + + enum CarryableConcreteItem { + Left, + Right, + } + + type Item = CarryableConcreteItem; + + // Aliases are more useful with long, complex types: + use std::cell::RefCell; + use std::sync::{Arc, RwLock}; + type PlayerInventory = RwLock>>>; + +.. raw:: html + +--------- +Details +--------- + +- A `newtype `__ is often a better alternative + since it creates a distinct type. Prefer + ``struct InventoryCount(usize)`` to ``type InventoryCount = usize``. + +- C programmers will recognize this as similar to a ``typedef``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst b/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst new file mode 100644 index 000000000..9fcc4b4a8 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/05_const.rst @@ -0,0 +1,60 @@ +=========== +``const`` +=========== + +----------- +``const`` +----------- + +Constants are evaluated at compile time and their values are inlined +wherever they are used: + +.. raw:: html + + + +.. code:: rust,editable + + const DIGEST_SIZE: usize = 3; + const FILL_VALUE: u8 = calculate_fill_value(); + + const fn calculate_fill_value() -> u8 { + if DIGEST_SIZE < 10 { + 42 + } else { + 13 + } + } + + fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] { + let mut digest = [FILL_VALUE; DIGEST_SIZE]; + for (idx, &b) in text.as_bytes().iter().enumerate() { + digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b); + } + digest + } + + fn main() { + let digest = compute_digest("Hello"); + println!("digest: {digest:?}"); + } + +According to the +`Rust RFC Book `__ +these are inlined upon use. + +Only functions marked ``const`` can be called at compile time to +generate ``const`` values. ``const`` functions can however be called at +runtime. + +.. raw:: html + +--------- +Details +--------- + +- Mention that ``const`` behaves semantically similar to C++'s + ``constexpr`` + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst b/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst new file mode 100644 index 000000000..510f0c1b0 --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/06_static.rst @@ -0,0 +1,58 @@ +============ +``static`` +============ + +------------ +``static`` +------------ + +Static variables will live during the whole execution of the program, +and therefore will not move: + +.. code:: rust,editable + + static BANNER: &str = "Welcome to RustOS 3.14"; + + fn main() { + println!("{BANNER}"); + } + +As noted in the +`Rust RFC Book `__, +these are not inlined upon use and have an actual associated memory +location. This is useful for unsafe and embedded code, and the variable +lives through the entirety of the program execution. When a +globally-scoped value does not have a reason to need object identity, +``const`` is generally preferred. + +.. raw:: html + +--------- +Details +--------- + +- ``static`` is similar to mutable global variables in C++. +- ``static`` provides object identity: an address in memory and state + as required by types with interior mutability such as ``Mutex``. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +Because ``static`` variables are accessible from any thread, they must +be ``Sync``. Interior mutability is possible through a +`Mutex `__, +atomic or similar. + +It is common to use ``OnceLock`` in a static as a way to support +initialization on first use. ``OnceCell`` is not ``Sync`` and thus +cannot be used in this context. + +Thread-local data can be created with the macro ``std::thread_local``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst b/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst new file mode 100644 index 000000000..d89c8d97e --- /dev/null +++ b/courses/comprehensive_rust_training/070_user_defined_types/07_exercise.rst @@ -0,0 +1,46 @@ +=========================== +Exercise: Elevator Events +=========================== + +--------------------------- +Exercise: Elevator Events +--------------------------- + +We will create a data structure to represent an event in an elevator +control system. It is up to you to define the types and functions to +construct various events. Use ``#[derive(Debug)]`` to allow the types to +be formatted with ``{:?}``. + +This exercise only requires creating and populating data structures so +that ``main`` runs without errors. The next part of the course will +cover getting data out of these structures. + +.. code:: rust,editable,should_panic + + {{#include exercise.rs:event}} + // TODO: add required variants + } + + {{#include exercise.rs:direction}} + + {{#include exercise.rs:car_arrived}} + todo!() + } + + {{#include exercise.rs:car_door_opened}} + todo!() + } + + {{#include exercise.rs:car_door_closed}} + todo!() + } + + {{#include exercise.rs:lobby_call_button_pressed}} + todo!() + } + + {{#include exercise.rs:car_floor_button_pressed}} + todo!() + } + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/080_pattern_matching.rst b/courses/comprehensive_rust_training/080_pattern_matching.rst new file mode 100644 index 000000000..6104446e0 --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst new file mode 100644 index 000000000..a15013aeb --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/01_match.rst @@ -0,0 +1,90 @@ +================= +Matching Values +================= + +----------------- +Matching Values +----------------- + +The ``match`` keyword lets you match a value against one or more +*patterns*. The patterns can be simple values, similarly to ``switch`` +in C and C++, but they can also be used to express more complex +conditions: + +.. code:: rust,editable + + #[rustfmt::skip] + fn main() { + let input = 'x'; + match input { + 'q' => println!("Quitting"), + 'a' | 's' | 'w' | 'd' => println!("Moving around"), + '0'..='9' => println!("Number input"), + key if key.is_lowercase() => println!("Lowercase: {key}"), + _ => println!("Something else"), + } + } + +A variable in the pattern (``key`` in this example) will create a +binding that can be used within the match arm. We will learn more about +this on the next slide. + +A match guard causes the arm to match only if the condition is true. If +the condition is false the match will continue checking later cases. + +.. raw:: html + +--------- +Details +--------- + +Key Points: + +- You might point out how some specific characters are being used when + in a pattern + + - ``|`` as an ``or`` + - ``..`` can expand as much as it needs to be + - ``1..=5`` represents an inclusive range + - ``_`` is a wild card + +- Match guards as a separate syntax feature are important and necessary + when we wish to concisely express more complex ideas than patterns + alone would allow. +- They are not the same as separate ``if`` expression inside of the + match arm. An ``if`` expression inside of the branch block (after + ``=>``) happens after the match arm is selected. Failing the ``if`` + condition inside of that block won't result in other arms of the + original ``match`` expression being considered. +- The condition defined in the guard applies to every expression in a + pattern with an ``|``. + +================= +More To Explore +================= + +----------------- +More To Explore +----------------- + +- Another piece of pattern syntax you can show students is the ``@`` + syntax which binds a part of a pattern to a variable. For example: + + .. code:: rust + + let opt = Some(123); + match opt { + outer @ Some(inner) => { + println!("outer: {outer:?}, inner: {inner}"); + } + None => {} + } + + In this example ``inner`` has the value 123 which it pulled from the + ``Option`` via destructuring, ``outer`` captures the entire + ``Some(inner)`` expression, so it contains the full + ``Option::Some(123)``. This is rarely used but can be useful in more + complex patterns. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst b/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst new file mode 100644 index 000000000..f6f3f7e2c --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/02_destructuring_structs.rst @@ -0,0 +1,30 @@ +========= +Structs +========= + +--------- +Structs +--------- + +Like tuples, Struct can also be destructured by matching: + +.. code:: rust,editable + + {{#include ../../third_party/rust-by-example/destructuring-structs.rs}} + +.. raw:: html + +--------- +Details +--------- + +- Change the literal values in ``foo`` to match with the other + patterns. +- Add a new field to ``Foo`` and make changes to the pattern as needed. +- The distinction between a capture and a constant expression can be + hard to spot. Try changing the ``2`` in the second arm to a variable, + and see that it subtly doesn't work. Change it to a ``const`` and see + it working again. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst b/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst new file mode 100644 index 000000000..cc974fec6 --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/03_destructuring_enums.rst @@ -0,0 +1,61 @@ +======= +Enums +======= + +------- +Enums +------- + +Like tuples, enums can also be destructured by matching: + +Patterns can also be used to bind variables to parts of your values. +This is how you inspect the structure of your types. Let us start with a +simple ``enum`` type: + +.. code:: rust,editable + + enum Result { + Ok(i32), + Err(String), + } + + fn divide_in_two(n: i32) -> Result { + if n % 2 == 0 { + Result::Ok(n / 2) + } else { + Result::Err(format!("cannot divide {n} into two equal parts")) + } + } + + fn main() { + let n = 100; + match divide_in_two(n) { + Result::Ok(half) => println!("{n} divided in two is {half}"), + Result::Err(msg) => println!("sorry, an error happened: {msg}"), + } + } + +Here we have used the arms to *destructure* the ``Result`` value. In the +first arm, ``half`` is bound to the value inside the ``Ok`` variant. In +the second arm, ``msg`` is bound to the error message. + +.. raw:: html + +--------- +Details +--------- + +- The ``if``/``else`` expression is returning an enum that is later + unpacked with a ``match``. +- You can try adding a third variant to the enum definition and + displaying the errors when running the code. Point out the places + where your code is now inexhaustive and how the compiler tries to + give you hints. +- The values in the enum variants can only be accessed after being + pattern matched. +- Demonstrate what happens when the search is inexhaustive. Note the + advantage the Rust compiler provides by confirming when all cases are + handled. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst b/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst new file mode 100644 index 000000000..7b9413c2b --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/04_let_control_flow.rst @@ -0,0 +1,167 @@ +================== +Let Control Flow +================== + +------------------ +Let Control Flow +------------------ + +Rust has a few control flow constructs which differ from other +languages. They are used for pattern matching: + +- ``if let`` expressions +- ``let else`` expressions +- ``while let`` expressions + +======================== +``if let`` expressions +======================== + +------------------------ +``if let`` expressions +------------------------ + +The +`if let expression `__ +lets you execute different code depending on whether a value matches a +pattern: + +.. code:: rust,editable + + use std::time::Duration; + + fn sleep_for(secs: f32) { + if let Ok(duration) = Duration::try_from_secs_f32(secs) { + std::thread::sleep(duration); + println!("slept for {duration:?}"); + } + } + + fn main() { + sleep_for(-10.0); + sleep_for(0.8); + } + +========================== +``let else`` expressions +========================== + +-------------------------- +``let else`` expressions +-------------------------- + +For the common case of matching a pattern and returning from the +function, use +`let else `__. +The "else" case must diverge (``return``, ``break``, or panic - anything +but falling off the end of the block). + +.. code:: rust,editable + + fn hex_or_die_trying(maybe_string: Option) -> Result { + // TODO: The structure of this code is difficult to follow -- rewrite it with let-else! + if let Some(s) = maybe_string { + if let Some(first_byte_char) = s.chars().next() { + if let Some(digit) = first_byte_char.to_digit(16) { + Ok(digit) + } else { + return Err(String::from("not a hex digit")); + } + } else { + return Err(String::from("got empty string")); + } + } else { + return Err(String::from("got None")); + } + } + + fn main() { + println!("result: {:?}", hex_or_die_trying(Some(String::from("foo")))); + } + +Like with ``if let``, there is a +`while let `__ +variant which repeatedly tests a value against a pattern: + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let mut name = String::from("Comprehensive Rust"); + while let Some(c) = name.pop() { + println!("character: {c}"); + } + // (There are more efficient ways to reverse a string!) + } + +Here +`String::pop `__ +returns ``Some(c)`` until the string is empty, after which it will +return ``None``. The ``while let`` lets us keep iterating through all +items. + +.. raw:: html + +--------- +Details +--------- + +-------- +if-let +-------- + +- Unlike ``match``, ``if let`` does not have to cover all branches. + This can make it more concise than ``match``. +- A common usage is handling ``Some`` values when working with + ``Option``. +- Unlike ``match``, ``if let`` does not support guard clauses for + pattern matching. + +---------- +let-else +---------- + +``if-let``\ s can pile up, as shown. The ``let-else`` construct supports +flattening this nested code. Rewrite the awkward version for students, +so they can see the transformation. + +The rewritten version is: + +.. code:: rust + + fn hex_or_die_trying(maybe_string: Option) -> Result { + let Some(s) = maybe_string else { + return Err(String::from("got None")); + }; + + let Some(first_byte_char) = s.chars().next() else { + return Err(String::from("got empty string")); + }; + + let Some(digit) = first_byte_char.to_digit(16) else { + return Err(String::from("not a hex digit")); + }; + + return Ok(digit); + } + +=========== +while-let +=========== + +----------- +while-let +----------- + +- Point out that the ``while let`` loop will keep going as long as the + value matches the pattern. +- You could rewrite the ``while let`` loop as an infinite loop with an + if statement that breaks when there is no value to unwrap for + ``name.pop()``. The ``while let`` provides syntactic sugar for the + above scenario. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst new file mode 100644 index 000000000..c27dc8729 --- /dev/null +++ b/courses/comprehensive_rust_training/080_pattern_matching/05_exercise.rst @@ -0,0 +1,88 @@ +================================= +Exercise: Expression Evaluation +================================= + +--------------------------------- +Exercise: Expression Evaluation +--------------------------------- + +Let's write a simple recursive evaluator for arithmetic expressions. + +An example of a small arithmetic expression could be ``10 + 20``, which +evaluates to ``30``. We can represent the expression as a tree: + +.. raw:: html + + + +.. code:: bob + + .-------. + .------ | + | ------. + | '-------' | + v v + .--------. .--------. + | 10 | | 20 | + '--------' '--------' + +A bigger and more complex expression would be +``(10 * 9) + ((3 - 4) * 5)``, which evaluate to ``85``. We represent +this as a much bigger tree: + +.. raw:: html + + + +.. code:: bob + + .-----. + .---------------- | + | ----------------. + | '-----' | + v v + .-----. .-----. + .---- | * | ----. .---- | * | ----. + | '-----' | | '-----' | + v v v v + .------. .-----. .-----. .-----. + | 10 | | 9 | .---- | "-"| ----. | 5 | + '------' '-----' | '-----' | '-----' + v v + .-----. .-----. + | 3 | | 4 | + '-----' '-----' + +In code, we will represent the tree with two types: + +.. code:: rust + + {{#include exercise.rs:Operation}} + + {{#include exercise.rs:Expression}} + +The ``Box`` type here is a smart pointer, and will be covered in detail +later in the course. An expression can be "boxed" with ``Box::new`` as +seen in the tests. To evaluate a boxed expression, use the deref +operator (``*``) to "unbox" it: ``eval(*boxed_expr)``. + +Copy and paste the code into the Rust playground, and begin implementing +``eval``. The final product should pass the tests. It may be helpful to +use ``todo!()`` and get the tests to pass one-by-one. You can also skip +a test temporarily with ``#[ignore]``: + +.. code:: none + + #[test] + #[ignore] + fn test_value() { .. } + +.. code:: rust + + {{#include exercise.rs:Operation}} + + {{#include exercise.rs:Expression}} + + {{#include exercise.rs:eval}} + todo!() + } + + {{#include exercise.rs:tests}} diff --git a/courses/comprehensive_rust_training/090_methods_and_traits.rst b/courses/comprehensive_rust_training/090_methods_and_traits.rst new file mode 100644 index 000000000..d2ae4c12f --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst b/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst new file mode 100644 index 000000000..bcec67c18 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits/01_methods.rst @@ -0,0 +1,106 @@ +========= +Methods +========= + +--------- +Methods +--------- + +Rust allows you to associate functions with your new types. You do this +with an ``impl`` block: + +.. code:: rust,editable + + #[derive(Debug)] + struct CarRace { + name: String, + laps: Vec, + } + + impl CarRace { + // No receiver, a static method + fn new(name: &str) -> Self { + Self { name: String::from(name), laps: Vec::new() } + } + + // Exclusive borrowed read-write access to self + fn add_lap(&mut self, lap: i32) { + self.laps.push(lap); + } + + // Shared and read-only borrowed access to self + fn print_laps(&self) { + println!("Recorded {} laps for {}:", self.laps.len(), self.name); + for (idx, lap) in self.laps.iter().enumerate() { + println!("Lap {idx}: {lap} sec"); + } + } + + // Exclusive ownership of self (covered later) + fn finish(self) { + let total: i32 = self.laps.iter().sum(); + println!("Race {} is finished, total lap time: {}", self.name, total); + } + } + + fn main() { + let mut race = CarRace::new("Monaco Grand Prix"); + race.add_lap(70); + race.add_lap(68); + race.print_laps(); + race.add_lap(71); + race.print_laps(); + race.finish(); + // race.add_lap(42); + } + +The ``self`` arguments specify the "receiver" - the object the method +acts on. There are several common receivers for a method: + +- ``&self``: borrows the object from the caller using a shared and + immutable reference. The object can be used again afterwards. +- ``&mut self``: borrows the object from the caller using a unique and + mutable reference. The object can be used again afterwards. +- ``self``: takes ownership of the object and moves it away from the + caller. The method becomes the owner of the object. The object will + be dropped (deallocated) when the method returns, unless its + ownership is explicitly transmitted. Complete ownership does not + automatically mean mutability. +- ``mut self``: same as above, but the method can mutate the object. +- No receiver: this becomes a static method on the struct. Typically + used to create constructors which are called ``new`` by convention. + +.. raw:: html + +--------- +Details +--------- + +Key Points: + +- It can be helpful to introduce methods by comparing them to + functions. + + - Methods are called on an instance of a type (such as a struct or + enum), the first parameter represents the instance as ``self``. + - Developers may choose to use methods to take advantage of method + receiver syntax and to help keep them more organized. By using + methods we can keep all the implementation code in one predictable + place. + +- Point out the use of the keyword ``self``, a method receiver. + + - Show that it is an abbreviated term for ``self: Self`` and perhaps + show how the struct name could also be used. + - Explain that ``Self`` is a type alias for the type the ``impl`` + block is in and can be used elsewhere in the block. + - Note how ``self`` is used like other structs and dot notation can + be used to refer to individual fields. + - This might be a good time to demonstrate how the ``&self`` differs + from ``self`` by trying to run ``finish`` twice. + - Beyond variants on ``self``, there are also + `special wrapper types `__ + allowed to be receiver types, such as ``Box``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst new file mode 100644 index 000000000..576f78323 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits/02_traits.rst @@ -0,0 +1,35 @@ +======== +Traits +======== + +-------- +Traits +-------- + +Rust lets you abstract over types with traits. They're similar to +interfaces: + +.. code:: rust,editable + + trait Pet { + /// Return a sentence from this pet. + fn talk(&self) -> String; + + /// Print a string to the terminal greeting this pet. + fn greet(&self); + } + +.. raw:: html + +--------- +Details +--------- + +- A trait defines a number of methods that types must have in order to + implement the trait. + +- In the "Generics" segment, next, we will see how to build + functionality that is generic over all types implementing a trait. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst b/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst new file mode 100644 index 000000000..71637bfe2 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits/03_deriving.rst @@ -0,0 +1,41 @@ +========== +Deriving +========== + +---------- +Deriving +---------- + +Supported traits can be automatically implemented for your custom types, +as follows: + +.. code:: rust,editable + + #[derive(Debug, Clone, Default)] + struct Player { + name: String, + strength: u8, + hit_points: u8, + } + + fn main() { + let p1 = Player::default(); // Default trait adds `default` constructor. + let mut p2 = p1.clone(); // Clone trait adds `clone` method. + p2.name = String::from("EldurScrollz"); + // Debug trait adds support for printing with `{:?}`. + println!("{p1:?} vs. {p2:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +Derivation is implemented with macros, and many crates provide useful +derive macros to add useful functionality. For example, ``serde`` can +derive serialization support for a struct using +``#[derive(Serialize)]``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst b/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst new file mode 100644 index 000000000..9470d4fe0 --- /dev/null +++ b/courses/comprehensive_rust_training/090_methods_and_traits/04_exercise.rst @@ -0,0 +1,29 @@ +======================== +Exercise: Logger Trait +======================== + +------------------------ +Exercise: Logger Trait +------------------------ + +Let's design a simple logging utility, using a trait ``Logger`` with a +``log`` method. Code which might log its progress can then take an +``&impl Logger``. In testing, this might put messages in the test +logfile, while in a production build it would send messages to a log +server. + +However, the ``StderrLogger`` given below logs all messages, regardless +of verbosity. Your task is to write a ``VerbosityFilter`` type that will +ignore messages above a maximum verbosity. + +This is a common pattern: a struct wrapping a trait implementation and +implementing that same trait, adding behavior in the process. What other +kinds of wrappers might be useful in a logging utility? + +.. code:: rust,compile_fail + + {{#include exercise.rs:setup}} + + // TODO: Define and implement `VerbosityFilter`. + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/100_generics.rst b/courses/comprehensive_rust_training/100_generics.rst new file mode 100644 index 000000000..a08b63e3f --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst new file mode 100644 index 000000000..c997d1650 --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/01_generic_functions.rst @@ -0,0 +1,62 @@ +=================== +Generic Functions +=================== + +------------------- +Generic Functions +------------------- + +Rust supports generics, which lets you abstract algorithms or data +structures (such as sorting or a binary tree) over the types used or +stored. + +.. code:: rust,editable + + /// Pick `even` or `odd` depending on the value of `n`. + fn pick(n: i32, even: T, odd: T) -> T { + if n % 2 == 0 { + even + } else { + odd + } + } + + fn main() { + println!("picked a number: {:?}", pick(97, 222, 333)); + println!("picked a string: {:?}", pick(28, "dog", "cat")); + } + +.. raw:: html + +--------- +Details +--------- + +- Rust infers a type for T based on the types of the arguments and + return value. + +- In this example we only use the primitive types ``i32`` and ``&str`` + for ``T``, but we can use any type here, including user-defined + types: + + .. code:: rust,ignore + + struct Foo { + val: u8, + } + + pick(123, Foo { val: 7 }, Foo { val: 456 }); + +- This is similar to C++ templates, but Rust partially compiles the + generic function immediately, so that function must be valid for all + types matching the constraints. For example, try modifying ``pick`` + to return ``even + odd`` if ``n == 0``. Even if only the ``pick`` + instantiation with integers is used, Rust still considers it invalid. + C++ would let you do this. + +- Generic code is turned into non-generic code based on the call sites. + This is a zero-cost abstraction: you get exactly the same result as + if you had hand-coded the data structures without the abstraction. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/02_generic_data.rst b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst new file mode 100644 index 000000000..e4f10203c --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/02_generic_data.rst @@ -0,0 +1,59 @@ +==================== +Generic Data Types +==================== + +-------------------- +Generic Data Types +-------------------- + +You can use generics to abstract over the concrete field type: + +.. code:: rust,editable + + #[derive(Debug)] + struct Point { + x: T, + y: T, + } + + impl Point { + fn coords(&self) -> (&T, &T) { + (&self.x, &self.y) + } + + fn set_x(&mut self, x: T) { + self.x = x; + } + } + + fn main() { + let integer = Point { x: 5, y: 10 }; + let float = Point { x: 1.0, y: 4.0 }; + println!("{integer:?} and {float:?}"); + println!("coords: {:?}", integer.coords()); + } + +.. raw:: html + +--------- +Details +--------- + +- *Q:* Why ``T`` is specified twice in ``impl Point {}``? Isn't + that redundant? + + - This is because it is a generic implementation section for generic + type. They are independently generic. + - It means these methods are defined for any ``T``. + - It is possible to write ``impl Point { .. }``. + + - ``Point`` is still generic and you can use ``Point``, but + methods in this block will only be available for + ``Point``. + +- Try declaring a new variable ``let p = Point { x: 5, y: 10.0 };``. + Update the code to allow points that have elements of different + types, by using two type variables, e.g., ``T`` and ``U``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst new file mode 100644 index 000000000..fb70d1bd7 --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/03_generic_traits.rst @@ -0,0 +1,60 @@ +================ +Generic Traits +================ + +---------------- +Generic Traits +---------------- + +Traits can also be generic, just like types and functions. A trait's +parameters get concrete types when it is used. + +.. code:: rust,editable + + #[derive(Debug)] + struct Foo(String); + + impl From for Foo { + fn from(from: u32) -> Foo { + Foo(format!("Converted from integer: {from}")) + } + } + + impl From for Foo { + fn from(from: bool) -> Foo { + Foo(format!("Converted from bool: {from}")) + } + } + + fn main() { + let from_int = Foo::from(123); + let from_bool = Foo::from(true); + println!("{from_int:?}, {from_bool:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- The ``From`` trait will be covered later in the course, but its + `definition in the std docs `__ + is simple. + +- Implementations of the trait do not need to cover all possible type + parameters. Here, ``Foo::from("hello")`` would not compile because + there is no ``From<&str>`` implementation for ``Foo``. + +- Generic traits take types as "input", while associated types are a + kind of "output" type. A trait can have multiple implementations for + different input types. + +- In fact, Rust requires that at most one implementation of a trait + match for any type T. Unlike some other languages, Rust has no + heuristic for choosing the "most specific" match. There is work on + adding this support, called + `specialization `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst new file mode 100644 index 000000000..142d3c81b --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/04_trait_bounds.rst @@ -0,0 +1,61 @@ +============== +Trait Bounds +============== + +-------------- +Trait Bounds +-------------- + +When working with generics, you often want to require the types to +implement some trait, so that you can call this trait's methods. + +You can do this with ``T: Trait``: + +.. code:: rust,editable + + fn duplicate(a: T) -> (T, T) { + (a.clone(), a.clone()) + } + + struct NotCloneable; + + fn main() { + let foo = String::from("foo"); + let pair = duplicate(foo); + println!("{pair:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- Try making a ``NonCloneable`` and passing it to ``duplicate``. + +- When multiple traits are necessary, use ``+`` to join them. + +- Show a ``where`` clause, students will encounter it when reading + code. + + .. code:: rust,ignore + + fn duplicate(a: T) -> (T, T) + where + T: Clone, + { + (a.clone(), a.clone()) + } + + - It declutters the function signature if you have many parameters. + - It has additional features making it more powerful. + + - If someone asks, the extra feature is that the type on the left + of ":" can be arbitrary, like ``Option``. + +- Note that Rust does not (yet) support specialization. For example, + given the original ``duplicate``, it is invalid to add a specialized + ``duplicate(a: u32)``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst new file mode 100644 index 000000000..d93da92dd --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/05_impl_trait.rst @@ -0,0 +1,62 @@ +================ +``impl Trait`` +================ + +---------------- +``impl Trait`` +---------------- + +Similar to trait bounds, an ``impl Trait`` syntax can be used in +function arguments and return values: + +.. code:: rust,editable + + // Syntactic sugar for: + // fn add_42_millions>(x: T) -> i32 { + fn add_42_millions(x: impl Into) -> i32 { + x.into() + 42_000_000 + } + + fn pair_of(x: u32) -> impl std::fmt::Debug { + (x + 1, x - 1) + } + + fn main() { + let many = add_42_millions(42_i8); + println!("{many}"); + let many_more = add_42_millions(10_000_000); + println!("{many_more}"); + let debuggable = pair_of(27); + println!("debuggable: {debuggable:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +``impl Trait`` allows you to work with types which you cannot name. The +meaning of ``impl Trait`` is a bit different in the different positions. + +- For a parameter, ``impl Trait`` is like an anonymous generic + parameter with a trait bound. + +- For a return type, it means that the return type is some concrete + type that implements the trait, without naming the type. This can be + useful when you don't want to expose the concrete type in a public + API. + + Inference is hard in return position. A function returning + ``impl Foo`` picks the concrete type it returns, without writing it + out in the source. A function returning a generic type like + ``collect() -> B`` can return any type satisfying ``B``, and the + caller may need to choose one, such as with + ``let x: Vec<_> = foo.collect()`` or with the turbofish, + ``foo.collect::>()``. + +What is the type of ``debuggable``? Try ``let debuggable: () = ..`` to +see what the error message shows. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst new file mode 100644 index 000000000..c64955363 --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/06_dyn_trait.rst @@ -0,0 +1,95 @@ +=============== +``dyn Trait`` +=============== + +--------------- +``dyn Trait`` +--------------- + +In addition to using traits for static dispatch via generics, Rust also +supports using them for type-erased, dynamic dispatch via trait objects: + +.. code:: rust,editable + + struct Dog { + name: String, + age: i8, + } + struct Cat { + lives: i8, + } + + trait Pet { + fn talk(&self) -> String; + } + + impl Pet for Dog { + fn talk(&self) -> String { + format!("Woof, my name is {}!", self.name) + } + } + + impl Pet for Cat { + fn talk(&self) -> String { + String::from("Miau!") + } + } + + // Uses generics and static dispatch. + fn generic(pet: &impl Pet) { + println!("Hello, who are you? {}", pet.talk()); + } + + // Uses type-erasure and dynamic dispatch. + fn dynamic(pet: &dyn Pet) { + println!("Hello, who are you? {}", pet.talk()); + } + + fn main() { + let cat = Cat { lives: 9 }; + let dog = Dog { name: String::from("Fido"), age: 5 }; + + generic(&cat); + generic(&dog); + + dynamic(&cat); + dynamic(&dog); + } + +.. raw:: html + +--------- +Details +--------- + +- Generics, including ``impl Trait``, use monomorphization to create a + specialized instance of the function for each different type that the + generic is instantiated with. This means that calling a trait method + from within a generic function still uses static dispatch, as the + compiler has full type information and can resolve which type's trait + implementation to use. + +- When using ``dyn Trait``, it instead uses dynamic dispatch through a + `virtual method table `__ + (vtable). This means that there's a single version of ``fn dynamic`` + that is used regardless of what type of ``Pet`` is passed in. + +- When using ``dyn Trait``, the trait object needs to be behind some + kind of indirection. In this case it's a reference, though smart + pointer types like ``Box`` can also be used (this will be + demonstrated on day 3). + +- At runtime, a ``&dyn Pet`` is represented as a "fat pointer", i.e. a + pair of two pointers: One pointer points to the concrete object that + implements ``Pet``, and the other points to the vtable for the trait + implementation for that type. When calling the ``talk`` method on + ``&dyn Pet`` the compiler looks up the function pointer for ``talk`` + in the vtable and then invokes the function, passing the pointer to + the ``Dog`` or ``Cat`` into that function. The compiler doesn't need + to know the concrete type of the ``Pet`` in order to do this. + +- A ``dyn Trait`` is considered to be "type-erased", because we no + longer have compile-time knowledge of what the concrete type is. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/100_generics/07_exercise.rst b/courses/comprehensive_rust_training/100_generics/07_exercise.rst new file mode 100644 index 000000000..c9f8d90f2 --- /dev/null +++ b/courses/comprehensive_rust_training/100_generics/07_exercise.rst @@ -0,0 +1,35 @@ +=========================== +Exercise: Generic ``min`` +=========================== + +--------------------------- +Exercise: Generic ``min`` +--------------------------- + +In this short exercise, you will implement a generic ``min`` function +that determines the minimum of two values, using the +`Ord `__ +trait. + +.. code:: rust,compile_fail + + use std::cmp::Ordering; + + // TODO: implement the `min` function used in `main`. + + {{#include exercise.rs:main}} + +.. raw:: html + +--------- +Details +--------- + +- Show students the + `Ord `__ + trait and + `Ordering `__ + enum. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types.rst b/courses/comprehensive_rust_training/110_std_types.rst new file mode 100644 index 000000000..56e4b1186 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/110_std_types/01_std.rst b/courses/comprehensive_rust_training/110_std_types/01_std.rst new file mode 100644 index 000000000..2ca301da9 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/01_std.rst @@ -0,0 +1,22 @@ +================== +Standard Library +================== + +------------------ +Standard Library +------------------ + +Rust comes with a standard library which helps establish a set of common +types used by Rust libraries and programs. This way, two libraries can +work together smoothly because they both use the same ``String`` type. + +In fact, Rust contains several layers of the Standard Library: ``core``, +``alloc`` and ``std``. + +- ``core`` includes the most basic types and functions that don't + depend on ``libc``, allocator or even the presence of an operating + system. +- ``alloc`` includes types which require a global heap allocator, such + as ``Vec``, ``Box`` and ``Arc``. +- Embedded Rust applications often only use ``core``, and sometimes + ``alloc``. diff --git a/courses/comprehensive_rust_training/110_std_types/02_docs.rst b/courses/comprehensive_rust_training/110_std_types/02_docs.rst new file mode 100644 index 000000000..2faa8d586 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/02_docs.rst @@ -0,0 +1,59 @@ +=============== +Documentation +=============== + +--------------- +Documentation +--------------- + +Rust comes with extensive documentation. For example: + +- All of the details about + `loops `__. +- Primitive types like + `u8 `__. +- Standard library types like + `Option `__ + or + `BinaryHeap `__. + +Use ``rustup doc --std`` or https://std.rs to view the documentation. + +In fact, you can document your own code: + +.. code:: rust,editable + + /// Determine whether the first argument is divisible by the second argument. + /// + /// If the second argument is zero, the result is false. + fn is_divisible_by(lhs: u32, rhs: u32) -> bool { + if rhs == 0 { + return false; + } + lhs % rhs == 0 + } + +The contents are treated as Markdown. All published Rust library crates +are automatically documented at `docs.rs `__ using +the `rustdoc `__ +tool. It is idiomatic to document all public items in an API using this +pattern. + +To document an item from inside the item (such as inside a module), use +``//!`` or ``/*! .. */``, called "inner doc comments": + +.. code:: rust,editable + + //! This module contains functionality relating to divisibility of integers. + +.. raw:: html + +--------- +Details +--------- + +- Show students the generated docs for the ``rand`` crate at + https://docs.rs/rand. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/03_option.rst b/courses/comprehensive_rust_training/110_std_types/03_option.rst new file mode 100644 index 000000000..704fb1561 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/03_option.rst @@ -0,0 +1,49 @@ +======== +Option +======== + +-------- +Option +-------- + +We have already seen some use of ``Option``. It stores either a value +of type ``T`` or nothing. For example, +`String::find `__ +returns an ``Option``. + +.. code:: rust,editable,should_panic + + fn main() { + let name = "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); + } + +.. raw:: html + +--------- +Details +--------- + +- ``Option`` is widely used, not just in the standard library. +- ``unwrap`` will return the value in an ``Option``, or panic. + ``expect`` is similar but takes an error message. + + - You can panic on None, but you can't "accidentally" forget to + check for None. + - It's common to ``unwrap``/``expect`` all over the place when + hacking something together, but production code typically handles + ``None`` in a nicer fashion. + +- The "niche optimization" means that ``Option`` often has the same + size in memory as ``T``, if there is some representation that is not + a valid value of T. For example, a reference cannot be NULL, so + ``Option<&T>`` automatically uses NULL to represent the ``None`` + variant, and thus can be stored in the same memory as ``&T``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/04_result.rst b/courses/comprehensive_rust_training/110_std_types/04_result.rst new file mode 100644 index 000000000..ab5840e70 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/04_result.rst @@ -0,0 +1,54 @@ +======== +Result +======== + +-------- +Result +-------- + +``Result`` is similar to ``Option``, but indicates the success or +failure of an operation, each with a different enum variant. It is +generic: ``Result`` where ``T`` is used in the ``Ok`` variant and +``E`` appears in the ``Err`` variant. + +.. code:: rust,editable + + use std::fs::File; + use std::io::Read; + + fn main() { + let file: Result = File::open("diary.txt"); + match file { + Ok(mut file) => { + let mut contents = String::new(); + if let Ok(bytes) = file.read_to_string(&mut contents) { + println!("Dear diary: {contents} ({bytes} bytes)"); + } else { + println!("Could not read file content"); + } + } + Err(err) => { + println!("The diary could not be opened: {err}"); + } + } + } + +.. raw:: html + +--------- +Details +--------- + +- As with ``Option``, the successful value sits inside of ``Result``, + forcing the developer to explicitly extract it. This encourages error + checking. In the case where an error should never happen, + ``unwrap()`` or ``expect()`` can be called, and this is a signal of + the developer intent too. +- ``Result`` documentation is a recommended read. Not during the + course, but it is worth mentioning. It contains a lot of convenience + methods and functions that help functional-style programming. +- ``Result`` is the standard type to implement error handling as we + will see on Day 4. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/05_string.rst b/courses/comprehensive_rust_training/110_std_types/05_string.rst new file mode 100644 index 000000000..1a4c1f364 --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/05_string.rst @@ -0,0 +1,76 @@ +======== +String +======== + +-------- +String +-------- + +`String `__ +is a growable UTF-8 encoded string: + +.. code:: rust,editable + + fn main() { + let mut s1 = String::new(); + s1.push_str("Hello"); + println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity()); + + let mut s2 = String::with_capacity(s1.len() + 1); + s2.push_str(&s1); + s2.push('!'); + println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity()); + + let s3 = String::from("a z"); + println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count()); + } + +``String`` implements +`Deref `__, +which means that you can call all ``str`` methods on a ``String``. + +.. raw:: html + +--------- +Details +--------- + +- ``String::new`` returns a new empty string, use + ``String::with_capacity`` when you know how much data you want to + push to the string. +- ``String::len`` returns the size of the ``String`` in bytes (which + can be different from its length in characters). +- ``String::chars`` returns an iterator over the actual characters. + Note that a ``char`` can be different from what a human will consider + a "character" due to + `grapheme clusters `__. +- When people refer to strings they could either be talking about + ``&str`` or ``String``. +- When a type implements ``Deref``, the compiler will let + you transparently call methods from ``T``. + + - We haven't discussed the ``Deref`` trait yet, so at this point + this mostly explains the structure of the sidebar in the + documentation. + - ``String`` implements ``Deref`` which transparently + gives it access to ``str``\ 's methods. + - Write and compare ``let s3 = s1.deref();`` and ``let s3 = &*s1;``. + +- ``String`` is implemented as a wrapper around a vector of bytes, many + of the operations you see supported on vectors are also supported on + ``String``, but with some extra guarantees. +- Compare the different ways to index a ``String``: + + - To a character by using ``s3.chars().nth(i).unwrap()`` where ``i`` + is in-bound, out-of-bounds. + - To a substring by using ``s3[0..4]``, where that slice is on + character boundaries or not. + +- Many types can be converted to a string with the + `to_string `__ + method. This trait is automatically implemented for all types that + implement ``Display``, so anything that can be formatted can also be + converted to a string. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/06_vec.rst b/courses/comprehensive_rust_training/110_std_types/06_vec.rst new file mode 100644 index 000000000..d72b8726c --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/06_vec.rst @@ -0,0 +1,63 @@ +========= +``Vec`` +========= + +--------- +``Vec`` +--------- + +`Vec `__ is the +standard resizable heap-allocated buffer: + +.. code:: rust,editable + + fn main() { + let mut v1 = Vec::new(); + v1.push(42); + println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity()); + + let mut v2 = Vec::with_capacity(v1.len() + 1); + v2.extend(v1.iter()); + v2.push(9999); + println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity()); + + // Canonical macro to initialize a vector with elements. + let mut v3 = vec![0, 0, 1, 2, 3, 4]; + + // Retain only the even elements. + v3.retain(|x| x % 2 == 0); + println!("{v3:?}"); + + // Remove consecutive duplicates. + v3.dedup(); + println!("{v3:?}"); + } + +``Vec`` implements +`Deref `__, +which means that you can call slice methods on a ``Vec``. + +.. raw:: html + +--------- +Details +--------- + +- ``Vec`` is a type of collection, along with ``String`` and + ``HashMap``. The data it contains is stored on the heap. This means + the amount of data doesn't need to be known at compile time. It can + grow or shrink at runtime. +- Notice how ``Vec`` is a generic type too, but you don't have to + specify ``T`` explicitly. As always with Rust type inference, the + ``T`` was established during the first ``push`` call. +- ``vec![...]`` is a canonical macro to use instead of ``Vec::new()`` + and it supports adding initial elements to the vector. +- To index the vector you use ``[`` ``]``, but they will panic if out + of bounds. Alternatively, using ``get`` will return an ``Option``. + The ``pop`` function will remove the last element. +- Slices are covered on day 3. For now, students only need to know that + a value of type ``Vec`` gives access to all of the documented slice + methods, too. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst new file mode 100644 index 000000000..d3e94dbae --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/07_hashmap.rst @@ -0,0 +1,91 @@ +============= +``HashMap`` +============= + +------------- +``HashMap`` +------------- + +Standard hash map with protection against HashDoS attacks: + +.. code:: rust,editable + + use std::collections::HashMap; + + fn main() { + let mut page_counts = HashMap::new(); + page_counts.insert("Adventures of Huckleberry Finn", 207); + page_counts.insert("Grimms' Fairy Tales", 751); + page_counts.insert("Pride and Prejudice", 303); + + if !page_counts.contains_key("Les 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:#?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- ``HashMap`` is not defined in the prelude and needs to be brought + into scope. + +- Try the following lines of code. The first line will see if a book is + in the hashmap and if not return an alternative value. The second + line will insert the alternative value in the hashmap if the book is + not found. + + .. code:: rust,ignore + + let pc1 = page_counts + .get("Harry Potter and the Sorcerer's Stone") + .unwrap_or(&336); + let pc2 = page_counts + .entry("The Hunger Games") + .or_insert(374); + +- Unlike ``vec!``, there is unfortunately no standard ``hashmap!`` + macro. + + - Although, since Rust 1.56, HashMap implements + `From<[(K, V); N]> `__, + which allows us to easily initialize a hash map from a literal + array: + + .. code:: rust,ignore + + let page_counts = HashMap::from([ + ("Harry Potter and the Sorcerer's Stone".to_string(), 336), + ("The Hunger Games".to_string(), 374), + ]); + +- Alternatively HashMap can be built from any ``Iterator`` which yields + key-value tuples. + +- This type has several "method-specific" return types, such as + ``std::collections::hash_map::Keys``. These types often appear in + searches of the Rust docs. Show students the docs for this type, and + the helpful link back to the ``keys`` method. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/110_std_types/08_exercise.rst b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst new file mode 100644 index 000000000..5b7d91a2d --- /dev/null +++ b/courses/comprehensive_rust_training/110_std_types/08_exercise.rst @@ -0,0 +1,56 @@ +=================== +Exercise: Counter +=================== + +------------------- +Exercise: Counter +------------------- + +In this exercise you will take a very simple data structure and make it +generic. It uses a +`std::collections::HashMap `__ +to keep track of which values have been seen and how many times each one +has appeared. + +The initial version of ``Counter`` is hard coded to only work for +``u32`` values. Make the struct and its methods generic over the type of +value being tracked, that way ``Counter`` can track any type of value. + +If you finish early, try using the +`entry `__ +method to halve the number of hash lookups required to implement the +``count`` method. + +.. code:: rust,compile_fail,editable + + use std::collections::HashMap; + + /// Counter counts the number of times each value of type T has been seen. + struct Counter { + values: HashMap, + } + + impl Counter { + /// Create a new Counter. + fn new() -> Self { + Counter { + values: HashMap::new(), + } + } + + /// Count an occurrence of the given value. + fn count(&mut self, value: u32) { + if self.values.contains_key(&value) { + *self.values.get_mut(&value).unwrap() += 1; + } else { + self.values.insert(value, 1); + } + } + + /// Return the number of times the given value has been seen. + fn times_seen(&self, value: u32) -> u64 { + self.values.get(&value).copied().unwrap_or_default() + } + } + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/120_std_traits.rst b/courses/comprehensive_rust_training/120_std_traits.rst new file mode 100644 index 000000000..7025ac99a --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst new file mode 100644 index 000000000..0c1167a3a --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/01_comparisons.rst @@ -0,0 +1,88 @@ +============= +Comparisons +============= + +------------- +Comparisons +------------- + +These traits support comparisons between values. All traits can be +derived for types containing fields that implement these traits. + +-------------------------- +``PartialEq`` and ``Eq`` +-------------------------- + +``PartialEq`` is a partial equivalence relation, with required method +``eq`` and provided method ``ne``. The ``==`` and ``!=`` operators will +call these methods. + +.. code:: rust,editable + + struct Key { + id: u32, + metadata: Option, + } + impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } + } + +``Eq`` is a full equivalence relation (reflexive, symmetric, and +transitive) and implies ``PartialEq``. Functions that require full +equivalence will use ``Eq`` as a trait bound. + +---------------------------- +``PartialOrd`` and ``Ord`` +---------------------------- + +``PartialOrd`` defines a partial ordering, with a ``partial_cmp`` +method. It is used to implement the ``<``, ``<=``, ``>=``, and ``>`` +operators. + +.. code:: rust,editable + + use std::cmp::Ordering; + #[derive(Eq, PartialEq)] + struct Citation { + author: String, + year: u32, + } + impl PartialOrd for Citation { + fn partial_cmp(&self, other: &Self) -> Option { + match self.author.partial_cmp(&other.author) { + Some(Ordering::Equal) => self.year.partial_cmp(&other.year), + author_ord => author_ord, + } + } + } + +``Ord`` is a total ordering, with ``cmp`` returning ``Ordering``. + +.. raw:: html + +--------- +Details +--------- + +``PartialEq`` can be implemented between different types, but ``Eq`` +cannot, because it is reflexive: + +.. code:: rust,editable + + struct Key { + id: u32, + metadata: Option, + } + impl PartialEq for Key { + fn eq(&self, other: &u32) -> bool { + self.id == *other + } + } + +In practice, it's common to derive these traits, but uncommon to +implement them. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/02_operators.rst b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst new file mode 100644 index 000000000..2e9a7679e --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/02_operators.rst @@ -0,0 +1,66 @@ +=========== +Operators +=========== + +----------- +Operators +----------- + +Operator overloading is implemented via traits in +`std::ops `__: + +.. code:: rust,editable + + #[derive(Debug, Copy, Clone)] + struct Point { + x: i32, + y: i32, + } + + impl std::ops::Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { x: self.x + other.x, y: self.y + other.y } + } + } + + fn main() { + let p1 = Point { x: 10, y: 20 }; + let p2 = Point { x: 100, y: 200 }; + println!("{p1:?} + {p2:?} = {:?}", p1 + p2); + } + +.. raw:: html + +--------- +Details +--------- + +Discussion points: + +- You could implement ``Add`` for ``&Point``. In which situations is + that useful? + + - Answer: ``Add:add`` consumes ``self``. If type ``T`` for which you + are overloading the operator is not ``Copy``, you should consider + overloading the operator for ``&T`` as well. This avoids + unnecessary cloning on the call site. + +- Why is ``Output`` an associated type? Could it be made a type + parameter of the method? + + - Short answer: Function type parameters are controlled by the + caller, but associated types (like ``Output``) are controlled by + the implementer of a trait. + +- You could implement ``Add`` for two different types, e.g. + ``impl Add<(i32, i32)> for Point`` would add a tuple to a ``Point``. + +The ``Not`` trait (``!`` operator) is notable because it does not +"boolify" like the same operator in C-family languages; instead, for +integer types it negates each bit of the number, which arithmetically is +equivalent to subtracting it from -1: ``!5 == -6``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst b/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst new file mode 100644 index 000000000..deb1ff902 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/03_from_and_into.rst @@ -0,0 +1,54 @@ +======================= +``From`` and ``Into`` +======================= + +----------------------- +``From`` and ``Into`` +----------------------- + +Types implement +`From `__ and +`Into `__ to +facilitate type conversions. Unlike ``as``, these traits correspond to +lossless, infallible conversions. + +.. code:: rust,editable + + fn main() { + let s = String::from("hello"); + let addr = std::net::Ipv4Addr::from([127, 0, 0, 1]); + let one = i16::from(true); + let bigger = i32::from(123_i16); + println!("{s}, {addr}, {one}, {bigger}"); + } + +`Into `__ is +automatically implemented when +`From `__ is +implemented: + +.. code:: rust,editable + + fn main() { + let s: String = "hello".into(); + let addr: std::net::Ipv4Addr = [127, 0, 0, 1].into(); + let one: i16 = true.into(); + let bigger: i32 = 123_i16.into(); + println!("{s}, {addr}, {one}, {bigger}"); + } + +.. raw:: html + +--------- +Details +--------- + +- That's why it is common to only implement ``From``, as your type will + get ``Into`` implementation too. +- When declaring a function argument input type like "anything that can + be converted into a ``String``", the rule is opposite, you should use + ``Into``. Your function will accept types that implement ``From`` and + those that *only* implement ``Into``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/04_casting.rst b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst new file mode 100644 index 000000000..010951b57 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/04_casting.rst @@ -0,0 +1,52 @@ +========= +Casting +========= + +--------- +Casting +--------- + +Rust has no *implicit* type conversions, but does support explicit casts +with ``as``. These generally follow C semantics where those are defined. + +.. code:: rust,editable + + fn main() { + let value: i64 = 1000; + println!("as u16: {}", value as u16); + println!("as i16: {}", value as i16); + println!("as u8: {}", value as u8); + } + +The results of ``as`` are *always* defined in Rust and consistent across +platforms. This might not match your intuition for changing sign or +casting to a smaller type - check the docs, and comment for clarity. + +Casting with ``as`` is a relatively sharp tool that is easy to use +incorrectly, and can be a source of subtle bugs as future maintenance +work changes the types that are used or the ranges of values in types. +Casts are best used only when the intent is to indicate unconditional +truncation (e.g. selecting the bottom 32 bits of a ``u64`` with +``as u32``, regardless of what was in the high bits). + +For infallible casts (e.g. ``u32`` to ``u64``), prefer using ``From`` or +``Into`` over ``as`` to confirm that the cast is in fact infallible. For +fallible casts, ``TryFrom`` and ``TryInto`` are available when you want +to handle casts that fit differently from those that don't. + +.. raw:: html + +--------- +Details +--------- + +Consider taking a break after this slide. + +``as`` is similar to a C++ static cast. Use of ``as`` in cases where +data might be lost is generally discouraged, or at least deserves an +explanatory comment. + +This is common in casting integers to ``usize`` for use as an index. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst b/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst new file mode 100644 index 000000000..9c93c7b08 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/05_read_and_write.rst @@ -0,0 +1,51 @@ +======================== +``Read`` and ``Write`` +======================== + +------------------------ +``Read`` and ``Write`` +------------------------ + +Using `Read `__ +and +`BufRead `__, +you can abstract over ``u8`` sources: + +.. code:: rust,editable + + use std::io::{BufRead, BufReader, Read, Result}; + + fn count_lines(reader: R) -> usize { + let buf_reader = BufReader::new(reader); + buf_reader.lines().count() + } + + fn main() -> Result<()> { + let slice: &[u8] = b"foo\nbar\nbaz\n"; + println!("lines in slice: {}", count_lines(slice)); + + let file = std::fs::File::open(std::env::current_exe()?)?; + println!("lines in file: {}", count_lines(file)); + Ok(()) + } + +Similarly, +`Write `__ lets +you abstract over ``u8`` sinks: + +.. code:: rust,editable + + use std::io::{Result, Write}; + + fn log(writer: &mut W, msg: &str) -> Result<()> { + writer.write_all(msg.as_bytes())?; + writer.write_all("\n".as_bytes()) + } + + fn main() -> Result<()> { + let mut buffer = Vec::new(); + log(&mut buffer, "Hello")?; + log(&mut buffer, "World")?; + println!("Logged: {buffer:?}"); + Ok(()) + } diff --git a/courses/comprehensive_rust_training/120_std_traits/06_default.rst b/courses/comprehensive_rust_training/120_std_traits/06_default.rst new file mode 100644 index 000000000..32cd57eee --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/06_default.rst @@ -0,0 +1,64 @@ +======================= +The ``Default`` Trait +======================= + +----------------------- +The ``Default`` Trait +----------------------- + +`Default `__ +trait produces a default value for a type. + +.. code:: rust,editable + + #[derive(Debug, Default)] + struct Derived { + x: u32, + y: String, + z: Implemented, + } + + #[derive(Debug)] + struct Implemented(String); + + impl Default for Implemented { + fn default() -> Self { + Self("John Smith".into()) + } + } + + fn main() { + let default_struct = Derived::default(); + println!("{default_struct:#?}"); + + let almost_default_struct = + Derived { y: "Y is set!".into(), ..Derived::default() }; + println!("{almost_default_struct:#?}"); + + let nothing: Option = None; + println!("{:#?}", nothing.unwrap_or_default()); + } + +.. raw:: html + +--------- +Details +--------- + +- It can be implemented directly or it can be derived via + ``#[derive(Default)]``. +- A derived implementation will produce a value where all fields are + set to their default values. + + - This means all types in the struct must implement ``Default`` too. + +- Standard Rust types often implement ``Default`` with reasonable + values (e.g. ``0``, ``""``, etc). +- The partial struct initialization works nicely with default. +- The Rust standard library is aware that types can implement + ``Default`` and provides convenience methods that use it. +- The ``..`` syntax is called + `struct update syntax `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/07_closures.rst b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst new file mode 100644 index 000000000..1bb5bfd76 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/07_closures.rst @@ -0,0 +1,92 @@ +========== +Closures +========== + +---------- +Closures +---------- + +Closures or lambda expressions have types which cannot be named. +However, they implement special +`Fn `__, +`FnMut `__, and +`FnOnce `__ +traits: + +.. code:: rust,editable + + fn apply_and_log(func: impl FnOnce(i32) -> i32, func_name: &str, input: i32) { + println!("Calling {func_name}({input}): {}", func(input)) + } + + fn main() { + let n = 3; + let add_3 = |x| x + n; + apply_and_log(&add_3, "add_3", 10); + apply_and_log(&add_3, "add_3", 20); + + let mut v = Vec::new(); + let mut accumulate = |x: i32| { + v.push(x); + v.iter().sum::() + }; + apply_and_log(&mut accumulate, "accumulate", 4); + apply_and_log(&mut accumulate, "accumulate", 5); + + let multiply_sum = |x| x * v.into_iter().sum::(); + apply_and_log(multiply_sum, "multiply_sum", 3); + } + +.. raw:: html + +--------- +Details +--------- + +An ``Fn`` (e.g. ``add_3``) neither consumes nor mutates captured values. +It can be called needing only a shared reference to the closure, which +means the closure can be executed repeatedly and even concurrently. + +An ``FnMut`` (e.g. ``accumulate``) might mutate captured values. The +closure object is accessed via exclusive reference, so it can be called +repeatedly but not concurrently. + +If you have an ``FnOnce`` (e.g. ``multiply_sum``), you may only call it +once. Doing so consumes the closure and any values captured by move. + +``FnMut`` is a subtype of ``FnOnce``. ``Fn`` is a subtype of ``FnMut`` +and ``FnOnce``. I.e. you can use an ``FnMut`` wherever an ``FnOnce`` is +called for, and you can use an ``Fn`` wherever an ``FnMut`` or +``FnOnce`` is called for. + +When you define a function that takes a closure, you should take +``FnOnce`` if you can (i.e. you call it once), or ``FnMut`` else, and +last ``Fn``. This allows the most flexibility for the caller. + +In contrast, when you have a closure, the most flexible you can have is +``Fn`` (which can be passed to a consumer of any of the 3 closure +traits), then ``FnMut``, and lastly ``FnOnce``. + +The compiler also infers ``Copy`` (e.g. for ``add_3``) and ``Clone`` +(e.g. ``multiply_sum``), depending on what the closure captures. +Function pointers (references to ``fn`` items) implement ``Copy`` and +``Fn``. + +By default, closures will capture each variable from an outer scope by +the least demanding form of access they can (by shared reference if +possible, then exclusive reference, then by move). The ``move`` keyword +forces capture by value. + +.. code:: rust,editable + + fn make_greeter(prefix: String) -> impl Fn(&str) { + return move |name| println!("{} {}", prefix, name); + } + + fn main() { + let hi = make_greeter("Hi".to_string()); + hi("Greg"); + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst new file mode 100644 index 000000000..05cd87433 --- /dev/null +++ b/courses/comprehensive_rust_training/120_std_traits/08_exercise.rst @@ -0,0 +1,23 @@ +================= +Exercise: ROT13 +================= + +----------------- +Exercise: ROT13 +----------------- + +In this example, you will implement the classic +`"ROT13" cipher `__. Copy this code to the +playground, and implement the missing bits. Only rotate ASCII alphabetic +characters, to ensure the result is still valid UTF-8. + +.. code:: rust,compile_fail + + {{#include exercise.rs:head }} + + // Implement the `Read` trait for `RotDecoder`. + + {{#include exercise.rs:main }} + +What happens if you chain two ``RotDecoder`` instances together, each +rotating by 13 characters? diff --git a/courses/comprehensive_rust_training/130_memory_management.rst b/courses/comprehensive_rust_training/130_memory_management.rst new file mode 100644 index 000000000..328a173c9 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/130_memory_management/01_review.rst b/courses/comprehensive_rust_training/130_memory_management/01_review.rst new file mode 100644 index 000000000..16e775ea7 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/01_review.rst @@ -0,0 +1,90 @@ +========================== +Review of Program Memory +========================== + +-------------------------- +Review of Program Memory +-------------------------- + +Programs allocate memory in two ways: + +- Stack: Continuous area of memory for local variables. + + - Values have fixed sizes known at compile time. + - Extremely fast: just move a stack pointer. + - Easy to manage: follows function calls. + - Great memory locality. + +- Heap: Storage of values outside of function calls. + + - Values have dynamic sizes determined at runtime. + - Slightly slower than the stack: some book-keeping needed. + - No guarantee of memory locality. + +--------- +Example +--------- + +Creating a ``String`` puts fixed-sized metadata on the stack and +dynamically sized data, the actual string, on the heap: + +.. code:: rust,editable + + fn main() { + let s1 = String::from("Hello"); + } + +.. code:: bob + + Stack + .- - - - - - - - - - - - - -. Heap + : : .- - - - - - - - - - - - - - - -. + : s1 : : : + : +-----------+-------+ : : : + : | capacity | 5 | : : +----+----+----+----+----+ : + : | ptr | o-+---+-----+-->| H | e | l | l | o | : + : | len | 5 | : : +----+----+----+----+----+ : + : +-----------+-------+ : : : + : : : : + `- - - - - - - - - - - - - -' `- - - - - - - - - - - - - - - -' + +.. raw:: html + +--------- +Details +--------- + +- Mention that a ``String`` is backed by a ``Vec``, so it has a + capacity and length and can grow if mutable via reallocation on the + heap. + +- If students ask about it, you can mention that the underlying memory + is heap allocated using the + `System Allocator `__ + and custom allocators can be implemented using the + `Allocator API `__ + +----------------- +More to Explore +----------------- + +We can inspect the memory layout with ``unsafe`` Rust. However, you +should point out that this is rightfully unsafe! + +.. code:: rust,editable + + fn main() { + let mut s1 = String::from("Hello"); + s1.push(' '); + s1.push_str("world"); + // DON'T DO THIS AT HOME! For educational purposes only. + // String provides no guarantees about its layout, so this could lead to + // undefined behavior. + unsafe { + let (capacity, ptr, len): (usize, usize, usize) = std::mem::transmute(s1); + println!("capacity = {capacity}, ptr = {ptr:#x}, len = {len}"); + } + } + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst new file mode 100644 index 000000000..9384d7550 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/02_approaches.rst @@ -0,0 +1,66 @@ +================================= +Approaches to Memory Management +================================= + +--------------------------------- +Approaches to Memory Management +--------------------------------- + +Traditionally, languages have fallen into two broad categories: + +- Full control via manual memory management: C, C++, Pascal, ... + + - Programmer decides when to allocate or free heap memory. + - Programmer must determine whether a pointer still points to valid + memory. + - Studies show, programmers make mistakes. + +- Full safety via automatic memory management at runtime: Java, Python, + Go, Haskell, ... + + - A runtime system ensures that memory is not freed until it can no + longer be referenced. + - Typically implemented with reference counting or garbage + collection. + +Rust offers a new mix: + + Full control *and* safety via compile time enforcement of correct + memory management. + +It does this with an explicit ownership concept. + +.. raw:: html + +--------- +Details +--------- + +This slide is intended to help students coming from other languages to +put Rust in context. + +- C must manage heap manually with ``malloc`` and ``free``. Common + errors include forgetting to call ``free``, calling it multiple times + for the same pointer, or dereferencing a pointer after the memory it + points to has been freed. + +- C++ has tools like smart pointers (``unique_ptr``, ``shared_ptr``) + that take advantage of language guarantees about calling destructors + to ensure memory is freed when a function returns. It is still quite + easy to mis-use these tools and create similar bugs to C. + +- Java, Go, and Python rely on the garbage collector to identify memory + that is no longer reachable and discard it. This guarantees that any + pointer can be dereferenced, eliminating use-after-free and other + classes of bugs. But, GC has a runtime cost and is difficult to tune + properly. + +Rust's ownership and borrowing model can, in many cases, get the +performance of C, with alloc and free operations precisely where they +are required - zero cost. It also provides tools similar to C++'s smart +pointers. When required, other options such as reference counting are +available, and there are even crates available to support runtime +garbage collection (not covered in this class). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst new file mode 100644 index 000000000..a196d4dae --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/03_ownership.rst @@ -0,0 +1,45 @@ +=========== +Ownership +=========== + +----------- +Ownership +----------- + +All variable bindings have a *scope* where they are valid and it is an +error to use a variable outside its scope: + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + struct Point(i32, i32); + + fn main() { + { + let p = Point(3, 4); + println!("x: {}", p.0); + } + println!("y: {}", p.1); + } + +We say that the variable *owns* the value. Every Rust value has +precisely one owner at all times. + +At the end of the scope, the variable is *dropped* and the data is +freed. A destructor can run here to free up resources. + +.. raw:: html + +--------- +Details +--------- + +Students familiar with garbage-collection implementations will know that +a garbage collector starts with a set of "roots" to find all reachable +memory. Rust's "single owner" principle is a similar idea. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/04_move.rst b/courses/comprehensive_rust_training/130_memory_management/04_move.rst new file mode 100644 index 000000000..8cdac3d43 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/04_move.rst @@ -0,0 +1,195 @@ +================ +Move Semantics +================ + +---------------- +Move Semantics +---------------- + +An assignment will transfer *ownership* between variables: + +.. code:: rust,editable + + fn main() { + let s1: String = String::from("Hello!"); + let s2: String = s1; + println!("s2: {s2}"); + // println!("s1: {s1}"); + } + +- The assignment of ``s1`` to ``s2`` transfers ownership. +- When ``s1`` goes out of scope, nothing happens: it does not own + anything. +- When ``s2`` goes out of scope, the string data is freed. + +Before move to ``s2``: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -. + : : : : + : s1 : : : + : +-----------+-------+ : : +----+----+----+----+----+----+ : + : | ptr | o---+---+-----+-->| H | e | l | l | o | ! | : + : | len | 6 | : : +----+----+----+----+----+----+ : + : | capacity | 6 | : : : + : +-----------+-------+ : : : + : : `- - - - - - - - - - - - - - - - - - -' + : : + `- - - - - - - - - - - - - -' + +After move to ``s2``: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -. + : : : : + : s1 "(inaccessible)" : : : + : +-----------+-------+ : : +----+----+----+----+----+----+ : + : | ptr | o---+---+--+--+-->| H | e | l | l | o | ! | : + : | len | 6 | : | : +----+----+----+----+----+----+ : + : | capacity | 6 | : | : : + : +-----------+-------+ : | : : + : : | `- - - - - - - - - - - - - - - - - - -' + : s2 : | + : +-----------+-------+ : | + : | ptr | o---+---+--' + : | len | 6 | : + : | capacity | 6 | : + : +-----------+-------+ : + : : + `- - - - - - - - - - - - - -' + +When you pass a value to a function, the value is assigned to the +function parameter. This transfers ownership: + +.. code:: rust,editable + + fn say_hello(name: String) { + println!("Hello {name}") + } + + fn main() { + let name = String::from("Alice"); + say_hello(name); + // say_hello(name); + } + +.. raw:: html + +--------- +Details +--------- + +- Mention that this is the opposite of the defaults in C++, which + copies by value unless you use ``std::move`` (and the move + constructor is defined!). + +- It is only the ownership that moves. Whether any machine code is + generated to manipulate the data itself is a matter of optimization, + and such copies are aggressively optimized away. + +- Simple values (such as integers) can be marked ``Copy`` (see later + slides). + +- In Rust, clones are explicit (by using ``clone``). + +In the ``say_hello`` example: + +- With the first call to ``say_hello``, ``main`` gives up ownership of + ``name``. Afterwards, ``name`` cannot be used anymore within + ``main``. +- The heap memory allocated for ``name`` will be freed at the end of + the ``say_hello`` function. +- ``main`` can retain ownership if it passes ``name`` as a reference + (``&name``) and if ``say_hello`` accepts a reference as a parameter. +- Alternatively, ``main`` can pass a clone of ``name`` in the first + call (``name.clone()``). +- Rust makes it harder than C++ to inadvertently create copies by + making move semantics the default, and by forcing programmers to make + clones explicit. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +-------------------------------- +Defensive Copies in Modern C++ +-------------------------------- + +Modern C++ solves this differently: + +.. code:: cpp + + std::string s1 = "Cpp"; + std::string s2 = s1; // Duplicate the data in s1. + +- The heap data from ``s1`` is duplicated and ``s2`` gets its own + independent copy. +- When ``s1`` and ``s2`` go out of scope, they each free their own + memory. + +Before copy-assignment: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - -. + : : : : + : s1 : : : + : +-----------+-------+ : : +----+----+----+ : + : | ptr | o---+---+--+--+-->| C | p | p | : + : | len | 3 | : : +----+----+----+ : + : | capacity | 3 | : : : + : +-----------+-------+ : : : + : : `- - - - - - - - - - - -' + `- - - - - - - - - - - - - -' + +After copy-assignment: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - -. .- - - - - - - - - - - -. + : : : : + : s1 : : : + : +-----------+-------+ : : +----+----+----+ : + : | ptr | o---+---+--+--+-->| C | p | p | : + : | len | 3 | : : +----+----+----+ : + : | capacity | 3 | : : : + : +-----------+-------+ : : : + : : : : + : s2 : : : + : +-----------+-------+ : : +----+----+----+ : + : | ptr | o---+---+-----+-->| C | p | p | : + : | len | 3 | : : +----+----+----+ : + : | capacity | 3 | : : : + : +-----------+-------+ : : : + : : `- - - - - - - - - - - -' + `- - - - - - - - - - - - - -' + +Key points: + +- C++ has made a slightly different choice than Rust. Because ``=`` + copies data, the string data has to be cloned. Otherwise we would get + a double-free when either string goes out of scope. + +- C++ also has + `std::move `__, + which is used to indicate when a value may be moved from. If the + example had been ``s2 = std::move(s1)``, no heap allocation would + take place. After the move, ``s1`` would be in a valid but + unspecified state. Unlike Rust, the programmer is allowed to keep + using ``s1``. + +- Unlike Rust, ``=`` in C++ can run arbitrary code as determined by the + type which is being copied or moved. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/05_clone.rst b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst new file mode 100644 index 000000000..a9a029b88 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/05_clone.rst @@ -0,0 +1,45 @@ +======= +Clone +======= + +------- +Clone +------- + +Sometimes you *want* to make a copy of a value. The ``Clone`` trait +accomplishes this. + +.. code:: rust,editable + + fn say_hello(name: String) { + println!("Hello {name}") + } + + fn main() { + let name = String::from("Alice"); + say_hello(name.clone()); + say_hello(name); + } + +.. raw:: html + +--------- +Details +--------- + +- The idea of ``Clone`` is to make it easy to spot where heap + allocations are occurring. Look for ``.clone()`` and a few others + like ``vec!`` or ``Box::new``. + +- It's common to "clone your way out" of problems with the borrow + checker, and return later to try to optimize those clones away. + +- ``clone`` generally performs a deep copy of the value, meaning that + if you e.g. clone an array, all of the elements of the array are + cloned as well. + +- The behavior for ``clone`` is user-defined, so it can perform custom + cloning logic if needed. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst new file mode 100644 index 000000000..0e2c21589 --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/06_copy_types.rst @@ -0,0 +1,86 @@ +============ +Copy Types +============ + +------------ +Copy Types +------------ + +While move semantics are the default, certain types are copied by +default: + +.. raw:: html + + + +.. code:: rust,editable + + fn main() { + let x = 42; + let y = x; + println!("x: {x}"); // would not be accessible if not Copy + println!("y: {y}"); + } + +These types implement the ``Copy`` trait. + +You can opt-in your own types to use copy semantics: + +.. raw:: html + + + +.. code:: rust,editable + + #[derive(Copy, Clone, Debug)] + struct Point(i32, i32); + + fn main() { + let p1 = Point(3, 4); + let p2 = p1; + println!("p1: {p1:?}"); + println!("p2: {p2:?}"); + } + +- After the assignment, both ``p1`` and ``p2`` own their own data. +- We can also use ``p1.clone()`` to explicitly copy the data. + +.. raw:: html + +--------- +Details +--------- + +Copying and cloning are not the same thing: + +- Copying refers to bitwise copies of memory regions and does not work + on arbitrary objects. +- Copying does not allow for custom logic (unlike copy constructors in + C++). +- Cloning is a more general operation and also allows for custom + behavior by implementing the ``Clone`` trait. +- Copying does not work on types that implement the ``Drop`` trait. + +In the above example, try the following: + +- Add a ``String`` field to ``struct Point``. It will not compile + because ``String`` is not a ``Copy`` type. +- Remove ``Copy`` from the ``derive`` attribute. The compiler error is + now in the ``println!`` for ``p1``. +- Show that it works if you clone ``p1`` instead. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +- Shared references are ``Copy``/``Clone``, mutable references are not. + This is because Rust requires that mutable references be exclusive, + so while it's valid to make a copy of a shared reference, creating a + copy of a mutable reference would violate Rust's borrowing rules. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/07_drop.rst b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst new file mode 100644 index 000000000..e5d218a6e --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/07_drop.rst @@ -0,0 +1,73 @@ +==================== +The ``Drop`` Trait +==================== + +-------------------- +The ``Drop`` Trait +-------------------- + +Values which implement +`Drop `__ can +specify code to run when they go out of scope: + +.. code:: rust,editable + + struct Droppable { + name: &'static str, + } + + impl Drop for Droppable { + fn drop(&mut self) { + println!("Dropping {}", self.name); + } + } + + fn main() { + let a = Droppable { name: "a" }; + { + let b = Droppable { name: "b" }; + { + let c = Droppable { name: "c" }; + let d = Droppable { name: "d" }; + println!("Exiting block B"); + } + println!("Exiting block A"); + } + drop(a); + println!("Exiting main"); + } + +.. raw:: html + +--------- +Details +--------- + +- Note that ``std::mem::drop`` is not the same as + ``std::ops::Drop::drop``. +- Values are automatically dropped when they go out of scope. +- When a value is dropped, if it implements ``std::ops::Drop`` then its + ``Drop::drop`` implementation will be called. +- All its fields will then be dropped too, whether or not it implements + ``Drop``. +- ``std::mem::drop`` is just an empty function that takes any value. + The significance is that it takes ownership of the value, so at the + end of its scope it gets dropped. This makes it a convenient way to + explicitly drop values earlier than they would otherwise go out of + scope. + + - This can be useful for objects that do some work on ``drop``: + releasing locks, closing files, etc. + +Discussion points: + +- Why doesn't ``Drop::drop`` take ``self``? + + - Short-answer: If it did, ``std::mem::drop`` would be called at the + end of the block, resulting in another call to ``Drop::drop``, and + a stack overflow! + +- Try replacing ``drop(a)`` with ``a.drop()``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst new file mode 100644 index 000000000..869bfbf6e --- /dev/null +++ b/courses/comprehensive_rust_training/130_memory_management/08_exercise.rst @@ -0,0 +1,44 @@ +======================== +Exercise: Builder Type +======================== + +------------------------ +Exercise: Builder Type +------------------------ + +In this example, we will implement a complex data type that owns all of +its data. We will use the "builder pattern" to support building a new +value piece-by-piece, using convenience functions. + +Fill in the missing pieces. + +.. code:: rust,should_panic,editable + + {{#include exercise.rs:Package}} + {{#include exercise.rs:as_dependency}} + todo!("1") + } + } + + {{#include exercise.rs:PackageBuilder}} + {{#include exercise.rs:new}} + todo!("2") + } + + {{#include exercise.rs:version}} + + {{#include exercise.rs:authors}} + todo!("3") + } + + {{#include exercise.rs:dependency}} + todo!("4") + } + + {{#include exercise.rs:language}} + todo!("5") + } + + {{#include exercise.rs:build}} + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/140_smart_pointers.rst b/courses/comprehensive_rust_training/140_smart_pointers.rst new file mode 100644 index 000000000..9ff353a0b --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst new file mode 100644 index 000000000..0da5865ff --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers/01_box.rst @@ -0,0 +1,105 @@ +============ +``Box`` +============ + +------------ +``Box`` +------------ + +`Box `__ is an +owned pointer to data on the heap: + +.. code:: rust,editable + + fn main() { + let five = Box::new(5); + println!("five: {}", *five); + } + +.. code:: bob + + Stack Heap + .- - - - - - -. .- - - - - - -. + : : : : + : five : : : + : +-----+ : : +-----+ : + : | o---|---+-----+-->| 5 | : + : +-----+ : : +-----+ : + : : : : + : : : : + `- - - - - - -' `- - - - - - -' + +``Box`` implements ``Deref``, which means that you can +`call methods from T directly on a Box `__. + +Recursive data types or data types with dynamic sizes cannot be stored +inline without a pointer indirection. ``Box`` accomplishes that +indirection: + +.. code:: rust,editable + + #[derive(Debug)] + enum List { + /// A non-empty list: first element and the rest of the list. + Element(T, Box>), + /// An empty list. + Nil, + } + + fn main() { + let list: List = + List::Element(1, Box::new(List::Element(2, Box::new(List::Nil)))); + println!("{list:?}"); + } + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - - . .- - - - - - - - - - - - - - - - - - - - - - - - -. + : : : : + : list : : : + : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : + : | Element | 1 | o--+----+-----+--->| Element | 2 | o--+--->| Nil | // | // | : + : +---------+----+----+ : : +---------+----+----+ +------+----+----+ : + : : : : + : : : : + '- - - - - - - - - - - - - - ' '- - - - - - - - - - - - - - - - - - - - - - - - -' + +.. raw:: html + +--------- +Details +--------- + +- ``Box`` is like ``std::unique_ptr`` in C++, except that it's + guaranteed to be not null. + +- A ``Box`` can be useful when you: + + - have a type whose size can't be known at compile time, but the + Rust compiler wants to know an exact size. + - want to transfer ownership of a large amount of data. To avoid + copying large amounts of data on the stack, instead store the data + on the heap in a ``Box`` so only the pointer is moved. + +- If ``Box`` was not used and we attempted to embed a ``List`` directly + into the ``List``, the compiler would not be able to compute a fixed + size for the struct in memory (the ``List`` would be of infinite + size). + +- ``Box`` solves this problem as it has the same size as a regular + pointer and just points at the next element of the ``List`` in the + heap. + +- Remove the ``Box`` in the List definition and show the compiler + error. We get the message "recursive without indirection", because + for data recursion, we have to use indirection, a ``Box`` or + reference of some kind, instead of storing the value directly. + +- Though ``Box`` looks like ``std::unique_ptr`` in C++, it cannot be + empty/null. This makes ``Box`` one of the types that allow the + compiler to optimize storage of some enums (the "niche + optimization"). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst new file mode 100644 index 000000000..509db1b42 --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers/02_rc.rst @@ -0,0 +1,52 @@ +======== +``Rc`` +======== + +-------- +``Rc`` +-------- + +`Rc `__ is a +reference-counted shared pointer. Use this when you need to refer to the +same data from multiple places: + +.. code:: rust,editable + + use std::rc::Rc; + + fn main() { + let a = Rc::new(10); + let b = Rc::clone(&a); + + println!("a: {a}"); + println!("b: {b}"); + } + +- See ```Arc`` <../concurrency/shared-state/arc.md>`__ and + `Mutex `__ + if you are in a multi-threaded context. +- You can *downgrade* a shared pointer into a + `Weak `__ + pointer to create cycles that will get dropped. + +.. raw:: html + +--------- +Details +--------- + +- ``Rc``\ 's count ensures that its contained value is valid for as + long as there are references. +- ``Rc`` in Rust is like ``std::shared_ptr`` in C++. +- ``Rc::clone`` is cheap: it creates a pointer to the same allocation + and increases the reference count. Does not make a deep clone and can + generally be ignored when looking for performance issues in code. +- ``make_mut`` actually clones the inner value if necessary + ("clone-on-write") and returns a mutable reference. +- Use ``Rc::strong_count`` to check the reference count. +- ``Rc::downgrade`` gives you a *weakly reference-counted* object to + create cycles that will be dropped properly (likely in combination + with ``RefCell``). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst b/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst new file mode 100644 index 000000000..1a64da882 --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers/03_trait_objects.rst @@ -0,0 +1,116 @@ +===================== +Owned Trait Objects +===================== + +--------------------- +Owned Trait Objects +--------------------- + +We previously saw how trait objects can be used with references, e.g +``&dyn Pet``. However, we can also use trait objects with smart pointers +like ``Box`` to create an owned trait object: ``Box``. + +.. code:: rust,editable + + struct Dog { + name: String, + age: i8, + } + struct Cat { + lives: i8, + } + + trait Pet { + fn talk(&self) -> String; + } + + impl Pet for Dog { + fn talk(&self) -> String { + format!("Woof, my name is {}!", self.name) + } + } + + impl Pet for Cat { + fn talk(&self) -> String { + String::from("Miau!") + } + } + + fn main() { + let pets: Vec> = vec![ + Box::new(Cat { lives: 9 }), + Box::new(Dog { name: String::from("Fido"), age: 5 }), + ]; + for pet in pets { + println!("Hello, who are you? {}", pet.talk()); + } + } + +Memory layout after allocating ``pets``: + +.. code:: bob + + Stack Heap + .- - - - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - -. + : : : : + : "pets: Vec>" : : "data: Cat" +----+----+----+----+ : + : +-----------+-------+ : : +-------+-------+ | F | i | d | o | : + : | ptr | o---+-------+--. : | lives | 9 | +----+----+----+----+ : + : | len | 2 | : | : +-------+-------+ ^ : + : | capacity | 2 | : | : ^ | : + : +-----------+-------+ : | : | '-------. : + : : | : | data:"Dog"| : + : : | : | +-------+--|-------+ : + `- - - - - - - - - - - - - - - -' | : +---|-+-----+ | name | o, 4, 4 | : + `--+-->| o o | o o-|----->| age | 5 | : + : +-|---+-|---+ +-------+----------+ : + : | | : + `- - -| - - |- - - - - - - - - - - - - - - - -' + | | + | | "Program text" + .- - -| - - |- - - - - - - - - - - - - - - - -. + : | | vtable : + : | | +----------------------+ : + : | `----->| "::talk" | : + : | +----------------------+ : + : | vtable : + : | +----------------------+ : + : '----------->| "::talk" | : + : +----------------------+ : + : : + '- - - - - - - - - - - - - - - - - - - - - - -' + +.. raw:: html + +--------- +Details +--------- + +- Types that implement a given trait may be of different sizes. This + makes it impossible to have things like ``Vec`` in the + example above. + +- ``dyn Pet`` is a way to tell the compiler about a dynamically sized + type that implements ``Pet``. + +- In the example, ``pets`` is allocated on the stack and the vector + data is on the heap. The two vector elements are *fat pointers*: + + - A fat pointer is a double-width pointer. It has two components: a + pointer to the actual object and a pointer to the + `virtual method table `__ + (vtable) for the ``Pet`` implementation of that particular object. + - The data for the ``Dog`` named Fido is the ``name`` and ``age`` + fields. The ``Cat`` has a ``lives`` field. + +- Compare these outputs in the above example: + + .. code:: rust,ignore + + println!("{} {}", std::mem::size_of::(), std::mem::size_of::()); + println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>()); + println!("{}", std::mem::size_of::<&dyn Pet>()); + println!("{}", std::mem::size_of::>()); + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst new file mode 100644 index 000000000..e9bcba20f --- /dev/null +++ b/courses/comprehensive_rust_training/140_smart_pointers/04_exercise.rst @@ -0,0 +1,26 @@ +======================= +Exercise: Binary Tree +======================= + +----------------------- +Exercise: Binary Tree +----------------------- + +A binary tree is a tree-type data structure where every node has two +children (left and right). We will create a tree where each node stores +a value. For a given node N, all nodes in a N's left subtree contain +smaller values, and all nodes in N's right subtree will contain larger +values. + +Implement the following types, so that the given tests pass. + +Extra Credit: implement an iterator over a binary tree that returns the +values in order. + +.. code:: rust,editable,ignore + + {{#include exercise.rs:types}} + + // Implement `new`, `insert`, `len`, and `has` for `Subtree`. + + {{#include exercise.rs:tests}} diff --git a/courses/comprehensive_rust_training/150_borrowing.rst b/courses/comprehensive_rust_training/150_borrowing.rst new file mode 100644 index 000000000..17b9f5f84 --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/150_borrowing/01_shared.rst b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst new file mode 100644 index 000000000..b66dcef5d --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/01_shared.rst @@ -0,0 +1,96 @@ +=================== +Borrowing a Value +=================== + +------------------- +Borrowing a Value +------------------- + +As we saw before, instead of transferring ownership when calling a +function, you can let a function *borrow* the value: + +.. raw:: html + + + +.. code:: rust,editable + + #[derive(Debug)] + struct Point(i32, i32); + + fn add(p1: &Point, p2: &Point) -> Point { + Point(p1.0 + p2.0, p1.1 + p2.1) + } + + fn main() { + let p1 = Point(3, 4); + let p2 = Point(10, 20); + let p3 = add(&p1, &p2); + println!("{p1:?} + {p2:?} = {p3:?}"); + } + +- The ``add`` function *borrows* two points and returns a new point. +- The caller retains ownership of the inputs. + +.. raw:: html + +--------- +Details +--------- + +This slide is a review of the material on references from day 1, +expanding slightly to include function arguments and return values. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +Notes on stack returns and inlining: + +- Demonstrate that the return from ``add`` is cheap because the + compiler can eliminate the copy operation, by inlining the call to + add into main. Change the above code to print stack addresses and run + it on the + `Playground `__ + or look at the assembly in `Godbolt `__. + In the "DEBUG" optimization level, the addresses should change, while + they stay the same when changing to the "RELEASE" setting: + + .. raw:: html + + + + .. code:: rust,editable + + #[derive(Debug)] + struct Point(i32, i32); + + fn add(p1: &Point, p2: &Point) -> Point { + let p = Point(p1.0 + p2.0, p1.1 + p2.1); + println!("&p.0: {:p}", &p.0); + p + } + + pub fn main() { + let p1 = Point(3, 4); + let p2 = Point(10, 20); + let p3 = add(&p1, &p2); + println!("&p3.0: {:p}", &p3.0); + println!("{p1:?} + {p2:?} = {p3:?}"); + } + +- The Rust compiler can do automatic inlining, that can be disabled on + a function level with ``#[inline(never)]``. + +- Once disabled, the printed address will change on all optimization + levels. Looking at Godbolt or Playground, one can see that in this + case, the return of the value depends on the ABI, e.g. on amd64 the + two i32 that is making up the point will be returned in 2 registers + (eax and edx). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst new file mode 100644 index 000000000..d17e15bf7 --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/02_borrowck.rst @@ -0,0 +1,83 @@ +================= +Borrow Checking +================= + +----------------- +Borrow Checking +----------------- + +Rust's *borrow checker* puts constraints on the ways you can borrow +values. We've already seen that a reference cannot *outlive* the value +it borrows: + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + fn main() { + let x_ref = { + let x = 10; + &x + }; + println!("x: {x_ref}"); + } + +There's also a second main rule that the borrow checker enforces: The +*aliasing* rule. For a given value, at any time: + +- You can have one or more shared references to the value, *or* +- You can have exactly one exclusive reference to the value. + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + fn main() { + let mut a: i32 = 10; + let b: &i32 = &a; + + { + let c: &mut i32 = &mut a; + *c = 20; + } + + println!("a: {a}"); + println!("b: {b}"); + } + +.. raw:: html + +--------- +Details +--------- + +- The "outlives" rule was demonstrated previously when we first looked + at references. We review it here to show students that the borrow + checking is following a few different rules to validate borrowing. +- Note that the requirement is that conflicting references not *exist* + at the same point. It does not matter where the reference is + dereferenced. +- The above code does not compile because ``a`` is borrowed as mutable + (through ``c``) and as immutable (through ``b``) at the same time. +- Move the ``println!`` statement for ``b`` before the scope that + introduces ``c`` to make the code compile. +- After that change, the compiler realizes that ``b`` is only ever used + before the new mutable borrow of ``a`` through ``c``. This is a + feature of the borrow checker called "non-lexical lifetimes". +- The exclusive reference constraint is quite strong. Rust uses it to + ensure that data races do not occur. Rust also *relies* on this + constraint to optimize code. For example, a value behind a shared + reference can be safely cached in a register for the lifetime of that + reference. +- The borrow checker is designed to accommodate many common patterns, + such as taking exclusive references to different fields in a struct + at the same time. But, there are some situations where it doesn't + quite "get it" and this often results in "fighting with the borrow + checker." + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/150_borrowing/03_examples.rst b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst new file mode 100644 index 000000000..406bf994d --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/03_examples.rst @@ -0,0 +1,44 @@ +=============== +Borrow Errors +=============== + +--------------- +Borrow Errors +--------------- + +As a concrete example of how these borrowing rules prevent memory +errors, consider the case of modifying a collection while there are +references to its elements: + +.. code:: rust,editable,compile_fail + + fn main() { + let mut vec = vec![1, 2, 3, 4, 5]; + let elem = &vec[2]; + vec.push(6); + println!("{elem}"); + } + +Similarly, consider the case of iterator invalidation: + +.. code:: rust,editable,compile_fail + + fn main() { + let mut vec = vec![1, 2, 3, 4, 5]; + for elem in &vec { + vec.push(elem * 2); + } + } + +.. raw:: html + +--------- +Details +--------- + +- In both of these cases, modifying the collection by pushing new + elements into it can potentially invalidate existing references to + the collection's elements if the collection has to reallocate. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst new file mode 100644 index 000000000..16c1e2002 --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/04_interior_mutability.rst @@ -0,0 +1,30 @@ +===================== +Interior Mutability +===================== + +--------------------- +Interior Mutability +--------------------- + +In some situations, it's necessary to modify data behind a shared +(read-only) reference. For example, a shared data structure might have +an internal cache, and wish to update that cache from read-only methods. + +The "interior mutability" pattern allows exclusive (mutable) access +behind a shared reference. The standard library provides several ways to +do this, all while still ensuring safety, typically by performing a +runtime check. + +.. raw:: html + +--------- +Details +--------- + +The main thing to take away from this slide is that Rust provides *safe* +ways to modify data behind a shared reference. There are a variety of +ways to ensure that safety, and the next sub-slides present a few of +them. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst new file mode 100644 index 000000000..bae5268bd --- /dev/null +++ b/courses/comprehensive_rust_training/150_borrowing/05_exercise.rst @@ -0,0 +1,28 @@ +============================= +Exercise: Health Statistics +============================= + +----------------------------- +Exercise: Health Statistics +----------------------------- + +{{#include ../../third_party/rust-on-exercism/health-statistics.md}} + +Copy the code below to https://play.rust-lang.org/ and fill in the +missing method: + +.. code:: rust + + // TODO: remove this when you're done with your implementation. + #![allow(unused_variables, dead_code)] + + {{#include ../../third_party/rust-on-exercism/health-statistics.rs:setup}} + + {{#include ../../third_party/rust-on-exercism/health-statistics.rs:User_visit_doctor}} + todo!("Update a user's statistics based on measurements from a visit to the doctor") + } + } + + {{#include ../../third_party/rust-on-exercism/health-statistics.rs:main}} + + {{#include ../../third_party/rust-on-exercism/health-statistics.rs:tests}} diff --git a/courses/comprehensive_rust_training/160_lifetimes.rst b/courses/comprehensive_rust_training/160_lifetimes.rst new file mode 100644 index 000000000..e2fc4be60 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst new file mode 100644 index 000000000..3bb19ba62 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes/01_lifetime_annotations.rst @@ -0,0 +1,76 @@ +====================== +Lifetime Annotations +====================== + +---------------------- +Lifetime Annotations +---------------------- + +A reference has a *lifetime*, which must not "outlive" the value it +refers to. This is verified by the borrow checker. + +The lifetime can be implicit - this is what we have seen so far. +Lifetimes can also be explicit: ``&'a Point``, ``&'document str``. +Lifetimes start with ``'`` and ``'a`` is a typical default name. Read +``&'a Point`` as "a borrowed ``Point`` which is valid for at least the +lifetime ``a``". + +Lifetimes are always inferred by the compiler: you cannot assign a +lifetime yourself. Explicit lifetime annotations create constraints +where there is ambiguity; the compiler verifies that there is a valid +solution. + +Lifetimes become more complicated when considering passing values to and +returning values from functions. + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + #[derive(Debug)] + struct Point(i32, i32); + + fn left_most(p1: &Point, p2: &Point) -> &Point { + if p1.0 < p2.0 { + p1 + } else { + p2 + } + } + + fn main() { + let p1: Point = Point(10, 10); + let p2: Point = Point(20, 20); + let p3 = left_most(&p1, &p2); // What is the lifetime of p3? + println!("p3: {p3:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +In this example, the compiler does not know what lifetime to infer for +``p3``. Looking inside the function body shows that it can only safely +assume that ``p3``\ 's lifetime is the shorter of ``p1`` and ``p2``. But +just like types, Rust requires explicit annotations of lifetimes on +function arguments and return values. + +Add ``'a`` appropriately to ``left_most``: + +.. code:: rust,ignore + + fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { + +This says, "given p1 and p2 which both outlive ``'a``, the return value +lives for at least ``'a``. + +In common cases, lifetimes can be elided, as described on the next +slide. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst new file mode 100644 index 000000000..2f729b005 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes/02_lifetime_elision.rst @@ -0,0 +1,78 @@ +============================= +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 + ``self``, that lifetime is given to all un-annotated return values. + +.. code:: rust,editable + + #[derive(Debug)] + struct Point(i32, i32); + + fn cab_distance(p1: &Point, p2: &Point) -> i32 { + (p1.0 - p2.0).abs() + (p1.1 - p2.1).abs() + } + + fn nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> { + let mut nearest = None; + for p in points { + if let Some((_, nearest_dist)) = nearest { + let dist = cab_distance(p, query); + if dist < nearest_dist { + nearest = Some((p, dist)); + } + } else { + nearest = Some((p, cab_distance(p, query))); + }; + } + nearest.map(|(p, _)| p) + } + + fn main() { + let points = &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1)]; + println!("{:?}", nearest(points, &Point(0, 2))); + } + +.. raw:: html + +--------- +Details +--------- + +In this example, ``cab_distance`` is trivially elided. + +The ``nearest`` function provides another example of a function with +multiple references in its arguments that requires explicit annotation. + +Try adjusting the signature to "lie" about the lifetimes returned: + +.. code:: rust,ignore + + fn nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> { + +This won't compile, demonstrating that the annotations are checked for +validity by the compiler. Note that this is not the case for raw +pointers (unsafe), and this is a common source of errors with unsafe +Rust. + +Students may ask when to use lifetimes. Rust borrows *always* have +lifetimes. Most of the time, elision and type inference mean these don't +need to be written out. In more complicated cases, lifetime annotations +can help resolve ambiguity. Often, especially when prototyping, it's +easier to just work with owned data by cloning values where necessary. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst new file mode 100644 index 000000000..6b8a6bfd4 --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes/03_struct_lifetimes.rst @@ -0,0 +1,52 @@ +============================== +Lifetimes in Data Structures +============================== + +------------------------------ +Lifetimes in Data Structures +------------------------------ + +If a data type stores borrowed data, it must be annotated with a +lifetime: + +.. code:: rust,editable + + #[derive(Debug)] + struct Highlight<'doc>(&'doc str); + + fn erase(text: String) { + println!("Bye {text}!"); + } + + fn main() { + let text = String::from("The quick brown fox jumps over the lazy dog."); + let fox = Highlight(&text[4..19]); + let dog = Highlight(&text[35..43]); + // erase(text); + println!("{fox:?}"); + println!("{dog:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- In the above example, the annotation on ``Highlight`` enforces that + the data underlying the contained ``&str`` lives at least as long as + any instance of ``Highlight`` that uses that data. +- If ``text`` is consumed before the end of the lifetime of ``fox`` (or + ``dog``), the borrow checker throws an error. +- Types with borrowed data force users to hold on to the original data. + This can be useful for creating lightweight views, but it generally + makes them somewhat harder to use. +- When possible, make data structures own their data directly. +- Some structs with multiple references inside can have more than one + lifetime annotation. This can be necessary if there is a need to + describe lifetime relationships between the references themselves, in + addition to the lifetime of the struct itself. Those are very + advanced use cases. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst new file mode 100644 index 000000000..1c82dd6cc --- /dev/null +++ b/courses/comprehensive_rust_training/160_lifetimes/04_exercise.rst @@ -0,0 +1,120 @@ +============================ +Exercise: Protobuf Parsing +============================ + +---------------------------- +Exercise: Protobuf Parsing +---------------------------- + +In this exercise, you will build a parser for the +`protobuf binary encoding `__. Don't +worry, it's simpler than it seems! This illustrates a common parsing +pattern, passing slices of data. The underlying data itself is never +copied. + +Fully parsing a protobuf message requires knowing the types of the +fields, indexed by their field numbers. That is typically provided in a +``proto`` file. In this exercise, we'll encode that information into +``match`` statements in functions that get called for each field. + +We'll use the following proto: + +.. code:: proto + + message PhoneNumber { + optional string number = 1; + optional string type = 2; + } + + message Person { + optional string name = 1; + optional int32 id = 2; + repeated PhoneNumber phones = 3; + } + +---------- +Messages +---------- + +A proto message is encoded as a series of fields, one after the next. +Each is implemented as a "tag" followed by the value. The tag contains a +field number (e.g., ``2`` for the ``id`` field of a ``Person`` message) +and a wire type defining how the payload should be determined from the +byte stream. These are combined into a single integer, as decoded in +``unpack_tag`` below. + +-------- +Varint +-------- + +Integers, including the tag, are represented with a variable-length +encoding called VARINT. Luckily, ``parse_varint`` is defined for you +below. + +------------ +Wire Types +------------ + +Proto defines several wire types, only two of which are used in this +exercise. + +The ``Varint`` wire type contains a single varint, and is used to encode +proto values of type ``int32`` such as ``Person.id``. + +The ``Len`` wire type contains a length expressed as a varint, followed +by a payload of that number of bytes. This is used to encode proto +values of type ``string`` such as ``Person.name``. It is also used to +encode proto values containing sub-messages such as ``Person.phones``, +where the payload contains an encoding of the sub-message. + +---------- +Exercise +---------- + +The given code also defines callbacks to handle ``Person`` and +``PhoneNumber`` fields, and to parse a message into a series of calls to +those callbacks. + +What remains for you is to implement the ``parse_field`` function and +the ``ProtoMessage`` trait for ``Person`` and ``PhoneNumber``. + +.. raw:: html + + + +.. code:: rust,editable,compile_fail + + {{#include exercise.rs:preliminaries }} + + + {{#include exercise.rs:parse_field }} + _ => todo!("Based on the wire type, build a Field, consuming as many bytes as necessary.") + }; + todo!("Return the field, and any un-consumed bytes.") + } + + {{#include exercise.rs:parse_message }} + + {{#include exercise.rs:message_phone_number_type}} + + {{#include exercise.rs:message_person_type}} + + // TODO: Implement ProtoMessage for Person and PhoneNumber. + + {{#include exercise.rs:main }} + +.. raw:: html + +--------- +Details +--------- + +- In this exercise there are various cases where protobuf parsing might + fail, e.g. if you try to parse an ``i32`` when there are fewer than 4 + bytes left in the data buffer. In normal Rust code we'd handle this + with the ``Result`` enum, but for simplicity in this exercise we + panic if any errors are encountered. On day 4 we'll cover error + handling in Rust in more detail. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators.rst b/courses/comprehensive_rust_training/170_iterators.rst new file mode 100644 index 000000000..b3c922f17 --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/170_iterators/01_motivation.rst b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst new file mode 100644 index 000000000..2d4938485 --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/01_motivation.rst @@ -0,0 +1,73 @@ +====================== +Motivating Iterators +====================== + +---------------------- +Motivating Iterators +---------------------- + +If you want to iterate over the contents of an array, you'll need to +define: + +- Some state to keep track of where you are in the iteration process, + e.g. an index. +- A condition to determine when iteration is done. +- Logic for updating the state of iteration each loop. +- Logic for fetching each element using that iteration state. + +In a C-style for loop you declare these things directly: + +.. code:: c,editable + + for (int i = 0; i < array_len; i += 1) { + int elem = array[i]; + } + +In Rust we bundle this state and logic together into an object known as +an "iterator". + +.. raw:: html + +--------- +Details +--------- + +- This slide provides context for what Rust iterators do under the + hood. We use the (hopefully) familiar construct of a C-style ``for`` + loop to show how iteration requires some state and some logic, that + way on the next slide we can show how an iterator bundles these + together. + +- Rust doesn't have a C-style ``for`` loop, but we can express the same + thing with ``while``: + + .. code:: rust,editable + + let array = [2, 4, 6, 8]; + let mut i = 0; + while i < array.len() { + let elem = array[i]; + i += 1; + } + +----------------- +More to Explore +----------------- + +There's another way to express array iteration using ``for`` in C and +C++: You can use a pointer to the front and a pointer to the end of the +array and then compare those pointers to determine when the loop should +end. + +.. code:: c,editable + + for (int *ptr = array; ptr < array + len; ptr += 1) { + int elem = *ptr; + } + +If students ask, you can point out that this is how Rust's slice and +array iterators work under the hood (though implemented as a Rust +iterator). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/02_iterator.rst b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst new file mode 100644 index 000000000..ef9dc7db5 --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/02_iterator.rst @@ -0,0 +1,78 @@ +==================== +``Iterator`` Trait +==================== + +-------------------- +``Iterator`` Trait +-------------------- + +The +`Iterator `__ +trait defines how an object can be used to produce a sequence of values. +For example, if we wanted to create an iterator that can produce the +elements of a slice it might look something like this: + +.. code:: rust,editable + + struct SliceIter<'s> { + slice: &'s [i32], + i: usize, + } + + impl<'s> Iterator for SliceIter<'s> { + type Item = &'s i32; + + fn next(&mut self) -> Option { + if self.i == self.slice.len() { + None + } else { + let next = &self.slice[self.i]; + self.i += 1; + Some(next) + } + } + } + + fn main() { + let slice = &[2, 4, 6, 8]; + let iter = SliceIter { slice, i: 0 }; + for elem in iter { + println!("elem: {elem}"); + } + } + +.. raw:: html + +--------- +Details +--------- + +- The ``SliceIter`` example implements the same logic as the C-style + ``for`` loop demonstrated on the last slide. + +- Point out to the students that iterators are lazy: Creating the + iterator just initializes the struct but does not otherwise do any + work. No work happens until the ``next`` method is called. + +- Iterators don't need to be finite! It's entirely valid to have an + iterator that will produce values forever. For example, a half open + range like ``0..`` will keep going until integer overflow occurs. + +----------------- +More to Explore +----------------- + +- The "real" version of ``SliceIter`` is the + `slice::Iter `__ + type in the standard library, however the real version uses pointers + under the hood instead of an index in order to eliminate bounds + checks. + +- The ``SliceIter`` example is a good example of a struct that contains + a reference and therefore uses lifetime annotations. + +- You can also demonstrate adding a generic parameter to ``SliceIter`` + to allow it to work with any kind of slice (not just ``&[i32]``). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/03_helpers.rst b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst new file mode 100644 index 000000000..1246fd39d --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/03_helpers.rst @@ -0,0 +1,52 @@ +============================= +``Iterator`` Helper Methods +============================= + +----------------------------- +``Iterator`` Helper Methods +----------------------------- + +In addition to the ``next`` method that defines how an iterator behaves, +the ``Iterator`` trait provides 70+ helper methods that can be used to +build customized iterators. + +.. code:: rust,editable + + let result: i32 = (1..=10) // Create a range from 1 to 10 + .filter(|&x| x % 2 == 0) // Keep only even numbers + .map(|x| x * x) // Square each number + .sum(); // Sum up all the squared numbers + + println!("The sum of squares of even numbers from 1 to 10 is: {}", result); + +.. raw:: html + +--------- +Details +--------- + +- The ``Iterator`` trait implements many common functional programming + operations over collections (e.g. ``map``, ``filter``, ``reduce``, + etc). This is the trait where you can find all the documentation + about them. + +- Many of these helper methods take the original iterator and produce a + new iterator with different behavior. These are know as "iterator + adapter methods". + +- Some methods, like ``sum`` and ``count``, consume the iterator and + pull all of the elements out of it. + +- These methods are designed to be chained together so that it's easy + to build a custom iterator that does exactly what you need. + +----------------- +More to Explore +----------------- + +- Rust's iterators are extremely efficient and highly optimizable. Even + complex iterators made by combining many adapter methods will still + result in code as efficient as equivalent imperative implementations. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/04_collect.rst b/courses/comprehensive_rust_training/170_iterators/04_collect.rst new file mode 100644 index 000000000..04c73ddd6 --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/04_collect.rst @@ -0,0 +1,61 @@ +============= +``collect`` +============= + +------------- +``collect`` +------------- + +The +`collect `__ +method lets you build a collection from an +`Iterator `__. + +.. code:: rust,editable + + fn main() { + let primes = vec![2, 3, 5, 7]; + let prime_squares = primes.into_iter().map(|p| p * p).collect::>(); + println!("prime_squares: {prime_squares:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +- Any iterator can be collected in to a ``Vec``, ``VecDeque``, or + ``HashSet``. Iterators that produce key-value pairs (i.e. a + two-element tuple) can also be collected into ``HashMap`` and + ``BTreeMap``. + +Show the students the definition for ``collect`` in the standard library +docs. There are two ways to specify the generic type ``B`` for this +method: + +- With the "turbofish": ``some_iterator.collect::()``, + as shown. The ``_`` shorthand used here lets Rust infer the type of + the ``Vec`` elements. +- With type inference: + ``let prime_squares: Vec<_> = some_iterator.collect()``. Rewrite the + example to use this form. + +----------------- +More to Explore +----------------- + +- If students are curious about how this works, you can bring up the + `FromIterator `__ + trait, which defines how each type of collection gets built from an + iterator. +- In addition to the basic implementations of ``FromIterator`` for + ``Vec``, ``HashMap``, etc., there are also more specialized + implementations which let you do cool things like convert an + ``Iterator>`` into a ``Result, E>``. +- The reason type annotations are often needed with ``collect`` is + because it's generic over its return type. This makes it harder for + the compiler to infer the correct type in a lot of cases. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst new file mode 100644 index 000000000..8816d53bd --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/05_intoiterator.rst @@ -0,0 +1,97 @@ +================== +``IntoIterator`` +================== + +------------------ +``IntoIterator`` +------------------ + +The ``Iterator`` trait tells you how to *iterate* once you have created +an iterator. The related trait +`IntoIterator `__ +defines how to create an iterator for a type. It is used automatically +by the ``for`` loop. + +.. code:: rust,editable + + struct Grid { + x_coords: Vec, + y_coords: Vec, + } + + impl IntoIterator for Grid { + type Item = (u32, u32); + type IntoIter = GridIter; + fn into_iter(self) -> GridIter { + GridIter { grid: self, i: 0, j: 0 } + } + } + + struct GridIter { + grid: Grid, + i: usize, + j: usize, + } + + impl Iterator for GridIter { + type Item = (u32, u32); + + fn next(&mut self) -> Option<(u32, u32)> { + if self.i >= self.grid.x_coords.len() { + self.i = 0; + self.j += 1; + if self.j >= self.grid.y_coords.len() { + return None; + } + } + let res = Some((self.grid.x_coords[self.i], self.grid.y_coords[self.j])); + self.i += 1; + res + } + } + + fn main() { + let grid = Grid { x_coords: vec![3, 5, 7, 9], y_coords: vec![10, 20, 30, 40] }; + for (x, y) in grid { + println!("point = {x}, {y}"); + } + } + +.. raw:: html + +--------- +Details +--------- + +- ``IntoIterator`` is the trait that makes for loops work. It is + implemented by collection types such as ``Vec`` and references to + them such as ``&Vec`` and ``&[T]``. Ranges also implement it. This + is why you can iterate over a vector with + ``for i in some_vec { .. }`` but ``some_vec.next()`` doesn't exist. + +Click through to the docs for ``IntoIterator``. Every implementation of +``IntoIterator`` must declare two types: + +- ``Item``: the type to iterate over, such as ``i8``, +- ``IntoIter``: the ``Iterator`` type returned by the ``into_iter`` + method. + +Note that ``IntoIter`` and ``Item`` are linked: the iterator must have +the same ``Item`` type, which means that it returns ``Option`` + +The example iterates over all combinations of x and y coordinates. + +Try iterating over the grid twice in ``main``. Why does this fail? Note +that ``IntoIterator::into_iter`` takes ownership of ``self``. + +Fix this issue by implementing ``IntoIterator`` for ``&Grid`` and +storing a reference to the ``Grid`` in ``GridIter``. + +The same problem can occur for standard library types: +``for e in some_vector`` will take ownership of ``some_vector`` and +iterate over owned elements from that vector. Use +``for e in &some_vector`` instead, to iterate over references to +elements of ``some_vector``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/170_iterators/06_exercise.rst b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst new file mode 100644 index 000000000..4060d4b2d --- /dev/null +++ b/courses/comprehensive_rust_training/170_iterators/06_exercise.rst @@ -0,0 +1,24 @@ +==================================== +Exercise: Iterator Method Chaining +==================================== + +------------------------------------ +Exercise: Iterator Method Chaining +------------------------------------ + +In this exercise, you will need to find and use some of the provided +methods in the +`Iterator `__ +trait to implement a complex calculation. + +Copy the following code to https://play.rust-lang.org/ and make the +tests pass. Use an iterator expression and ``collect`` the result to +construct the return value. + +.. code:: rust + + {{#include exercise.rs:offset_differences}} + unimplemented!() + } + + {{#include exercise.rs:unit-tests}} diff --git a/courses/comprehensive_rust_training/180_modules.rst b/courses/comprehensive_rust_training/180_modules.rst new file mode 100644 index 000000000..beda480e2 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/180_modules/01_modules.rst b/courses/comprehensive_rust_training/180_modules/01_modules.rst new file mode 100644 index 000000000..d56709e5b --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/01_modules.rst @@ -0,0 +1,46 @@ +========= +Modules +========= + +--------- +Modules +--------- + +We have seen how ``impl`` blocks let us namespace functions to a type. + +Similarly, ``mod`` lets us namespace types and functions: + +.. code:: rust,editable + + mod foo { + pub fn do_something() { + println!("In the foo module"); + } + } + + mod bar { + pub fn do_something() { + println!("In the bar module"); + } + } + + fn main() { + foo::do_something(); + bar::do_something(); + } + +.. raw:: html + +--------- +Details +--------- + +- Packages provide functionality and include a ``Cargo.toml`` file that + describes how to build a bundle of 1+ crates. +- Crates are a tree of modules, where a binary crate creates an + executable and a library crate compiles to a library. +- Modules define organization, scope, and are the focus of this + section. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/02_filesystem.rst b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst new file mode 100644 index 000000000..5349471b9 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/02_filesystem.rst @@ -0,0 +1,85 @@ +====================== +Filesystem Hierarchy +====================== + +---------------------- +Filesystem Hierarchy +---------------------- + +Omitting the module content will tell Rust to look for it in another +file: + +.. code:: rust,editable,compile_fail + + mod garden; + +This tells Rust that the ``garden`` module content is found at +``src/garden.rs``. Similarly, a ``garden::vegetables`` module can be +found at ``src/garden/vegetables.rs``. + +The ``crate`` root is in: + +- ``src/lib.rs`` (for a library crate) +- ``src/main.rs`` (for a binary crate) + +Modules defined in files can be documented, too, using "inner doc +comments". These document the item that contains them - in this case, a +module. + +.. code:: rust,editable,compile_fail + + //! This module implements the garden, including a highly performant germination + //! implementation. + + // Re-export types from this module. + pub use garden::Garden; + pub use seeds::SeedPacket; + + /// Sow the given seed packets. + pub fn sow(seeds: Vec) { + todo!() + } + + /// Harvest the produce in the garden that is ready. + pub fn harvest(garden: &mut Garden) { + todo!() + } + +.. raw:: html + +--------- +Details +--------- + +- Before Rust 2018, modules needed to be located at ``module/mod.rs`` + instead of ``module.rs``, and this is still a working alternative for + editions after 2018. + +- The main reason to introduce ``filename.rs`` as alternative to + ``filename/mod.rs`` was because many files named ``mod.rs`` can be + hard to distinguish in IDEs. + +- Deeper nesting can use folders, even if the main module is a file: + + .. code:: ignore + + src/ + |-- 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,ignore + + #[path = "some/path.rs"] + mod some_module; + + This is useful, for example, if you would like to place tests for a + module in a file named ``some_module_test.rs``, similar to the + convention in Go. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/03_visibility.rst b/courses/comprehensive_rust_training/180_modules/03_visibility.rst new file mode 100644 index 000000000..c5e40b8cb --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/03_visibility.rst @@ -0,0 +1,62 @@ +============ +Visibility +============ + +------------ +Visibility +------------ + +Modules are a privacy boundary: + +- Module items are private by default (hides implementation details). +- Parent and sibling items are always visible. +- In other words, if an item is visible in module ``foo``, it's visible + in all the descendants of ``foo``. + +.. code:: rust,editable + + mod outer { + fn private() { + println!("outer::private"); + } + + pub fn public() { + println!("outer::public"); + } + + mod inner { + fn private() { + println!("outer::inner::private"); + } + + pub fn public() { + println!("outer::inner::public"); + super::private(); + } + } + } + + fn main() { + outer::public(); + } + +.. raw:: html + +--------- +Details +--------- + +- Use the ``pub`` keyword to make modules public. + +Additionally, there are advanced ``pub(...)`` specifiers to restrict the +scope of public visibility. + +- See the + `Rust Reference `__. +- Configuring ``pub(crate)`` visibility is a common pattern. +- Less commonly, you can give visibility to a specific path. +- In any case, visibility must be granted to an ancestor module (and + all of its descendants). + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst new file mode 100644 index 000000000..0b2dd2861 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/04_encapsulation.rst @@ -0,0 +1,90 @@ +============================== +Visibility and Encapsulation +============================== + +------------------------------ +Visibility and Encapsulation +------------------------------ + +Like with items in a module, struct fields are also private by default. +Private fields are likewise visible within the rest of the module +(including child modules). This allows us to encapsulate implementation +details of struct, controlling what data and functionality is visible +externally. + +.. code:: rust,editable + + use outer::Foo; + + mod outer { + pub struct Foo { + pub val: i32, + is_big: bool, + } + + impl Foo { + pub fn new(val: i32) -> Self { + Self { val, is_big: val > 100 } + } + } + + pub mod inner { + use super::Foo; + + pub fn print_foo(foo: &Foo) { + println!("Is {} big? {}", foo.val, foo.is_big); + } + } + } + + fn main() { + let foo = Foo::new(42); + println!("foo.val = {}", foo.val); + // let foo = Foo { val: 42, is_big: true }; + + outer::inner::print_foo(&foo); + // println!("Is {} big? {}", foo.val, foo.is_big); + } + +.. raw:: html + +--------- +Details +--------- + +- This slide demonstrates how privacy in structs is module-based. + Students coming from object oriented languages may be used to types + being the encapsulation boundary, so this demonstrates how Rust + behaves differently while showing how we can still achieve + encapsulation. + +- Note how the ``is_big`` field is fully controlled by ``Foo``, + allowing ``Foo`` to control how it's initialized and enforce any + invariants it needs to (e.g. that ``is_big`` is only ``true`` if + ``val > 100``). + +- Point out how helper functions can be defined in the same module + (including child modules) in order to get access to the type's + private fields/methods. + +- The first commented out line demonstrates that you cannot initialize + a struct with private fields. The second one demonstrates that you + also can't directly access private fields. + +- Enums do not support privacy: Variants and data within those variants + is always public. + +----------------- +More to Explore +----------------- + +- If students want more information about privacy (or lack thereof) in + enums, you can bring up ``#[doc_hidden]`` and ``#[non_exhaustive]`` + and show how they're used to limit what can be done with an enum. + +- Module privacy still applies when there are ``impl`` blocks in other + modules + `(example in the playground) `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/05_paths.rst b/courses/comprehensive_rust_training/180_modules/05_paths.rst new file mode 100644 index 000000000..a0f41faab --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/05_paths.rst @@ -0,0 +1,63 @@ +================== +use, super, self +================== + +------------------ +use, super, self +------------------ + +A module can bring symbols from another module into scope with ``use``. +You will typically see something like this at the top of each module: + +.. code:: rust,editable + + use std::collections::HashSet; + use std::process::abort; + +------- +Paths +------- + +Paths are resolved as follows: + +1. As a relative path: + + - ``foo`` or ``self::foo`` refers to ``foo`` in the current module, + - ``super::foo`` refers to ``foo`` in the parent module. + +2. As an absolute path: + + - ``crate::foo`` refers to ``foo`` in the root of the current crate, + - ``bar::foo`` refers to ``foo`` in the ``bar`` crate. + +.. raw:: html + +--------- +Details +--------- + +- It is common to "re-export" symbols at a shorter path. For example, + the top-level ``lib.rs`` in a crate might have + + .. code:: rust,ignore + + mod storage; + + pub use storage::disk::DiskStorage; + pub use storage::network::NetworkStorage; + + making ``DiskStorage`` and ``NetworkStorage`` available to other + crates with a convenient, short path. + +- For the most part, only items that appear in a module need to be + ``use``\ 'd. However, a trait must be in scope to call any methods on + that trait, even if a type implementing that trait is already in + scope. For example, to use the ``read_to_string`` method on a type + implementing the ``Read`` trait, you need to ``use std::io::Read``. + +- The ``use`` statement can have a wildcard: ``use std::io::*``. This + is discouraged because it is not clear which items are imported, and + those might change over time. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/180_modules/06_exercise.rst b/courses/comprehensive_rust_training/180_modules/06_exercise.rst new file mode 100644 index 000000000..d4b094ad5 --- /dev/null +++ b/courses/comprehensive_rust_training/180_modules/06_exercise.rst @@ -0,0 +1,53 @@ +===================================== +Exercise: Modules for a GUI Library +===================================== + +------------------------------------- +Exercise: Modules for a GUI Library +------------------------------------- + +In this exercise, you will reorganize a small GUI Library +implementation. This library defines a ``Widget`` trait and a few +implementations of that trait, as well as a ``main`` function. + +It is typical to put each type or set of closely-related types into its +own module, so each widget type should get its own module. + +------------- +Cargo Setup +------------- + +The Rust playground only supports one file, so you will need to make a +Cargo project on your local filesystem: + +.. code:: shell + + cargo init gui-modules + cd gui-modules + cargo run + +Edit the resulting ``src/main.rs`` to add ``mod`` statements, and add +additional files in the ``src`` directory. + +-------- +Source +-------- + +Here's the single-module implementation of the GUI library: + +.. code:: rust + + {{#include exercise.rs:single-module}} + +.. raw:: html + +--------- +Details +--------- + +Encourage students to divide the code in a way that feels natural for +them, and get accustomed to the required ``mod``, ``use``, and ``pub`` +declarations. Afterward, discuss what organizations are most idiomatic. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/190_testing.rst b/courses/comprehensive_rust_training/190_testing.rst new file mode 100644 index 000000000..68b64b594 --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst new file mode 100644 index 000000000..a69211bfd --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing/01_unit_tests.rst @@ -0,0 +1,56 @@ +============ +Unit Tests +============ + +------------ +Unit Tests +------------ + +Rust and Cargo come with a simple unit test framework. Tests are marked +with ``#[test]``. Unit tests are often put in a nested ``tests`` module, +using ``#[cfg(test)]`` to conditionally compile them only when building +tests. + +.. code:: rust,editable,ignore + + fn first_word(text: &str) -> &str { + match text.find(' ') { + Some(idx) => &text[..idx], + None => &text, + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_empty() { + assert_eq!(first_word(""), ""); + } + + #[test] + fn test_single_word() { + assert_eq!(first_word("Hello"), "Hello"); + } + + #[test] + fn test_multiple_words() { + assert_eq!(first_word("Hello World"), "Hello"); + } + } + +- This lets you unit test private helpers. +- The ``#[cfg(test)]`` attribute is only active when you run + ``cargo test``. + +.. raw:: html + +--------- +Details +--------- + +Run the tests in the playground in order to show their results. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/190_testing/02_other.rst b/courses/comprehensive_rust_training/190_testing/02_other.rst new file mode 100644 index 000000000..146224bd5 --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing/02_other.rst @@ -0,0 +1,53 @@ +====================== +Other Types of Tests +====================== + +---------------------- +Other Types of Tests +---------------------- + +------------------- +Integration Tests +------------------- + +If you want to test your library as a client, use an integration test. + +Create a ``.rs`` file under ``tests/``: + +.. code:: rust,ignore + + // tests/my_library.rs + use my_library::init; + + #[test] + fn test_init() { + assert!(init().is_ok()); + } + +These tests only have access to the public API of your crate. + +--------------------- +Documentation Tests +--------------------- + +Rust has built-in support for documentation tests: + +.. code:: rust + + /// Shortens a string to the given length. + /// + /// ``` + /// # use playground::shorten_string; + /// assert_eq!(shorten_string("Hello World", 5), "Hello"); + /// assert_eq!(shorten_string("Hello World", 20), "Hello World"); + /// ``` + pub fn shorten_string(s: &str, length: usize) -> &str { + &s[..std::cmp::min(length, s.len())] + } + +- Code blocks in ``///`` comments are automatically seen as Rust code. +- The code will be compiled and executed as part of ``cargo test``. +- Adding ``#`` in the code will hide it from the docs, but will still + compile/run it. +- Test the above code on the + `Rust Playground `__. diff --git a/courses/comprehensive_rust_training/190_testing/03_lints.rst b/courses/comprehensive_rust_training/190_testing/03_lints.rst new file mode 100644 index 000000000..510dca137 --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing/03_lints.rst @@ -0,0 +1,39 @@ +=========================== +Compiler Lints and Clippy +=========================== + +--------------------------- +Compiler Lints and Clippy +--------------------------- + +The Rust compiler produces fantastic error messages, as well as helpful +built-in lints. `Clippy `__ provides +even more lints, organized into groups that can be enabled per-project. + +.. code:: rust,editable,should_panic,warnunused + + #[deny(clippy::cast_possible_truncation)] + fn main() { + let mut x = 3; + while (x < 70000) { + x *= 2; + } + println!("X probably fits in a u16, right? {}", x as u16); + } + +.. raw:: html + +--------- +Details +--------- + +There are compiler lints visible here, but not clippy lints. Run +``clippy`` on the playground site to show clippy warnings. Clippy has +extensive documentation of its lints, and adds new lints (including +default-deny lints) all the time. + +Note that errors or warnings with ``help: ...`` can be fixed with +``cargo fix`` or via your editor. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/190_testing/04_exercise.rst b/courses/comprehensive_rust_training/190_testing/04_exercise.rst new file mode 100644 index 000000000..633f84215 --- /dev/null +++ b/courses/comprehensive_rust_training/190_testing/04_exercise.rst @@ -0,0 +1,39 @@ +========================== +Exercise: Luhn Algorithm +========================== + +-------------------------- +Exercise: Luhn Algorithm +-------------------------- + +The `Luhn algorithm `__ is +used to validate credit card numbers. The algorithm takes a string as +input and does the following to validate the credit card number: + +- Ignore all spaces. Reject numbers with fewer than two digits. + +- Moving from **right to left**, double every second digit: for the + number ``1234``, we double ``3`` and ``1``. For the number ``98765``, + we double ``6`` and ``8``. + +- After doubling a digit, sum the digits if the result is greater than + 9. So doubling ``7`` becomes ``14`` which becomes ``1 + 4 = 5``. + +- Sum all the undoubled and doubled digits. + +- The credit card number is valid if the sum ends with ``0``. + +The provided code provides a buggy implementation of the luhn algorithm, +along with two basic unit tests that confirm that most of the algorithm +is implemented correctly. + +Copy the code below to https://play.rust-lang.org/ and write additional +tests to uncover bugs in the provided implementation, fixing any bugs +you find. + +.. code:: rust + + {{#include exercise.rs:luhn}} + + {{#include exercise.rs:unit-tests}} + } diff --git a/courses/comprehensive_rust_training/200_error_handling.rst b/courses/comprehensive_rust_training/200_error_handling.rst new file mode 100644 index 000000000..e8a88c20b --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/200_error_handling/01_panics.rst b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst new file mode 100644 index 000000000..79499cab7 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/01_panics.rst @@ -0,0 +1,63 @@ +======== +Panics +======== + +-------- +Panics +-------- + +Rust handles fatal errors with a "panic". + +Rust will trigger a panic if a fatal error happens at runtime: + +.. code:: rust,editable,should_panic + + fn main() { + let v = vec![10, 20, 30]; + println!("v[100]: {}", v[100]); + } + +- Panics are for unrecoverable and unexpected errors. + + - Panics are symptoms of bugs in the program. + - Runtime failures like failed bounds checks can panic + - Assertions (such as ``assert!``) panic on failure + - Purpose-specific panics can use the ``panic!`` macro. + +- A panic will "unwind" the stack, dropping values just as if the + functions had returned. +- Use non-panicking APIs (such as ``Vec::get``) if crashing is not + acceptable. + +.. raw:: html + +--------- +Details +--------- + +By default, a panic will cause the stack to unwind. The unwinding can be +caught: + +.. code:: rust,editable + + use std::panic; + + fn main() { + let result = panic::catch_unwind(|| "No problem here!"); + println!("{result:?}"); + + let result = panic::catch_unwind(|| { + panic!("oh no!"); + }); + println!("{result:?}"); + } + +- Catching is unusual; do not attempt to implement exceptions with + ``catch_unwind``! +- This can be useful in servers which should keep running even if a + single request crashes. +- This does not work if ``panic = 'abort'`` is set in your + ``Cargo.toml``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/02_result.rst b/courses/comprehensive_rust_training/200_error_handling/02_result.rst new file mode 100644 index 000000000..8eb6f64ea --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/02_result.rst @@ -0,0 +1,96 @@ +============ +``Result`` +============ + +------------ +``Result`` +------------ + +Our primary mechanism for error handling in Rust is the +`Result `__ +enum, which we briefly saw when discussing standard library types. + +.. code:: rust,editable + + use std::fs::File; + use std::io::Read; + + fn main() { + let file: Result = File::open("diary.txt"); + match file { + Ok(mut file) => { + let mut contents = String::new(); + if let Ok(bytes) = file.read_to_string(&mut contents) { + println!("Dear diary: {contents} ({bytes} bytes)"); + } else { + println!("Could not read file content"); + } + } + Err(err) => { + println!("The diary could not be opened: {err}"); + } + } + } + +.. raw:: html + +--------- +Details +--------- + +- ``Result`` has two variants: ``Ok`` which contains the success value, + and ``Err`` which contains an error value of some kind. + +- Whether or not a function can produce an error is encoded in the + function's type signature by having the function return a ``Result`` + value. + +- Like with ``Option``, there is no way to forget to handle an error: + You cannot access either the success value or the error value without + first pattern matching on the ``Result`` to check which variant you + have. Methods like ``unwrap`` make it easier to write quick-and-dirty + code that doesn't do robust error handling, but means that you can + always see in your source code where proper error handling is being + skipped. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +It may be helpful to compare error handling in Rust to error handling +conventions that students may be familiar with from other programming +languages. + +------------ +Exceptions +------------ + +- Many languages use exceptions, e.g. C++, Java, Python. + +- In most languages with exceptions, whether or not a function can + throw an exception is not visible as part of its type signature. This + generally means that you can't tell when calling a function if it may + throw an exception or not. + +- Exceptions generally unwind the call stack, propagating upward until + a ``try`` block is reached. An error originating deep in the call + stack may impact an unrelated function further up. + +--------------- +Error Numbers +--------------- + +- Some languages have functions return an error number (or some other + error value) separately from the successful return value of the + function. Examples include C and Go. + +- Depending on the language it may be possible to forget to check the + error value, in which case you may be accessing an uninitialized or + otherwise invalid success value. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/03_try.rst b/courses/comprehensive_rust_training/200_error_handling/03_try.rst new file mode 100644 index 000000000..2074a0289 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/03_try.rst @@ -0,0 +1,74 @@ +============== +Try Operator +============== + +-------------- +Try Operator +-------------- + +Runtime errors like connection-refused or file-not-found are handled +with the ``Result`` type, but matching this type on every call can be +cumbersome. The try-operator ``?`` is used to return errors to the +caller. It lets you turn the common + +.. code:: rust,ignore + + match some_expression { + Ok(value) => value, + Err(err) => return Err(err), + } + +into the much simpler + +.. code:: rust,ignore + + some_expression? + +We can use this to simplify our error handling code: + +.. code:: rust,editable + + use std::io::Read; + use std::{fs, io}; + + fn read_username(path: &str) -> Result { + let username_file_result = fs::File::open(path); + let mut username_file = match username_file_result { + Ok(file) => file, + Err(err) => return Err(err), + }; + + let mut username = String::new(); + match username_file.read_to_string(&mut username) { + Ok(_) => Ok(username), + Err(err) => Err(err), + } + } + + fn main() { + //fs::write("config.dat", "alice").unwrap(); + let username = read_username("config.dat"); + println!("username or error: {username:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +Simplify the ``read_username`` function to use ``?``. + +Key points: + +- The ``username`` variable can be either ``Ok(string)`` or + ``Err(error)``. +- Use the ``fs::write`` call to test out the different scenarios: no + file, empty file, file with username. +- Note that ``main`` can return a ``Result<(), E>`` as long as it + implements ``std::process::Termination``. In practice, this means + that ``E`` implements ``Debug``. The executable will print the + ``Err`` variant and return a nonzero exit status on error. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst b/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst new file mode 100644 index 000000000..aa772da79 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/04_try_conversions.rst @@ -0,0 +1,103 @@ +================= +Try Conversions +================= + +----------------- +Try Conversions +----------------- + +The effective expansion of ``?`` is a little more complicated than +previously indicated: + +.. code:: rust,ignore + + expression? + +works the same as + +.. code:: rust,ignore + + match expression { + Ok(value) => value, + Err(err) => return Err(From::from(err)), + } + +The ``From::from`` call here means we attempt to convert the error type +to the type returned by the function. This makes it easy to encapsulate +errors into higher-level errors. + +--------- +Example +--------- + +.. code:: rust,editable + + use std::error::Error; + use std::io::Read; + use std::{fmt, fs, io}; + + #[derive(Debug)] + enum ReadUsernameError { + IoError(io::Error), + EmptyUsername(String), + } + + impl Error for ReadUsernameError {} + + impl fmt::Display for ReadUsernameError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::IoError(e) => write!(f, "I/O error: {e}"), + Self::EmptyUsername(path) => write!(f, "Found no username in {path}"), + } + } + } + + impl From for ReadUsernameError { + fn from(err: io::Error) -> Self { + Self::IoError(err) + } + } + + fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + fs::File::open(path)?.read_to_string(&mut username)?; + if username.is_empty() { + return Err(ReadUsernameError::EmptyUsername(String::from(path))); + } + Ok(username) + } + + fn main() { + //std::fs::write("config.dat", "").unwrap(); + let username = read_username("config.dat"); + println!("username or error: {username:?}"); + } + +.. raw:: html + +--------- +Details +--------- + +The ``?`` operator must return a value compatible with the return type +of the function. For ``Result``, it means that the error types have to +be compatible. A function that returns ``Result`` can +only use ``?`` on a value of type ``Result`` if +``ErrorOuter`` and ``ErrorInner`` are the same type or if ``ErrorOuter`` +implements ``From``. + +A common alternative to a ``From`` implementation is +``Result::map_err``, especially when the conversion only happens in one +place. + +There is no compatibility requirement for ``Option``. A function +returning ``Option`` can use the ``?`` operator on ``Option`` for +arbitrary ``T`` and ``U`` types. + +A function that returns ``Result`` cannot use ``?`` on ``Option`` and +vice versa. However, ``Option::ok_or`` converts ``Option`` to ``Result`` +whereas ``Result::ok`` turns ``Result`` into ``Option``. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/05_error.rst b/courses/comprehensive_rust_training/200_error_handling/05_error.rst new file mode 100644 index 000000000..465936d86 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/05_error.rst @@ -0,0 +1,54 @@ +===================== +Dynamic Error Types +===================== + +--------------------- +Dynamic Error Types +--------------------- + +Sometimes we want to allow any type of error to be returned without +writing our own enum covering all the different possibilities. The +``std::error::Error`` trait makes it easy to create a trait object that +can contain any error. + +.. code:: rust,editable + + use std::error::Error; + use std::fs; + use std::io::Read; + + fn read_count(path: &str) -> Result> { + let mut count_str = String::new(); + fs::File::open(path)?.read_to_string(&mut count_str)?; + let count: i32 = count_str.parse()?; + Ok(count) + } + + fn main() { + fs::write("count.dat", "1i3").unwrap(); + match read_count("count.dat") { + Ok(count) => println!("Count: {count}"), + Err(err) => println!("Error: {err}"), + } + } + +.. raw:: html + +--------- +Details +--------- + +The ``read_count`` function can return ``std::io::Error`` (from file +operations) or ``std::num::ParseIntError`` (from ``String::parse``). + +Boxing errors saves on code, but gives up the ability to cleanly handle +different error cases differently in the program. As such it's generally +not a good idea to use ``Box`` in the public API of a +library, but it can be a good option in a program where you just want to +display the error message somewhere. + +Make sure to implement the ``std::error::Error`` trait when defining a +custom error type so it can be boxed. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst new file mode 100644 index 000000000..bb84afdcf --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/06_thiserror.rst @@ -0,0 +1,60 @@ +=============== +``thiserror`` +=============== + +--------------- +``thiserror`` +--------------- + +The `thiserror `__ crate provides macros +to help avoid boilerplate when defining error types. It provides derive +macros that assist in implementing ``From``, ``Display``, and the +``Error`` trait. + +.. code:: rust,editable,compile_fail + + use std::io::Read; + use std::{fs, io}; + use thiserror::Error; + + #[derive(Debug, Error)] + enum ReadUsernameError { + #[error("I/O error: {0}")] + IoError(#[from] io::Error), + #[error("Found no username in {0}")] + EmptyUsername(String), + } + + fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + fs::File::open(path)?.read_to_string(&mut username)?; + if username.is_empty() { + return Err(ReadUsernameError::EmptyUsername(String::from(path))); + } + Ok(username) + } + + fn main() { + //fs::write("config.dat", "").unwrap(); + match read_username("config.dat") { + Ok(username) => println!("Username: {username}"), + Err(err) => println!("Error: {err:?}"), + } + } + +.. raw:: html + +--------- +Details +--------- + +- The ``Error`` derive macro is provided by ``thiserror``, and has lots + of useful attributes to help define error types in a compact way. +- The message from ``#[error]`` is used to derive the ``Display`` + trait. +- Note that the (``thiserror::``)\ ``Error`` derive macro, while it has + the effect of implementing the (``std::error::``)\ ``Error`` trait, + is not the same this; traits and macros do not share a namespace. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst new file mode 100644 index 000000000..c920169c8 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/07_anyhow.rst @@ -0,0 +1,83 @@ +============ +``anyhow`` +============ + +------------ +``anyhow`` +------------ + +The `anyhow `__ crate provides a rich error +type with support for carrying additional contextual information, which +can be used to provide a semantic trace of what the program was doing +leading up to the error. + +This can be combined with the convenience macros from +`thiserror `__ to avoid writing out +trait impls explicitly for custom error types. + +.. code:: rust,editable,compile_fail + + use anyhow::{bail, Context, Result}; + use std::fs; + use std::io::Read; + use thiserror::Error; + + #[derive(Clone, Debug, Eq, Error, PartialEq)] + #[error("Found no username in {0}")] + struct EmptyUsernameError(String); + + fn read_username(path: &str) -> Result { + let mut username = String::with_capacity(100); + fs::File::open(path) + .with_context(|| format!("Failed to open {path}"))? + .read_to_string(&mut username) + .context("Failed to read")?; + if username.is_empty() { + bail!(EmptyUsernameError(path.to_string())); + } + Ok(username) + } + + fn main() { + //fs::write("config.dat", "").unwrap(); + match read_username("config.dat") { + Ok(username) => println!("Username: {username}"), + Err(err) => println!("Error: {err:?}"), + } + } + +.. raw:: html + +--------- +Details +--------- + +- ``anyhow::Error`` is essentially a wrapper around ``Box``. + As such it's again generally not a good choice for the public API of + a library, but is widely used in applications. +- ``anyhow::Result`` is a type alias for + ``Result``. +- Functionality provided by ``anyhow::Error`` may be familiar to Go + developers, as it provides similar behavior to the Go ``error`` type + and ``Result`` is much like a Go ``(T, error)`` + (with the convention that only one element of the pair is + meaningful). +- ``anyhow::Context`` is a trait implemented for the standard + ``Result`` and ``Option`` types. ``use anyhow::Context`` is necessary + to enable ``.context()`` and ``.with_context()`` on those types. + +================= +More to Explore +================= + +----------------- +More to Explore +----------------- + +- ``anyhow::Error`` has support for downcasting, much like + ``std::any::Any``; the specific error type stored inside can be + extracted for examination if desired with + `Error::downcast `__. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst new file mode 100644 index 000000000..b65584ce6 --- /dev/null +++ b/courses/comprehensive_rust_training/200_error_handling/08_exercise.rst @@ -0,0 +1,30 @@ +================================= +Exercise: Rewriting with Result +================================= + +--------------------------------- +Exercise: Rewriting with Result +--------------------------------- + +In this exercise we're revisiting the expression evaluator exercise that +we did in day 2. Our initial solution ignores a possible error case: +Dividing by zero! Rewrite ``eval`` to instead use idiomatic error +handling to handle this error case and return an error when it occurs. +We provide a simple ``DivideByZeroError`` type to use as the error type +for ``eval``. + +.. code:: rust,editable + + {{#include exercise.rs:types}} + + {{#include exercise.rs:eval}} + + {{#include exercise.rs:tests}} + +- The starting code here isn't exactly the same as the previous + exercise's solution: We've added in an explicit panic to show + students where the error case is. Point this out if students get + confused. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust.rst b/courses/comprehensive_rust_training/210_unsafe_rust.rst new file mode 100644 index 000000000..5dcfeb734 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust.rst @@ -0,0 +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 diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst new file mode 100644 index 000000000..3dec2e85f --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/01_unsafe.rst @@ -0,0 +1,46 @@ +============= +Unsafe Rust +============= + +------------- +Unsafe Rust +------------- + +The Rust language has two parts: + +- **Safe Rust:** memory safe, no undefined behavior possible. +- **Unsafe Rust:** can trigger undefined behavior if preconditions are + violated. + +We saw mostly safe Rust in this course, but it's important to know what +Unsafe Rust is. + +Unsafe code is usually small and isolated, and its correctness should be +carefully documented. It is usually wrapped in a safe abstraction layer. + +Unsafe Rust gives you access to five new capabilities: + +- Dereference raw pointers. +- Access or modify mutable static variables. +- Access ``union`` fields. +- Call ``unsafe`` functions, including ``extern`` functions. +- Implement ``unsafe`` traits. + +We will briefly cover unsafe capabilities next. For full details, please +see +`Chapter 19.1 in the Rust Book `__ and +the `Rustonomicon `__. + +.. raw:: html + +--------- +Details +--------- + +Unsafe Rust does not mean the code is incorrect. It means that +developers have turned off some compiler safety features and have to +write correct code by themselves. It means the compiler no longer +enforces Rust's memory-safety rules. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst new file mode 100644 index 000000000..c290932b1 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/02_dereferencing.rst @@ -0,0 +1,70 @@ +============================ +Dereferencing Raw Pointers +============================ + +---------------------------- +Dereferencing Raw Pointers +---------------------------- + +Creating pointers is safe, but dereferencing them requires ``unsafe``: + +.. code:: rust,editable + + fn main() { + let mut s = String::from("careful!"); + + let r1 = &raw mut s; + let r2 = r1 as *const String; + + // SAFETY: r1 and r2 were obtained from references and so are guaranteed to + // be non-null and properly aligned, the objects underlying the references + // from which they were obtained are live throughout the whole unsafe + // block, and they are not accessed either through the references or + // concurrently through any other pointers. + unsafe { + println!("r1 is: {}", *r1); + *r1 = String::from("uhoh"); + println!("r2 is: {}", *r2); + } + + // NOT SAFE. DO NOT DO THIS. + /* + let r3: &String = unsafe { &*r1 }; + drop(s); + println!("r3 is: {}", *r3); + */ + } + +.. raw:: html + +--------- +Details +--------- + +It is good practice (and required by the Android Rust style guide) to +write a comment for each ``unsafe`` block explaining how the code inside +it satisfies the safety requirements of the unsafe operations it is +doing. + +In the case of pointer dereferences, this means that the pointers must +be `valid `__, +i.e.: + +- The pointer must be non-null. +- The pointer must be *dereferenceable* (within the bounds of a single + allocated object). +- The object must not have been deallocated. +- There must not be concurrent accesses to the same location. +- If the pointer was obtained by casting a reference, the underlying + object must be live and no reference may be used to access the + memory. + +In most cases the pointer must also be properly aligned. + +The "NOT SAFE" section gives an example of a common kind of UB bug: +``*r1`` has the ``'static`` lifetime, so ``r3`` has type +``&'static String``, and thus outlives ``s``. Creating a reference from +a pointer requires *great care*. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst b/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst new file mode 100644 index 000000000..cdbc9027d --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/03_mutable_static.rst @@ -0,0 +1,58 @@ +========================== +Mutable Static Variables +========================== + +-------------------------- +Mutable Static Variables +-------------------------- + +It is safe to read an immutable static variable: + +.. code:: rust,editable + + static HELLO_WORLD: &str = "Hello, world!"; + + fn main() { + println!("HELLO_WORLD: {HELLO_WORLD}"); + } + +However, since data races can occur, it is unsafe to read and write +mutable static variables: + +.. code:: rust,editable + + static mut COUNTER: u32 = 0; + + fn add_to_counter(inc: u32) { + // SAFETY: There are no other threads which could be accessing `COUNTER`. + unsafe { + COUNTER += inc; + } + } + + fn main() { + add_to_counter(42); + + // SAFETY: There are no other threads which could be accessing `COUNTER`. + unsafe { + println!("COUNTER: {COUNTER}"); + } + } + +.. raw:: html + +--------- +Details +--------- + +- The program here is safe because it is single-threaded. However, the + Rust compiler is conservative and will assume the worst. Try removing + the ``unsafe`` and see how the compiler explains that it is undefined + behavior to mutate a static from multiple threads. + +- Using a mutable static is generally a bad idea, but there are some + cases where it might make sense in low-level ``no_std`` code, such as + implementing a heap allocator or working with some C APIs. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst new file mode 100644 index 000000000..7f0ee8785 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/04_unions.rst @@ -0,0 +1,41 @@ +======== +Unions +======== + +-------- +Unions +-------- + +Unions are like enums, but you need to track the active field yourself: + +.. code:: rust,editable + + #[repr(C)] + union MyUnion { + i: u8, + b: bool, + } + + fn main() { + let u = MyUnion { i: 42 }; + println!("int: {}", unsafe { u.i }); + println!("bool: {}", unsafe { u.b }); // Undefined behavior! + } + +.. raw:: html + +--------- +Details +--------- + +Unions are very rarely needed in Rust as you can usually use an enum. +They are occasionally needed for interacting with C library APIs. + +If you just want to reinterpret bytes as a different type, you probably +want +`std::mem::transmute `__ +or a safe wrapper such as the +`zerocopy `__ crate. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst b/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst new file mode 100644 index 000000000..cc066daa6 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/05_unsafe_functions.rst @@ -0,0 +1,121 @@ +================== +Unsafe Functions +================== + +------------------ +Unsafe Functions +------------------ + +-------------------------- +Calling Unsafe Functions +-------------------------- + +A function or method can be marked ``unsafe`` if it has extra +preconditions you must uphold to avoid undefined behaviour: + +.. code:: rust,editable + + extern "C" { + fn abs(input: i32) -> i32; + } + + fn main() { + let emojis = "TBDTBDTBD"; + + // SAFETY: The indices are in the correct order, within the bounds of the + // string slice, and lie on UTF-8 sequence boundaries. + unsafe { + println!("emoji: {}", emojis.get_unchecked(0..4)); + println!("emoji: {}", emojis.get_unchecked(4..7)); + println!("emoji: {}", emojis.get_unchecked(7..11)); + } + + println!("char count: {}", count_chars(unsafe { emojis.get_unchecked(0..7) })); + + // SAFETY: `abs` doesn't deal with pointers and doesn't have any safety + // requirements. + unsafe { + println!("Absolute value of -3 according to C: {}", abs(-3)); + } + + // Not upholding the UTF-8 encoding requirement breaks memory safety! + // println!("emoji: {}", unsafe { emojis.get_unchecked(0..3) }); + // println!("char count: {}", count_chars(unsafe { + // emojis.get_unchecked(0..3) })); + } + + fn count_chars(s: &str) -> usize { + s.chars().count() + } + +-------------------------- +Writing Unsafe Functions +-------------------------- + +You can mark your own functions as ``unsafe`` if they require particular +conditions to avoid undefined behaviour. + +.. code:: rust,editable + + /// Swaps the values pointed to by the given pointers. + /// + /// # Safety + /// + /// The pointers must be valid and properly aligned. + unsafe fn swap(a: *mut u8, b: *mut u8) { + let temp = *a; + *a = *b; + *b = temp; + } + + fn main() { + let mut a = 42; + let mut b = 66; + + // SAFETY: ... + unsafe { + swap(&mut a, &mut b); + } + + println!("a = {}, b = {}", a, b); + } + +.. raw:: html + +--------- +Details +--------- + +.. _calling-unsafe-functions-1: + +-------------------------- +Calling Unsafe Functions +-------------------------- + +``get_unchecked``, like most ``_unchecked`` functions, is unsafe, +because it can create UB if the range is incorrect. ``abs`` is unsafe +for a different reason: it is an external function (FFI). Calling +external functions is usually only a problem when those functions do +things with pointers which might violate Rust's memory model, but in +general any C function might have undefined behaviour under any +arbitrary circumstances. + +The ``"C"`` in this example is the ABI; +`other ABIs are available too `__. + +.. _writing-unsafe-functions-1: + +-------------------------- +Writing Unsafe Functions +-------------------------- + +We wouldn't actually use pointers for a ``swap`` function - it can be +done safely with references. + +Note that unsafe code is allowed within an unsafe function without an +``unsafe`` block. We can prohibit this with +``#[deny(unsafe_op_in_unsafe_fn)]``. Try adding it and see what happens. +This will likely change in a future Rust edition. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst b/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst new file mode 100644 index 000000000..5af6dd949 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/06_unsafe_traits.rst @@ -0,0 +1,49 @@ +============================ +Implementing Unsafe Traits +============================ + +---------------------------- +Implementing Unsafe Traits +---------------------------- + +Like with functions, you can mark a trait as ``unsafe`` if the +implementation must guarantee particular conditions to avoid undefined +behaviour. + +For example, the ``zerocopy`` crate has an unsafe trait that looks +`something like this `__: + +.. code:: rust,editable + + use std::{mem, slice}; + + /// ... + /// # Safety + /// The type must have a defined representation and no padding. + pub unsafe trait IntoBytes { + fn as_bytes(&self) -> &[u8] { + let len = mem::size_of_val(self); + let slf: *const Self = self; + unsafe { slice::from_raw_parts(slf.cast::(), len) } + } + } + + // SAFETY: `u32` has a defined representation and no padding. + unsafe impl IntoBytes for u32 {} + +.. raw:: html + +--------- +Details +--------- + +There should be a ``# Safety`` section on the Rustdoc for the trait +explaining the requirements for the trait to be safely implemented. + +The actual safety section for ``IntoBytes`` is rather longer and more +complicated. + +The built-in ``Send`` and ``Sync`` traits are unsafe. + +.. raw:: html + diff --git a/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst new file mode 100644 index 000000000..35a9153d3 --- /dev/null +++ b/courses/comprehensive_rust_training/210_unsafe_rust/07_exercise.rst @@ -0,0 +1,87 @@ +================== +Safe FFI Wrapper +================== + +------------------ +Safe FFI Wrapper +------------------ + +Rust has great support for calling functions through a *foreign function +interface* (FFI). We will use this to build a safe wrapper for the +``libc`` functions you would use from C to read the names of files in a +directory. + +You will want to consult the manual pages: + +- `opendir(3) `__ +- `readdir(3) `__ +- `closedir(3) `__ + +You will also want to browse the +`std::ffi `__ module. There you +find a number of string types which you need for the exercise: + +.. 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: + +- ``&str`` to ``CString``: you need to allocate space for a trailing + ``\0`` character, +- ``CString`` to ``*const i8``: you need a pointer to call C functions, +- ``*const i8`` to ``&CStr``: you need something which can find the + trailing ``\0`` character, +- ``&CStr`` to ``&[u8]``: a slice of bytes is the universal interface + for "some unknown data", +- ``&[u8]`` to ``&OsStr``: ``&OsStr`` is a step towards ``OsString``, + use + `OsStrExt `__ + to create it, +- ``&OsStr`` to ``OsString``: you need to clone the data in ``&OsStr`` + to be able to return it and call ``readdir`` again. + +The `Nomicon `__ also has a +very useful chapter about FFI. + +Copy the code below to https://play.rust-lang.org/ and fill in the +missing functions and methods: + +.. code:: rust,should_panic + + // TODO: remove this when you're done with your implementation. + #![allow(unused_imports, unused_variables, dead_code)] + + {{#include exercise.rs:ffi}} + + {{#include exercise.rs:DirectoryIterator}} + unimplemented!() + } + } + + {{#include exercise.rs:Iterator}} + unimplemented!() + } + } + + {{#include exercise.rs:Drop}} + unimplemented!() + } + } + + {{#include exercise.rs:main}} diff --git a/courses/comprehensive_rust_training/comprehensive_course.txt b/courses/comprehensive_rust_training/comprehensive_course.txt new file mode 100644 index 000000000..f53aca18c --- /dev/null +++ b/courses/comprehensive_rust_training/comprehensive_course.txt @@ -0,0 +1,21 @@ +010_introduction.rst +020_hello_world.rst +030_types_and_values.rst +040_control_flow_basics.rst +050_tuples_and_arrays.rst +060_references.rst +070_user_defined_types.rst +080_pattern_matching.rst +090_methods_and_traits.rst +100_generics.rst +110_std_types.rst +120_std_traits.rst +130_memory_management.rst +140_smart_pointers.rst +150_borrowing.rst +160_lifetimes.rst +170_iterators.rst +180_modules.rst +190_testing.rst +200_error_handling.rst +210_unsafe_rust.rst diff --git a/courses/comprehensive_rust_training/course.toml b/courses/comprehensive_rust_training/course.toml new file mode 100644 index 000000000..6da1dc1c2 --- /dev/null +++ b/courses/comprehensive_rust_training/course.toml @@ -0,0 +1 @@ +name = "Comprehensive Rust Training" diff --git a/courses/comprehensive_rust_training/standard_course.txt b/courses/comprehensive_rust_training/standard_course.txt new file mode 100644 index 000000000..83fb911a2 --- /dev/null +++ b/courses/comprehensive_rust_training/standard_course.txt @@ -0,0 +1,19 @@ +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