Skip to content

Commit 2f58438

Browse files
tonowakwprzytula
andauthored
lessons 03: fix comments, add questions in lesson, add example in Python (#98)
Co-authored-by: Wojciech Przytuła <[email protected]>
1 parent 15dc4c0 commit 2f58438

File tree

7 files changed

+159
-81
lines changed

7 files changed

+159
-81
lines changed
Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2-
struct Position(i32, i32); // tuple struct
2+
struct Position(i32, i32); // This is a "tuple struct".
33

44
// Could Hero derive the Copy trait?
55
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -10,9 +10,10 @@ struct Hero {
1010
position: Position,
1111
}
1212

13-
// we can add methods to structs using the 'impl' keyword
13+
// We can add methods to structs using the 'impl' keyword.
1414
impl Hero {
15-
// static method (in Rust nomenclature: "associated function")
15+
// Static method (in Rust nomenclature: "associated function").
16+
// It can then be called as follows: `Hero::new(String::from("Ferris"))`.
1617
fn new(name: String) -> Hero {
1718
Hero {
1819
name,
@@ -23,38 +24,43 @@ impl Hero {
2324
}
2425
}
2526

26-
// multiple impl blocks are possible for one struct
27+
// We can have multiple `impl` blocks for one struct.
2728
impl Hero {
28-
// instance method, first argument (self) is the calling instance
29+
// Instance method. The first argument (self) is the calling instance,
30+
// just like `self` in Python and `this` in C++.
2931
fn distance(&self, pos: Position) -> u32 {
30-
// shorthand to: `self: &Self`
31-
// field `i` of a tuple or a tuple struct can be accessed through 'tuple.i'
32+
// For convenience, we don't have to type the argument as `self: &Self`.
33+
// The i-th field of a tuple or a tuple struct can be accessed through 'tuple.i'.
34+
// Do not abuse this syntax, though; it's often cleaner to perform
35+
// pattern matching to decompose the tuple.
3236
(pos.0 - self.position.0).unsigned_abs() + (pos.1 - self.position.1).unsigned_abs()
3337
}
3438

35-
// mutable borrow of self allows to change instance fields
39+
// Mutable borrow of self allows to change instance fields.
3640
fn level_up(&mut self) {
37-
// shorthand to: `self: &mut Self`
41+
// Again, we don't have to type the argument as `self: &mut Self`.
3842
self.experience = 0;
3943
self.level += 1;
4044
}
4145

42-
// 'self' is not borrowed here and will be moved into the method
46+
// 'self' is not borrowed here and will be moved into the method.
4347
fn die(self) {
44-
// shorthand to: `self: Self`
4548
println!(
4649
"Here lies {}, a hero who reached level {}. RIP.",
4750
self.name, self.level
4851
);
52+
// The `self: Self` is now dropped.
4953
}
5054
}
5155

5256
fn main() {
5357
// Calling associated functions requires scope (`::`) operator.
5458
let mut hero: Hero = Hero::new(String::from("Ferris"));
55-
hero.level_up(); // 'self' is always passed implicitly
59+
hero.level_up(); // 'self' is always passed implicitly as the first argument.
5660

57-
// fields other than 'name' will be the same as in 'hero'
61+
// Thanks to `..hero`, fields other than 'name' will be the same as in 'hero'.
62+
// In general, they are moved. Here, they are copied, because all missing fields
63+
// implement the `Copy` trait.
5864
let steve = Hero {
5965
name: String::from("Steve The Normal Guy"),
6066
..hero
@@ -64,25 +70,26 @@ fn main() {
6470

6571
let mut twin = hero.clone();
6672

67-
// we can compare Hero objects because it derives the PartialEq trait
73+
// We can compare `Hero` objects because it derives the `PartialEq` trait.
6874
assert_eq!(hero, twin);
6975
twin.level_up();
7076
assert_ne!(hero, twin);
7177
hero.level_up();
7278
assert_eq!(hero, twin);
7379

74-
// we can print out a the struct's debug string with '{:?}'
80+
// We can print out the struct's debug string
81+
// (which is implemented thanks to `Debug` trait) with '{:?}'.
7582
println!("print to stdout: {:?}", hero);
7683

77-
hero.die(); // 'hero' is not usable after this invocation, see the method's definiton
84+
hero.die(); // 'hero' is not usable after this invocation, see the method's definiton.
7885

79-
// the dbg! macro prints debug strings to stderr along with file and line number
80-
// dbg! takes its arguments by value, so better borrow them not to have them
81-
// moved into dbg! and consumed.
86+
// The `dbg!` macro prints debug strings to stderr along with file and line number.
87+
// `dbg!` takes its arguments by value, so it's better to borrow them to not have them
88+
// moved into `dbg!` and consumed.
8289
dbg!("print to stderr: {}", &twin);
8390

8491
let pos = Position(42, 0);
85-
let dist = steve.distance(pos); // no clone here as Position derives the Copy trait
92+
let dist = steve.distance(pos); // No clone here as `Position` derives the `Copy` trait.
8693
println!("{:?}", pos);
8794
assert_eq!(dist, 42);
8895
}

content/lessons/03_data_types/index.md

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
+++
22
title = "Data Types"
3-
date = 2029-01-01
3+
date = 2025-10-15
44
weight = 1
55
[extra]
6-
lesson_date = 2029-01-01
6+
lesson_date = 2025-10-16
77
+++
88

99
## Aggregating data
1010

11-
Below is a compact overview of Rust's structs
11+
Below is a compact overview of Rust's structs.
1212

1313
{{ include_code_sample(path="lessons/03_data_types/data_types.rs", language="rust") }}
1414

1515
## Enums
1616

1717
It is often the case that we want to define a variable that can only take
18-
a certain set of values and the values are known up front. In C you can use an `enum` for this.
18+
a certain set of values and the values are known up front.
19+
Just like the below code shows, in C you can use an `enum` for this.
1920

2021
{{ include_code_sample(path="lessons/03_data_types/enums.c", language="c") }}
2122

@@ -38,9 +39,14 @@ C++ introduces enum classes which are type-safe. Legacy enums are also somewhat
3839
| int
3940
```
4041

42+
Even though in this case we got an error, we can still write code that somehow casts the integer to the enum.
43+
44+
## Algebraic data types
45+
4146
Some programming languages (especially functional ones) allow programmers to define
4247
enums which carry additional information. Such types are usually called `tagged unions`
43-
or `algebraic data types`.
48+
or `algebraic data types`. This name can be new to you, but there's a chance that you
49+
already used it (or something similar). It's pretty easy to understand it.
4450

4551
In C++ we can use `union` with an `enum` tag to define it:
4652

@@ -52,6 +58,10 @@ You can read more about it [here](https://en.cppreference.com/w/cpp/utility/vari
5258
Java has a more or less analogous feature called `sealed classes`
5359
since [version 17](https://docs.oracle.com/en/java/javase/17/language/sealed-classes-and-interfaces.html.).
5460

61+
In Python, this is quite clean starting from Python 3.10.
62+
63+
{{ include_code_sample(path="lessons/03_data_types/variant.py", language="py") }}
64+
5565
## Enums in Rust
5666

5767
Let's see how they are defined in Rust.
@@ -82,6 +92,14 @@ the solution to the billion dollar mistake!
8292

8393
{{ include_code_sample(path="lessons/03_data_types/option.rs", language="rust") }}
8494

95+
## To discuss during class
96+
97+
- Why `enum` is considered a core feature of the language?
98+
- How Rust `enum`s could save us during standard project refactors/library usages, which wouldn't be as safe in some other languages?
99+
- People find Rust `enum`s convenient, to the degree where they miss this feature in other languages. What makes them so convenient?
100+
- Let's see an example of a file with a big enum. Is the file readable? How is that balanced versus type safety?
101+
- [Implementation](https://github.com/rust-lang/rust/blob/master/compiler/rustc_const_eval/src/interpret/step.rs) of doing a single step in calculations of const values in the compiler.
102+
85103
## Pattern matching
86104

87105
Pattern matching is a powerful feature of Rust and many functional languages, but it's slowly making
@@ -96,6 +114,28 @@ So how do we handle situations which can fail? That's where the `Result` type co
96114

97115
{{ include_code_sample(path="lessons/03_data_types/result.rs", language="rust") }}
98116

117+
## To discuss during class
118+
119+
- So, why would the approach with `Result` be any cleaner than exceptions?
120+
- Are there any other benefits of using this approach rather than exceptions?
121+
- Look into the following libraries, are those interfaces convenient? How is that balanced versus type safety?
122+
- [Examples of CLI parsing](https://github.com/clap-rs/clap/tree/master/examples/tutorial_derive) from `clap` repository,
123+
- [README.md](https://github.com/serde-rs/json) with examples of the interface of JSON serialization/deserialization library,
124+
- [Documentation](https://doc.rust-lang.org/std/path/struct.Path.html) of filesystem path handling from the standard library,
125+
- [Implementation](https://github.com/BurntSushi/ripgrep/blob/master/crates/grep/examples/simplegrep.rs) of a third-party well-liked app [`ripgrep`](https://github.com/BurntSushi/ripgrep) written in Rust.
126+
127+
## Slides
128+
129+
<iframe
130+
src="module_system/module_system.html"
131+
title="Module system"
132+
loading="lazy"
133+
style="width: 100%; aspect-ratio: 16 / 9; border: none;"
134+
></iframe>
135+
136+
- [Open the slides in a new tab (HTML)](module_system/module_system.html)
137+
- [Download the slides as PDF](module_system/module_system.pdf)
138+
99139
## Obligatory reading
100140

101141
- The Book, chapters [5](https://doc.rust-lang.org/book/ch05-00-structs.html),
@@ -107,6 +147,6 @@ So how do we handle situations which can fail? That's where the `Result` type co
107147

108148
## Assignment 2 (graded)
109149

110-
[Communications](https://classroom.github.com/a/gDraT0lo)
150+
[Communications](https://classroom.github.com/a/s7wihiAa)
111151

112-
Deadline: 23.10.2024 23:59
152+
Deadline: per-group.

content/lessons/03_data_types/option.rs

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,36 @@
55
fn main() {
66
let mut not_null: i32 = 42;
77
not_null = 43;
8-
// not_null = None; // this won't compile because it's a different type!
8+
// not_null = None; // This won't compile because it's a different type!
99

1010
let mut nullable: Option<i32> = Some(42);
1111
nullable = None;
1212
nullable = Some(43);
1313

14-
// such construction is rare, but possible
14+
// Such construction is rare, but possible.
1515
let mut double_nullable: Option<Option<i32>> = Some(Some(42));
16-
// assert_ne!(double_nullable, Some(42)); // this won't even compile because it's a different type!
16+
// This won't even compile because it's a different type.
17+
// assert_ne!(double_nullable, Some(42));
1718
double_nullable = None;
1819
double_nullable = Some(None);
1920

20-
// None and Some(None) are different!
21+
// `None` and `Some(None)` are different.
22+
// Why? How are enums represented in memory?
2123
assert_ne!(double_nullable, None);
2224

23-
// Now recall that division by 0 *panics*
24-
// A panic is an unrecoverable error
25+
// Now recall that division by 0 *panics*.
26+
// A panic is an unrecoverable error.
2527
// It is not an exception!
26-
// And in Rust there are no exceptions, so there are no try/catch blocks
27-
// Now let's imagine that we want to divide one number by another
28+
// And in Rust there are no exceptions, so there are no try/catch blocks.
29+
// Now let's imagine that we want to divide one number by another:
2830
fn divide(dividend: i32, divisor: i32) -> i32 {
2931
dividend / divisor
3032
}
3133

32-
// We get the divisor from the user, so it can be 0
33-
// We want to handle this situation gracefully - we don't want to crash the program!
34-
// We can do this by using the Option<T> type
34+
// We get the divisor from the user, so it can be 0.
35+
// We want to handle this situation gracefully,
36+
// as we don't want to crash the program.
37+
// We can do this by using the `Option<T>` type.
3538
fn safe_divide(dividend: i32, divisor: i32) -> Option<i32> {
3639
if divisor == 0 {
3740
None
@@ -40,11 +43,11 @@ fn main() {
4043
}
4144
}
4245

43-
// Fortunately, such a function is already included in the standard library
46+
// Fortunately, such a function is already included in the standard library.
47+
// We need to specify the type of `number` explicitly,
48+
// because `checked_div` is implemented for all integer types
49+
// and Rust won't know which type we want to use.
4450
let number: i32 = 42;
45-
// We need to specify the type explicitly
46-
// because checked_div is implemented for all integer types
47-
// and Rust won't know which type we want to use
4851
assert_eq!(number.checked_div(2), Some(21));
4952
assert_eq!(number.checked_div(0), None);
5053

@@ -55,41 +58,46 @@ fn main() {
5558
let seven = numbers.iter().copied().find(|&x| x == 7);
5659
assert_eq!(seven, None);
5760
// We won't delve deeper into the details of how iterators work for now,
58-
// but the key takeaway is that there are no sentinel or special values like `nullptr` in Rust
61+
// but the key takeaway is that there are no sentinel
62+
// or special values like `nullptr`/`std::iterator` in Rust.
5963

6064
// Usually there are two kinds of methods:
61-
// ones that will panic if the argument is incorrect,
62-
// numbers[8]; // this will panic!
63-
// and `checked` ones that return an Option
65+
// - ones that will panic if the argument is incorrect,
66+
// like: `numbers[8];` (this will panic),
67+
// - and `checked` ones that return an `Option`.
6468
assert_eq!(numbers.get(8), None);
6569

66-
// We can use `unwrap` to get the value out of an Option
67-
// but we must be absolutely sure that the Option is Some, otherwise we'll get a panic
68-
// numbers.get(8).unwrap(); // this will panic!
69-
assert_eq!(numbers.get(8).copied().unwrap_or(0), 0); // or we can provide a default value
70+
// We can use `unwrap` to get the value out of an `Option`,
71+
// but we must be absolutely sure that the `Option` is `Some`,
72+
// otherwise we'll get a panic,
73+
// like: `numbers.get(8).unwrap();` (this will panic).
74+
// Or we can provide a default value:
75+
assert_eq!(numbers.get(8).copied().unwrap_or(0), 0);
7076

71-
// Usually instead of unwrapping we use pattern matching, we'll get to this in a minute
72-
// but first let's see what else we can do with an option
77+
// Usually instead of unwrapping we use pattern matching,
78+
// we'll get to this in a minute,
79+
// but first let's see what else we can do with an option.
7380
let number: Option<i32> = Some(42);
74-
// We can use `map` to transform the value inside an Option
81+
// We can use `map` to transform the value inside an `Option`.
7582
let doubled = number.map(|x| x * 2);
7683
assert_eq!(doubled, Some(84));
77-
// We can use flatten to reduce one level of nesting
84+
// We can use `flatten` to reduce one level of nesting.
7885
let nested = Some(Some(42));
7986
assert_eq!(nested.flatten(), Some(42));
80-
// We can use `and_then` to chain multiple options
81-
// This operation is called `flatmap` in some languages
87+
// We can use `and_then` to chain multiple options.
88+
// This operation is called `flatmap` in some languages.
8289
let chained = number
8390
.and_then(|x| x.checked_div(0))
8491
.and_then(|x| x.checked_div(2));
8592
assert_eq!(chained, None);
8693

87-
// The last two things we'll cover here are `take` and `replace`
88-
// They are important when dealing with non-Copy types
89-
// `take` will return the value inside an Option and leave a None in its place
94+
// The last two things we'll cover here are `take` and `replace`.
95+
// They are important when dealing with non-`Copy` types.
96+
// The `take` will return the value inside an `Option`
97+
// and leave a `None` in its place.
9098
let mut option: Option<i32> = None;
91-
// Again, we need to specify the type
92-
// Even though we want to say that there is no value inside the Option,
99+
// Again, we need to specify the type.
100+
// Even though we want to say that there is no value inside the `Option`,
93101
// this absent value must have a concrete type!
94102
assert_eq!(option.take(), None);
95103
assert_eq!(option, None);
@@ -99,7 +107,7 @@ fn main() {
99107
assert_eq!(x, None);
100108
assert_eq!(y, Some(2));
101109

102-
// `replace` can be used to swap the value inside an Option
110+
// Also `replace` can be used to swap the value inside an `Option`.
103111
let mut x = Some(2);
104112
let old = x.replace(5);
105113
assert_eq!(x, Some(5));

0 commit comments

Comments
 (0)