You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/lessons/05_types_reasoning/index.md
+20-13Lines changed: 20 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,9 +1,9 @@
1
1
+++
2
2
title = "Reasoning About Types"
3
-
date = 2029-01-01
3
+
date = 2025-10-20
4
4
weight = 1
5
-
[extra]
6
-
lesson_date = 2029-01-01
5
+
[extra]
6
+
lesson_date = 2025-10-23
7
7
+++
8
8
9
9
# Type traits
@@ -22,9 +22,11 @@ Trait definitions can also be provided with default implementations of behaviors
22
22
23
23
## What about _derive_?
24
24
25
-
There is a trait-related thing we have used quite extensively and not explained yet, namely the `#[derive]` attribute. What it does is generate items (in our case a trait implementation) based on the given data definition (here a struct). Below you can find a list of derivable traits from the standard library. Writing derivation rules for user defined traits is also possible, but goes out of the scope of this lesson.
25
+
There is a trait-related feature we have used quite extensively but not explained yet, namely the `#[derive]` attribute. When placed above a struct or enum, it tells the compiler to generate an implementation of certain traits automatically. For example, `#[derive(Debug)]` will cause the compiler to create the necessary `impl Debug for YourType { ... }` code behind the scenes, so that your type can be printed with `{:?}` in `println!`.
26
26
27
-
Derivable traits:
27
+
We'll learn about how to make it work with our own traits in the next lessons. For now, you can think of `derive` as a kind of code generator built into the compiler, which is especially useful when the implementation of a trait can be generalized for any type.
28
+
29
+
Below you can find a list of derivable traits from the standard library.
28
30
29
31
- Equality traits: `Eq`, `PartialEq` and comparison traits: `Ord` and `PartialOrd`. The `Partial-` versions exist because there are types which don't fulfill the reflexivity requirement of equality (`NaN != NaN`) or do not form a total order (` NaN < 0.0 == false` and `NaN >= 0.0 == false`).
30
32
@@ -34,15 +36,15 @@ Derivable traits:
34
36
35
37
-`Default` - provides a zero-arg constructor function
36
38
37
-
-`Debug` - provides a formatting of the value which can be used in debugging context. It should _NOT_ be implemented manually. In general, if it's possible to derive the `Debug`, there are no reasons against doing it.
39
+
-`Debug` - provides a formatting of the value which can be used in debugging context. Because the `derive` attribute automatically implements a pretty way of formatting, it is discouraged to implement this trait manually. In general, if it's possible to derive the `Debug`, there are no reasons against doing it.
38
40
39
41
### When is it possible to derive a trait?
40
42
41
43
When all fields of a struct/variants of an enum implement that trait.
42
44
43
45
### Should all traits always be derived if it is possible?
44
46
45
-
No. Although it may be tempting to just slap `#[derive(Clone, Copy)]` everywhere, it would be counter-effective. For example, at some later point you might add a non-Copy field to the struct and your (or, what's worse, someone else's!) code would break. Another example: it makes little sense to use containers as keys in hashmaps or to compare tweets.
47
+
No. Although it may be tempting to just slap `#[derive(Clone, Copy)]` everywhere, it would be counter-effective. For example, at some later point you might add a non-`Copy` field to the struct and your (or, what's worse, someone else's!) code would break. Another example: it makes little sense to use containers as keys in hashmaps or to compare tweets.
46
48
47
49
# Generics
48
50
@@ -52,7 +54,7 @@ Suppose we want to find the largest element in a sequence and return it. Very mu
52
54
53
55
Perfect, it works! Now only twenty more types to go...
54
56
55
-
Fortunately, Rust gives us a way to avoid all this code duplication and generalize the types we're working on.
57
+
Of course, Rust gives us a way to avoid all this code duplication and generalize the types we're working on.
56
58
57
59
```rust
58
60
fnlargest<T>(list:&[T]) ->T {
@@ -85,7 +87,7 @@ help: consider restricting type parameter `T`
85
87
| ++++++++++++++++++++++
86
88
```
87
89
88
-
Since `T` can be of absolutely any type now, the compiler cannot be sure that operator `>` is defined. This aligns with what we wanted, as without comparing elements we don't have a notion of the largest one either. As always, the compiler comes to our aid:
90
+
Since `T` can be of absolutely any type now, the compiler cannot be sure that operator `>` is defined. This aligns with what we wanted, as without comparing elements we don't have a notion of the largest one either. As always, the compiler messages come to our aid:
89
91
90
92
```rust
91
93
fnlargest<T:PartialOrd>(list:&[T]) ->T {
@@ -135,7 +137,7 @@ There's a lot more that we can do with generics:
Going back to the lesson about ownership, if we try to compile the following code:
149
152
150
153
```rust
@@ -281,7 +284,7 @@ error[E0597]: `string2` does not live long enough
281
284
282
285
## Lifetime elision
283
286
284
-
We now know how to explicitly write lifetime parameters, but you might recall that we don't always have to that. Indeed, Rust will first try to figure out the lifetimes itself, applying a set of predefined rules. We call this _lifetime elision_.
287
+
We now know how to explicitly write lifetime parameters, but you might recall that we don't always have to do that. Indeed, Rust will first try to figure out the lifetimes itself, applying a set of predefined rules. We call this _lifetime elision_.
0 commit comments