You've already forked comprehensive-rust
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:
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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()])
|
||||
|
@ -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
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user