You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-06-15 22:00:26 +02:00
Format all Markdown files with dprint
(#1157)
This is the result of running `dprint fmt` after removing `src/` from the list of excluded directories. This also reformats the Rust code: we might want to tweak this a bit in the future since some of the changes removes the hand-formatting. Of course, this formatting can be seen as a mis-feature, so maybe this is good overall. Thanks to mdbook-i18n-helpers 0.2, the POT file is nearly unchanged after this, meaning that all existing translations remain valid! A few messages were changed because of stray whitespace characters: msgid "" "Slices always borrow from another object. In this example, `a` has to remain " -"'alive' (in scope) for at least as long as our slice. " +"'alive' (in scope) for at least as long as our slice." msgstr "" The formatting is enforced in CI and we will have to see how annoying this is in practice for the many contributors. If it becomes annoying, we should look into fixing dprint/check#11 so that `dprint` can annotate the lines that need fixing directly, then I think we can consider more strict formatting checks. I added more customization to `rustfmt.toml`. This is to better emulate the dense style used in the course: - `max_width = 85` allows lines to take up the full width available in our code blocks (when taking margins and the line numbers into account). - `wrap_comments = true` ensures that we don't show very long comments in the code examples. I edited some comments to shorten them and avoid unnecessary line breaks — please trim other unnecessarily long comments when you see them! Remember we're writing code for slides 😄 - `use_small_heuristics = "Max"` allows for things like struct literals and if-statements to take up the full line width configured above. The formatting settings apply to all our Rust code right now — I think we could improve this with https://github.com/dprint/dprint/issues/711 which lets us add per-directory `dprint` configuration files. However, the `inherit: true` setting is not yet implemented (as far as I can tell), so a nested configuration file will have to copy most or all of the top-level file.
This commit is contained in:
@ -4,7 +4,8 @@ minutes: 2
|
||||
|
||||
# Type Aliases
|
||||
|
||||
A type alias creates a name for another type. The two types can be used interchangeably.
|
||||
A type alias creates a name for another type. The two types can be used
|
||||
interchangeably.
|
||||
|
||||
```rust,editable
|
||||
enum CarryableConcreteItem {
|
||||
@ -15,7 +16,8 @@ enum CarryableConcreteItem {
|
||||
type Item = CarryableConcreteItem;
|
||||
|
||||
// Aliases are more useful with long, complex types:
|
||||
use std::{sync::{Arc, RwLock}, cell::RefCell};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::{Arc, RwLock};
|
||||
type PlayerInventory = RwLock<Vec<Arc<RefCell<Item>>>>;
|
||||
```
|
||||
|
||||
|
@ -4,8 +4,8 @@ minutes: 5
|
||||
|
||||
# Enums
|
||||
|
||||
The `enum` keyword allows the creation of a type which has a few
|
||||
different variants:
|
||||
The `enum` keyword allows the creation of a type which has a few different
|
||||
variants:
|
||||
|
||||
```rust,editable
|
||||
#[derive(Debug)]
|
||||
@ -31,82 +31,89 @@ fn main() {
|
||||
|
||||
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
|
||||
- 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):
|
||||
- You can control the discriminant if needed (e.g., for compatibility with C):
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
#[repr(u32)]
|
||||
enum Bar {
|
||||
A, // 0
|
||||
B = 10000,
|
||||
C, // 10001
|
||||
}
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```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);
|
||||
}
|
||||
```
|
||||
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.
|
||||
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](https://doc.rust-lang.org/std/option/#representation), Rust guarantees
|
||||
that `size_of::<T>()` equals `size_of::<Option<T>>()`.
|
||||
- Null pointer optimization: For
|
||||
[some types](https://doc.rust-lang.org/std/option/#representation), Rust
|
||||
guarantees that `size_of::<T>()` equals `size_of::<Option<T>>()`.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
use std::mem::transmute;
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
```rust,editable
|
||||
use std::mem::transmute;
|
||||
|
||||
macro_rules! dbg_bits {
|
||||
($e:expr, $bit_type:ty) => {
|
||||
println!("- {}: {:#x}", stringify!($e), transmute::<_, $bit_type>($e));
|
||||
};
|
||||
}
|
||||
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);
|
||||
fn main() {
|
||||
unsafe {
|
||||
println!("bool:");
|
||||
dbg_bits!(false, u8);
|
||||
dbg_bits!(true, u8);
|
||||
|
||||
println!("Option<bool>:");
|
||||
dbg_bits!(None::<bool>, u8);
|
||||
dbg_bits!(Some(false), u8);
|
||||
dbg_bits!(Some(true), u8);
|
||||
println!("Option<bool>:");
|
||||
dbg_bits!(None::<bool>, u8);
|
||||
dbg_bits!(Some(false), u8);
|
||||
dbg_bits!(Some(true), u8);
|
||||
|
||||
println!("Option<Option<bool>>:");
|
||||
dbg_bits!(Some(Some(false)), u8);
|
||||
dbg_bits!(Some(Some(true)), u8);
|
||||
dbg_bits!(Some(None::<bool>), u8);
|
||||
dbg_bits!(None::<Option<bool>>, u8);
|
||||
println!("Option<Option<bool>>:");
|
||||
dbg_bits!(Some(Some(false)), u8);
|
||||
dbg_bits!(Some(Some(true)), u8);
|
||||
dbg_bits!(Some(None::<bool>), u8);
|
||||
dbg_bits!(None::<Option<bool>>, u8);
|
||||
|
||||
println!("Option<&i32>:");
|
||||
dbg_bits!(None::<&i32>, usize);
|
||||
dbg_bits!(Some(&0i32), usize);
|
||||
}
|
||||
}
|
||||
```
|
||||
println!("Option<&i32>:");
|
||||
dbg_bits!(None::<&i32>, usize);
|
||||
dbg_bits!(Some(&0i32), usize);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
@ -96,10 +96,7 @@ fn main() {
|
||||
"A ground floor passenger has pressed the up button: {:?}",
|
||||
lobby_call_button_pressed(0, Direction::Up)
|
||||
);
|
||||
println!(
|
||||
"The car has arrived on the ground floor: {:?}",
|
||||
car_arrived(0)
|
||||
);
|
||||
println!("The car has arrived on the ground floor: {:?}", car_arrived(0));
|
||||
println!("The car door opened: {:?}", car_door_opened());
|
||||
println!(
|
||||
"A passenger has pressed the 3rd floor button: {:?}",
|
||||
|
@ -17,10 +17,7 @@ fn describe(person: &Person) {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut peter = Person {
|
||||
name: String::from("Peter"),
|
||||
age: 27,
|
||||
};
|
||||
let mut peter = Person { name: String::from("Peter"), age: 27 };
|
||||
describe(&peter);
|
||||
|
||||
peter.age = 28;
|
||||
@ -31,10 +28,7 @@ fn main() {
|
||||
let avery = Person { name, age };
|
||||
describe(&avery);
|
||||
|
||||
let jackie = Person {
|
||||
name: String::from("Jackie"),
|
||||
..avery
|
||||
};
|
||||
let jackie = Person { name: String::from("Jackie"), ..avery };
|
||||
describe(&jackie);
|
||||
}
|
||||
```
|
||||
@ -43,14 +37,20 @@ fn main() {
|
||||
|
||||
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.
|
||||
- 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.
|
||||
|
||||
</details>
|
||||
|
@ -4,8 +4,8 @@ minutes: 5
|
||||
|
||||
# Static and Const
|
||||
|
||||
Static and constant variables are two different ways to create globally-scoped values that
|
||||
cannot be moved or reallocated during the execution of the program.
|
||||
Static and constant variables are two different ways to create globally-scoped
|
||||
values that cannot be moved or reallocated during the execution of the program.
|
||||
|
||||
## `const`
|
||||
|
||||
@ -13,6 +13,7 @@ Constant variables are evaluated at compile time and their values are inlined
|
||||
wherever they are used:
|
||||
|
||||
<!-- mdbook-xgettext: skip -->
|
||||
|
||||
```rust,editable
|
||||
const DIGEST_SIZE: usize = 3;
|
||||
const ZERO: Option<u8> = Some(42);
|
||||
@ -33,11 +34,13 @@ fn main() {
|
||||
|
||||
According to the [Rust RFC Book][1] 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.
|
||||
Only functions marked `const` can be called at compile time to generate `const`
|
||||
values. `const` functions can however be called at runtime.
|
||||
|
||||
## `static`
|
||||
|
||||
Static variables will live during the whole execution of the program, and therefore will not move:
|
||||
Static variables will live during the whole execution of the program, and
|
||||
therefore will not move:
|
||||
|
||||
```rust,editable
|
||||
static BANNER: &str = "Welcome to RustOS 3.14";
|
||||
@ -47,31 +50,37 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
As noted in the [Rust RFC Book][1], 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.
|
||||
As noted in the [Rust RFC Book][1], 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.
|
||||
|
||||
<details>
|
||||
|
||||
* Mention that `const` behaves semantically similar to C++'s `constexpr`.
|
||||
* `static`, on the other hand, is much more similar to a `const` or mutable global variable in C++.
|
||||
* `static` provides object identity: an address in memory and state as required by types with interior mutability such as `Mutex<T>`.
|
||||
* It isn't super common that one would need a runtime evaluated constant, but it is helpful and safer than using a static.
|
||||
- Mention that `const` behaves semantically similar to C++'s `constexpr`.
|
||||
- `static`, on the other hand, is much more similar to a `const` or mutable
|
||||
global variable in C++.
|
||||
- `static` provides object identity: an address in memory and state as required
|
||||
by types with interior mutability such as `Mutex<T>`.
|
||||
- It isn't super common that one would need a runtime evaluated constant, but it
|
||||
is helpful and safer than using a static.
|
||||
|
||||
### Properties table:
|
||||
|
||||
| Property | Static | Constant |
|
||||
|---|---|---|
|
||||
| Has an address in memory | Yes | No (inlined) |
|
||||
| Lives for the entire duration of the program | Yes | No |
|
||||
| Can be mutable | Yes (unsafe) | No |
|
||||
| Evaluated at compile time | Yes (initialised at compile time) | Yes |
|
||||
| Inlined wherever it is used | No | Yes |
|
||||
| Property | Static | Constant |
|
||||
| -------------------------------------------- | --------------------------------- | ------------ |
|
||||
| Has an address in memory | Yes | No (inlined) |
|
||||
| Lives for the entire duration of the program | Yes | No |
|
||||
| Can be mutable | Yes (unsafe) | No |
|
||||
| Evaluated at compile time | Yes (initialised at compile time) | Yes |
|
||||
| Inlined wherever it is used | No | Yes |
|
||||
|
||||
# More to Explore
|
||||
|
||||
Because `static` variables are accessible from any thread, they must be `Sync`. Interior mutability
|
||||
is possible through a [`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html), atomic or
|
||||
Because `static` variables are accessible from any thread, they must be `Sync`.
|
||||
Interior mutability is possible through a
|
||||
[`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html), atomic or
|
||||
similar.
|
||||
|
||||
Thread-local data can be created with the macro `std::thread_local`.
|
||||
|
@ -5,6 +5,7 @@ minutes: 10
|
||||
<!-- NOTES:
|
||||
Tuple structs, newtype wrappers, unit-like structs, including initialization syntax
|
||||
-->
|
||||
|
||||
# Tuple Structs
|
||||
|
||||
If the field names are unimportant, you can use a tuple struct:
|
||||
@ -36,17 +37,23 @@ fn main() {
|
||||
let force = compute_thruster_force();
|
||||
set_thruster_force(force);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Newtypes are a great way to encode additional information about the value in a primitive type, for example:
|
||||
* The number is measured in some units: `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](https://en.wikipedia.org/wiki/Mars_Climate_Orbiter) failure.
|
||||
- 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](https://en.wikipedia.org/wiki/Mars_Climate_Orbiter)
|
||||
failure.
|
||||
|
||||
</details>
|
||||
|
Reference in New Issue
Block a user