1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-06-16 22:27:34 +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

@ -6,17 +6,19 @@ minutes: 10
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, garbage collection, or RAII.
- 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, garbage collection, or RAII.
Rust offers a new mix:
> Full control *and* safety via compile time enforcement of correct memory
> Full control _and_ safety via compile time enforcement of correct memory
> management.
It does this with an explicit ownership concept.
@ -26,20 +28,19 @@ It does this with an explicit ownership concept.
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 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.
- 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.
- 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

View File

@ -4,7 +4,8 @@ minutes: 2
# Clone
Sometimes you _want_ to make a copy of a value. The `Clone` trait accomplishes this.
Sometimes you _want_ to make a copy of a value. The `Clone` trait accomplishes
this.
```rust,editable
#[derive(Default)]

View File

@ -5,11 +5,13 @@ minutes: 5
<!-- NOTES:
Present Copy as added functionality on top of the default move semantics: with Copy, the old value does not become invalid; Can derive Copy for a type if it implements Clone
-->
# Copy Types
While move semantics are the default, certain types are copied by default:
<!-- mdbook-xgettext: skip -->
```rust,editable
fn main() {
let x = 42;
@ -24,6 +26,7 @@ These types implement the `Copy` trait.
You can opt-in your own types to use copy semantics:
<!-- mdbook-xgettext: skip -->
```rust,editable
#[derive(Copy, Clone, Debug)]
struct Point(i32, i32);
@ -36,22 +39,26 @@ fn main() {
}
```
* After the assignment, both `p1` and `p2` own their own data.
* We can also use `p1.clone()` to explicitly copy the data.
- After the assignment, both `p1` and `p2` own their own data.
- We can also use `p1.clone()` to explicitly copy the data.
<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.
- 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.
- 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.
</details>

View File

@ -4,7 +4,8 @@ minutes: 10
# The `Drop` Trait
Values which implement [`Drop`][1] can specify code to run when they go out of scope:
Values which implement [`Drop`][1] can specify code to run when they go out of
scope:
```rust,editable
struct Droppable {
@ -35,24 +36,24 @@ fn main() {
<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.
- 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()`.
- 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()`.
</details>

View File

@ -114,10 +114,8 @@ impl PackageBuilder {
fn main() {
let base64 = PackageBuilder::new("base64").version("0.13").build();
println!("base64: {base64:?}");
let log = PackageBuilder::new("log")
.version("0.4")
.language(Language::Rust)
.build();
let log =
PackageBuilder::new("log").version("0.4").language(Language::Rust).build();
println!("log: {log:?}");
let serde = PackageBuilder::new("serde")
.authors(vec!["djmitche".into()])

View File

@ -15,9 +15,9 @@ fn main() {
}
```
* 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.
- 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`:
@ -76,21 +76,29 @@ fn main() {
<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!).
- 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.
- 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).
- Simple values (such as integers) can be marked `Copy` (see later slides).
* In Rust, clones are explicit (by using `clone`).
- 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.
- 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
@ -103,12 +111,11 @@ 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.
- 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:
```bob
Stack Heap
.- - - - - - - - - - - - - -. .- - - - - - - - - - - -.
@ -146,7 +153,6 @@ After copy-assignment:
`- - - - - - - - - - - - - -'
```
Key points:
- C++ has made a slightly different choice than Rust. Because `=` copies data,
@ -158,8 +164,8 @@ Key points:
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.
- Unlike Rust, `=` in C++ can run arbitrary code as determined by the type which
is being copied or moved.
[`std::move`]: https://en.cppreference.com/w/cpp/utility/move

View File

@ -8,6 +8,7 @@ All variable bindings have a _scope_ where they are valid and it is an error to
use a variable outside its scope:
<!-- mdbook-xgettext: skip -->
```rust,editable,compile_fail
struct Point(i32, i32);
@ -23,8 +24,8 @@ fn main() {
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.
At the end of the scope, the variable is _dropped_ and the data is freed. A
destructor can run here to free up resources.
<details>

View File

@ -6,16 +6,16 @@ minutes: 5
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.
- 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.
- 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
@ -44,9 +44,12 @@ fn main() {
<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.
- 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]
- 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