1
0
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:
Martin Geisler
2023-12-31 00:15:07 +01:00
committed by GitHub
parent f43e72e0ad
commit c9f66fd425
302 changed files with 3067 additions and 2622 deletions

View File

@ -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>>>>;
```

View File

@ -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>

View File

@ -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: {:?}",

View File

@ -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>

View File

@ -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`.

View File

@ -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>