You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-08-08 00:12:51 +02:00
add new RAII
intro segment
refresher on `RAII` and use the OS File Descriptor example to start the discussions around RAII all previous content is for now moved to `_old` for my own reference, will add the new content based on the agreed upon new structure next.
This commit is contained in:
@ -438,9 +438,6 @@
|
||||
- [Parse, Don't Validate](idiomatic/leveraging-the-type-system/newtype-pattern/parse-don-t-validate.md)
|
||||
- [Is It Encapsulated?](idiomatic/leveraging-the-type-system/newtype-pattern/is-it-encapsulated.md)
|
||||
- [RAII](idiomatic/leveraging-the-type-system/raii.md)
|
||||
- [Drop Limitations](idiomatic/leveraging-the-type-system/raii/drop_limitations.md)
|
||||
- [Drop Bomb](idiomatic/leveraging-the-type-system/raii/drop_bomb.md)
|
||||
- [Scope Guards](idiomatic/leveraging-the-type-system/raii/scope_guards.md)
|
||||
|
||||
---
|
||||
|
||||
|
166
src/idiomatic/leveraging-the-type-system/_old/raii.md
Normal file
166
src/idiomatic/leveraging-the-type-system/_old/raii.md
Normal file
@ -0,0 +1,166 @@
|
||||
---
|
||||
minutes: 30
|
||||
---
|
||||
|
||||
# RAII and `Drop` in Practice
|
||||
|
||||
RAII (_Resource Acquisition Is Initialization_) means tying the lifetime of a
|
||||
resource to the lifetime of a value.
|
||||
|
||||
Rust uses RAII for managing heap memory. The `Drop` trait lets you extend this
|
||||
pattern to anything else.
|
||||
|
||||
```rust
|
||||
use std::sync::Mutex;
|
||||
|
||||
fn main() {
|
||||
let mutex = Mutex::new(vec![1, 2, 3]);
|
||||
|
||||
{
|
||||
let mut data = mutex.lock().unwrap();
|
||||
data.push(4); // lock held here
|
||||
} // lock automatically released here
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
- In the above example
|
||||
[the `Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) owns its
|
||||
data: you can’t access the value inside without first acquiring the lock.
|
||||
|
||||
`mux.lock()` returns a
|
||||
[`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html),
|
||||
which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) to
|
||||
the data and implements
|
||||
[`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html)
|
||||
|
||||
TODO: consider devoting 1-2 slides to demonstrate the relevant snippets of
|
||||
Mutex and MutexGuard API
|
||||
|
||||
- You may recall from
|
||||
[the Memory Management segment](../../memory-management/drop.md) that the
|
||||
[`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html) lets you
|
||||
define what should happen when a resource is dropped.
|
||||
|
||||
- In
|
||||
[the Blocks and Scopes chapter](../../control-flow-basics/blocks-and-scopes.md),
|
||||
we saw the most common situation where a resource is dropped: when the scope
|
||||
of its _owner_ ends at the boundary of a block (`{}`).
|
||||
|
||||
- The use of
|
||||
[`std::mem::drop(val)`](https://doc.rust-lang.org/std/mem/fn.drop.html)
|
||||
allows you to _move_ a value out of scope before the block ends.
|
||||
|
||||
- There are also other scenarios where this can happen, such as when the value
|
||||
owning the resource is "shadowed" by another value:
|
||||
|
||||
```rust
|
||||
let a = String::from("foo");
|
||||
let a = 3; // ^ The previous string is dropped here
|
||||
// because we shadow its binding with a new value.
|
||||
```
|
||||
|
||||
- Recall also from [the Drop chapter](../../memory-management/drop.md) that
|
||||
for a composite type such as a `struct`, all its fields will be dropped when
|
||||
the struct itself is dropped. If a field implements the `Drop` trait, its
|
||||
`Drop::drop` _trait_ method will also be invoked.
|
||||
|
||||
- In any scenario where the stack unwinds the value, it is guaranteed that the
|
||||
[`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
|
||||
method of a value `a` will be called.
|
||||
|
||||
- This holds true for happy paths such as:
|
||||
|
||||
- Exiting a block or function scope.
|
||||
|
||||
- Returning early with an explicit `return` statement, or implicitly by
|
||||
using [the Try operator (`?`)](../../error-handling/try.md) to
|
||||
early-return `Option` or `Result` values.
|
||||
|
||||
- It also holds for unexpected scenarios where a `panic` is triggered, if:
|
||||
|
||||
- The stack is unwound on panic (which is the default), allowing for
|
||||
graceful cleanup of resources.
|
||||
|
||||
(TODO: we might want to refactor this to make clear this also happens in
|
||||
normal function returns)
|
||||
|
||||
This unwind behavior can be overridden to instead
|
||||
[abort on panic](https://github.com/rust-lang/rust/blob/master/library/panic_abort/src/lib.rs),
|
||||
in which case no destructors will run.
|
||||
|
||||
- No panic occurs within any of the `drop` methods invoked before reaching
|
||||
the `drop` call of the object `a`.
|
||||
|
||||
- Note that
|
||||
[an explicit exit of the program](https://doc.rust-lang.org/std/process/fn.exit.html),
|
||||
as sometimes used in CLI tools, terminates the process immediately. In other
|
||||
words, the stack is not unwound in this case, and the `drop` method will not
|
||||
be called.
|
||||
|
||||
TODO: apply feedback:
|
||||
|
||||
```
|
||||
I think this whole point can be pulled out into its own slide.
|
||||
Talking about when Drop runs and when it doesn't is worth covering
|
||||
directly. I think you'd also want to talk about forget on that slide,
|
||||
and maybe briefly note that leaking destructors is not unsafe
|
||||
(unless you plan to cover them elsewhere).
|
||||
```
|
||||
|
||||
- `Drop` is a great fit for use cases like `Mutex`.
|
||||
|
||||
When the guard goes out of scope,
|
||||
[`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
|
||||
is called and unlocks the mutex automatically.
|
||||
|
||||
In contrast to C++ or Java, where you often have to unlock manually or use a
|
||||
`lock/unlock` pattern, Rust ensures the lock _cannot_ be forgotten, thanks to
|
||||
the compiler.
|
||||
|
||||
TODO: revisit references to C++ and Java, be careful in wording. E.g. C++ and
|
||||
Java their mutexes are also RAII based
|
||||
([std::lock_guard](https://en.cppreference.com/w/cpp/thread/lock_guard.html),
|
||||
[absl::MutexLock](https://github.com/abseil/abseil-cpp/blob/master/absl/synchronization/mutex.h#L583),
|
||||
`synchronized(obj) {}` in Java).
|
||||
|
||||
TODO: incorporate @gribozavr's feedback here:
|
||||
|
||||
```
|
||||
It can't be forgotten, but the MutexGuard can be forgot()'en intentionally, or leaked - like any other value.
|
||||
|
||||
It is a good tie-in to discuss use cases for drop: it is good for cleaning up things within the scope of a process, but not the right tool for guaranteeing that something happens outside of the process (e.g., on local disk, or in another service in a distributed system).
|
||||
|
||||
For example, it is a bad idea to rely exclusively on drop to clean up temporary files: if the program terminates in a way that skips running drop, temporary files will persist, and eventually the computer will run out of space. This can happen if the program crashes or leaks the value whose drop is responsible for deleting the file. In addition to a drop implementation within the program, one also needs a classic unix-style temp file reaper that runs as a separate process.
|
||||
```
|
||||
|
||||
- In other scenarios, the `Drop` trait shows its limitations. Next, we'll look
|
||||
at what those are and how we can address them.
|
||||
|
||||
TODO: apply feedback from @gribozavr when refactoring the RAII content:
|
||||
|
||||
```
|
||||
First, a custom File type that wraps a file descriptor. A file descriptor is a classic OS-level resource. We could show how to implement a simple read-only file type a with a minimal API: open() and read() to read a single byte. Then show how to implement Drop. Discuss when the drop() function runs, and how it isn't run when values are moved (contrast with C++ where the destructor always runs at the end of the scope, even for moved-from values). Show the forget() function, discuss its signature and what it means.
|
||||
|
||||
In other words, use this simple File type as an opportunity to do a 5-minute refresher on drop and move semantics. I see you're already doing it with instructor notes like "for a composite type such as a struct, all its fields will be dropped" and by mentioning the std::mem::drop() function. Let's lean more into it and make sure that during this discussion we have an example of a drop implementation on the screen.
|
||||
|
||||
Then we move on to Mutex. There we would focus on explaining the idea that for a mutex the "resource" is more abstract. In case of a mutex, the resource is exclusive access to the wrapped value. Thus, we need a second type - a MutexGuard - to represent that.
|
||||
|
||||
The mutex example is perfect to facilitate the drop x panic discussion. Maybe draft an extra slide that shows what happens by default with a naive drop implementation (the drop simply runs, no special code is needed for that), and then discuss why panics poison the mutex in Rust (there is a good chance that the code was mutating the shared data, so its invariants might be broken).
|
||||
```
|
||||
|
||||
Also apply feedback from @djimitche:
|
||||
|
||||
```
|
||||
It's probably only necessary to include one "callback" to Fundamentals --
|
||||
the important point is to that this slide is a quick review of previous
|
||||
content, and if students need a deeper refresher they can find that
|
||||
content in the Fundamentals course.
|
||||
|
||||
That said, these speaker notes are pretty long! Is it possible to trim
|
||||
this down to just call out the bits necessary for the RAII patterns
|
||||
introduced here, leaving the rest to the students' memory of Fundamentals?
|
||||
```
|
||||
|
||||
</details>
|
@ -13,7 +13,6 @@ handled normally in Drop (realistically, commit and rollback would want to
|
||||
return Results to indicate if they succeeded or not). This is one of the
|
||||
limitations of Drop mentioned on the previous slide, so it'd be worth
|
||||
noting that this pattern is a common way to work around that limitation.
|
||||
|
||||
---
|
||||
|
||||
```rust
|
||||
@ -92,17 +91,14 @@ impl Drop for Transaction {
|
||||
it's typically not possible to just rollback a transaction in drop().
|
||||
```
|
||||
|
||||
- APIs that are expected to panic like this should document
|
||||
the cases when a panic will occur under a `Panics` section.
|
||||
- APIs that are expected to panic like this should document the cases when a
|
||||
panic will occur under a `Panics` section.
|
||||
|
||||
(^ TODO: this was reworded to be more minimal. Shorter the speaker
|
||||
notes the better, to make it easier to skim through as instructor)
|
||||
Original:
|
||||
> Similar to unsafe code, it is recommended that APIs with
|
||||
> expectations like
|
||||
> these are clearly documented under a Panic section.
|
||||
> This helps ensure that
|
||||
> users of the API are aware of the consequences of misuse.
|
||||
(^ TODO: this was reworded to be more minimal. Shorter the speaker notes the
|
||||
better, to make it easier to skim through as instructor) Original:
|
||||
> Similar to unsafe code, it is recommended that APIs with expectations like
|
||||
> these are clearly documented under a Panic section. This helps ensure that
|
||||
> users of the API are aware of the consequences of misuse.
|
||||
|
||||
TODO: apply feedback:
|
||||
|
||||
@ -141,9 +137,8 @@ impl Drop for Transaction {
|
||||
|
||||
- [`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html):
|
||||
A zero-cost wrapper that disables the automatic drop behavior of a value,
|
||||
making manual cleanup required and explicit.
|
||||
This requires unsafe code to use,
|
||||
though, so it's recommended to only use this if strictly necessary.
|
||||
making manual cleanup required and explicit. This requires unsafe code to
|
||||
use, though, so it's recommended to only use this if strictly necessary.
|
||||
|
||||
- The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/) provides
|
||||
a way to enforce that certain values are not dropped unless explicitly
|
@ -19,7 +19,8 @@ fn write_log() -> io::Result<()> {
|
||||
<details>
|
||||
|
||||
- In the earlier example, our `File` resource owns a file handle provided by the
|
||||
operating system. (TODO: be careful in wording: earlier is ambiguous here. Better use "above".)
|
||||
operating system. (TODO: be careful in wording: earlier is ambiguous here.
|
||||
Better use "above".)
|
||||
|
||||
[As stated in the documentation](https://doc.rust-lang.org/std/fs/struct.File.html):
|
||||
|
||||
@ -61,8 +62,8 @@ fn write_log() -> io::Result<()> {
|
||||
|
||||
- One workaround is to panic inside `drop` when a failure occurs. However, this
|
||||
is risky—if a panic happens while the stack is already unwinding, the program
|
||||
will abort immediately, and remaining resources will not be cleaned up.
|
||||
(TODO: be careful in wording and context. E.g. here it is about external resources)
|
||||
will abort immediately, and remaining resources will not be cleaned up. (TODO:
|
||||
be careful in wording and context. E.g. here it is about external resources)
|
||||
|
||||
While panicking in `drop` can serve certain purposes (see
|
||||
[the next chapter on "drop bombs"](./drop_bomb.md)), it should be used
|
||||
@ -75,7 +76,8 @@ fn write_log() -> io::Result<()> {
|
||||
|
||||
(TODO: non-deterministic is incorrect here, fix wording and description)
|
||||
|
||||
(TODO: be careful with wording 'you cannot control'. As you can control, by impl drop)
|
||||
(TODO: be careful with wording 'you cannot control'. As you can control, by
|
||||
impl drop)
|
||||
|
||||
This matters particularly for I/O: normally you might set a timeout on
|
||||
blocking operations, but when I/O occurs in a `drop` implementation, you have
|
||||
@ -108,9 +110,8 @@ fn write_log() -> io::Result<()> {
|
||||
cause undefined behavior. The poisoned state disappears along with the
|
||||
termination of the program.
|
||||
|
||||
(TODO: apply feedback:
|
||||
Note that the chapter does not discuss poisoned mutexes
|
||||
at the moment (I'm requesting that to be added in my comments above)
|
||||
(TODO: apply feedback: Note that the chapter does not discuss poisoned mutexes
|
||||
at the moment (I'm requesting that to be added in my comments above)
|
||||
|
||||
- For use cases such as I/O or FFI, it may be preferable to let the user clean
|
||||
up resources explicitly using a close function.
|
@ -2,165 +2,111 @@
|
||||
minutes: 30
|
||||
---
|
||||
|
||||
# RAII and `Drop` in Practice
|
||||
# RAII: `Drop` trait
|
||||
|
||||
RAII (_Resource Acquisition Is Initialization_) means tying the lifetime of a
|
||||
RAII (Resource Acquisition Is Initialization) means tying the lifetime of a
|
||||
resource to the lifetime of a value.
|
||||
|
||||
Rust uses RAII for managing heap memory. The `Drop` trait lets you
|
||||
extend this pattern to anything else.
|
||||
[Rust uses RAII to manage memory](https://doc.rust-lang.org/rust-by-example/scope/raii.html),
|
||||
and the `Drop` trait allows you to extend this to other resources, such as file
|
||||
descriptors or locks.
|
||||
|
||||
```rust
|
||||
use std::sync::Mutex;
|
||||
```rust,editable
|
||||
struct FileLock;
|
||||
struct File {
|
||||
stub: Option<u8>,
|
||||
lock: FileLock,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Error;
|
||||
|
||||
impl File {
|
||||
fn open(path: &str) -> Result<Self, Error> {
|
||||
println!("acquire file descriptor: {path}");
|
||||
Ok(Self { stub: Some(1), lock: FileLock })
|
||||
}
|
||||
|
||||
fn read(&mut self) -> Result<u8, Error> {
|
||||
self.stub.take().ok_or(Error)
|
||||
}
|
||||
|
||||
fn close(self) -> Result<(), Error> {
|
||||
self.lock.release()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileLock {
|
||||
fn release(self) -> Result<(), Error> {
|
||||
println!("release file descriptor");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mutex = Mutex::new(vec![1, 2, 3]);
|
||||
let mut file = File::open("example.txt").unwrap();
|
||||
|
||||
{
|
||||
let mut data = mutex.lock().unwrap();
|
||||
data.push(4); // lock held here
|
||||
} // lock automatically released here
|
||||
let mut content = Vec::new();
|
||||
while let Ok(byte) = file.read() {
|
||||
content.push(byte);
|
||||
}
|
||||
|
||||
println!("content: {content:?}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
- In the above example
|
||||
[the `Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) owns its
|
||||
data: you can’t access the value inside without first acquiring the lock.
|
||||
- The example shows how easy it is to forget releasing a file descriptor when
|
||||
managing it manually. In fact, the current code does not release it at all.
|
||||
Did anyone notice that `file.close()` is missing?
|
||||
|
||||
`mux.lock()` returns a
|
||||
[`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html),
|
||||
which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) to
|
||||
the data and implements
|
||||
[`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html)
|
||||
- Try inserting `file.close().unwrap();` at the end of `main`. Then try moving
|
||||
it before the loop — Rust will prevent that. Once `file` is moved, it can no
|
||||
longer be used. The borrow checker ensures we cannot access a `File` after it
|
||||
has been closed.
|
||||
|
||||
TODO: consider devoting 1-2 slides to demonstrate the relevant snippets of
|
||||
Mutex and MutexGuard API
|
||||
- Instead of relying on the programmer to remember to call `close()`, we can
|
||||
implement the `Drop` trait to handle cleanup automatically. This ties the
|
||||
resource to the lifetime of the `File` value. But note: `Drop` cannot return
|
||||
errors. Anything fallible must be handled inside the `drop()` method or
|
||||
avoided altogether.
|
||||
|
||||
- You may recall from
|
||||
[the Memory Management segment](../../memory-management/drop.md) that the
|
||||
[`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html) lets you
|
||||
define what should happen when a resource is dropped.
|
||||
|
||||
- In
|
||||
[the Blocks and Scopes chapter](../../control-flow-basics/blocks-and-scopes.md),
|
||||
we saw the most common situation where a resource is dropped: when the scope
|
||||
of its _owner_ ends at the boundary of a block (`{}`).
|
||||
|
||||
- The use of
|
||||
[`std::mem::drop(val)`](https://doc.rust-lang.org/std/mem/fn.drop.html)
|
||||
allows you to _move_ a value out of scope before the block ends.
|
||||
|
||||
- There are also other scenarios where this can happen, such as when the value
|
||||
owning the resource is "shadowed" by another value:
|
||||
|
||||
```rust
|
||||
let a = String::from("foo");
|
||||
let a = 3; // ^ The previous string is dropped here
|
||||
// because we shadow its binding with a new value.
|
||||
```
|
||||
|
||||
- Recall also from [the Drop chapter](../../memory-management/drop.md) that
|
||||
for a composite type such as a `struct`, all its fields will be dropped when
|
||||
the struct itself is dropped. If a field implements the `Drop` trait, its
|
||||
`Drop::drop` _trait_ method will also be invoked.
|
||||
|
||||
- In any scenario where the stack unwinds the value, it is guaranteed that the
|
||||
[`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
|
||||
method of a value `a` will be called.
|
||||
|
||||
- This holds true for happy paths such as:
|
||||
|
||||
- Exiting a block or function scope.
|
||||
|
||||
- Returning early with an explicit `return` statement, or implicitly by
|
||||
using [the Try operator (`?`)](../../error-handling/try.md) to
|
||||
early-return `Option` or `Result` values.
|
||||
|
||||
- It also holds for unexpected scenarios where a `panic` is triggered, if:
|
||||
|
||||
- The stack is unwound on panic (which is the default), allowing for graceful
|
||||
cleanup of resources.
|
||||
|
||||
(TODO: we might want to refactor this to make clear
|
||||
this also happens in normal function returns)
|
||||
|
||||
This unwind behavior can be overridden to instead
|
||||
[abort on panic](https://github.com/rust-lang/rust/blob/master/library/panic_abort/src/lib.rs),
|
||||
in which case no destructors will run.
|
||||
|
||||
- No panic occurs within any of the `drop` methods invoked before reaching
|
||||
the `drop` call of the object `a`.
|
||||
|
||||
- Note that
|
||||
[an explicit exit of the program](https://doc.rust-lang.org/std/process/fn.exit.html),
|
||||
as sometimes used in CLI tools, terminates the process immediately. In other
|
||||
words, the stack is not unwound in this case, and the `drop` method will not
|
||||
be called.
|
||||
|
||||
TODO: apply feedback:
|
||||
|
||||
```
|
||||
I think this whole point can be pulled out into its own slide.
|
||||
Talking about when Drop runs and when it doesn't is worth covering
|
||||
directly. I think you'd also want to talk about forget on that slide,
|
||||
and maybe briefly note that leaking destructors is not unsafe
|
||||
(unless you plan to cover them elsewhere).
|
||||
```
|
||||
|
||||
- `Drop` is a great fit for use cases like `Mutex`.
|
||||
|
||||
When the guard goes out of scope,
|
||||
[`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop)
|
||||
is called and unlocks the mutex automatically.
|
||||
|
||||
In contrast to C++ or Java, where you often have to unlock manually or use a
|
||||
`lock/unlock` pattern, Rust ensures the lock _cannot_ be forgotten, thanks to
|
||||
the compiler.
|
||||
|
||||
TODO: revisit references to C++ and Java, be careful in wording.
|
||||
E.g. C++ and Java their mutexes are also RAII based
|
||||
([std::lock_guard](https://en.cppreference.com/w/cpp/thread/lock_guard.html), [absl::MutexLock](https://github.com/abseil/abseil-cpp/blob/master/absl/synchronization/mutex.h#L583), `synchronized(obj) {}` in Java).
|
||||
|
||||
TODO: incorporate @gribozavr's feedback here:
|
||||
|
||||
```
|
||||
It can't be forgotten, but the MutexGuard can be forgot()'en intentionally, or leaked - like any other value.
|
||||
|
||||
It is a good tie-in to discuss use cases for drop: it is good for cleaning up things within the scope of a process, but not the right tool for guaranteeing that something happens outside of the process (e.g., on local disk, or in another service in a distributed system).
|
||||
|
||||
For example, it is a bad idea to rely exclusively on drop to clean up temporary files: if the program terminates in a way that skips running drop, temporary files will persist, and eventually the computer will run out of space. This can happen if the program crashes or leaks the value whose drop is responsible for deleting the file. In addition to a drop implementation within the program, one also needs a classic unix-style temp file reaper that runs as a separate process.
|
||||
```rust,compile_fail
|
||||
impl Drop for FileLock {
|
||||
fn drop(&mut self) {
|
||||
println!("release file descriptor automatically");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- In other scenarios, the `Drop` trait shows its limitations. Next, we'll look
|
||||
at what those are and how we can address them.
|
||||
- If we keep both `drop()` and `close()`, the file descriptor is released twice.
|
||||
To avoid this, remove `close()` and rely on `Drop` alone.
|
||||
|
||||
TODO: apply feedback from @gribozavr when refactoring the RAII content:
|
||||
- Demonstrate ownership transfer by moving the file into a separate `read_all()`
|
||||
function. The file is dropped when that local variable goes out of scope — not
|
||||
in `main`. This contrasts with C++, where the original scope always runs the
|
||||
destructor, even after moves.
|
||||
|
||||
```
|
||||
First, a custom File type that wraps a file descriptor. A file descriptor is a classic OS-level resource. We could show how to implement a simple read-only file type a with a minimal API: open() and read() to read a single byte. Then show how to implement Drop. Discuss when the drop() function runs, and how it isn't run when values are moved (contrast with C++ where the destructor always runs at the end of the scope, even for moved-from values). Show the forget() function, discuss its signature and what it means.
|
||||
- Add `panic!("oops")` at the start of `read_all()` to illustrate that `drop()`
|
||||
still runs during unwinding. Rust guarantees that destructors run during a
|
||||
panic unless the panic strategy is set to abort.
|
||||
|
||||
In other words, use this simple File type as an opportunity to do a 5-minute refresher on drop and move semantics. I see you're already doing it with instructor notes like "for a composite type such as a struct, all its fields will be dropped" and by mentioning the std::mem::drop() function. Let's lean more into it and make sure that during this discussion we have an example of a drop implementation on the screen.
|
||||
- There are exceptions where destructors will not run:
|
||||
- If a destructor panics during unwinding, the program aborts immediately.
|
||||
- The program also aborts immediately when using `std::process::exit()` or
|
||||
when the panic strategy is set to `abort`.
|
||||
|
||||
Then we move on to Mutex. There we would focus on explaining the idea that for a mutex the "resource" is more abstract. In case of a mutex, the resource is exclusive access to the wrapped value. Thus, we need a second type - a MutexGuard - to represent that.
|
||||
### More to Explore
|
||||
|
||||
The mutex example is perfect to facilitate the drop x panic discussion. Maybe draft an extra slide that shows what happens by default with a naive drop implementation (the drop simply runs, no special code is needed for that), and then discuss why panics poison the mutex in Rust (there is a good chance that the code was mutating the shared data, so its invariants might be broken).
|
||||
```
|
||||
The `Drop` trait has another important limitation: it is not `async`.
|
||||
|
||||
Also apply feedback from @djimitche:
|
||||
This means you cannot `await` inside a destructor, which is often needed when
|
||||
cleaning up asynchronous resources like sockets, database connections, or tasks
|
||||
that must notify another system before shutdown.
|
||||
|
||||
```
|
||||
|
||||
|
||||
It's probably only necessary to include one "callback" to Fundamentals --
|
||||
the important point is to that this slide is a quick review of previous
|
||||
content, and if students need a deeper refresher they can find that
|
||||
content in the Fundamentals course.
|
||||
|
||||
That said, these speaker notes are pretty long! Is it possible to trim
|
||||
this down to just call out the bits necessary for the RAII patterns
|
||||
introduced here, leaving the rest to the students' memory of Fundamentals?
|
||||
```
|
||||
- Learn more:
|
||||
<https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html>
|
||||
- Available on nightly:
|
||||
<https://doc.rust-lang.org/nightly/std/future/trait.AsyncDrop.html>
|
||||
|
||||
</details>
|
||||
|
@ -34,11 +34,10 @@ or more slides, depending on its complexity and relevance.
|
||||
|
||||
## Target Audience
|
||||
|
||||
Engineers with at least 2-3 years of coding experience in C, C++11 or
|
||||
newer, Java 7 or newer, Python 2 or 3, Go or any other similar
|
||||
imperative programming language. We have no expectation of experience
|
||||
with more modern or feature-rich languages like Swift, Kotlin, C#, or
|
||||
TypeScript.
|
||||
Engineers with at least 2-3 years of coding experience in C, C++11 or newer,
|
||||
Java 7 or newer, Python 2 or 3, Go or any other similar imperative programming
|
||||
language. We have no expectation of experience with more modern or feature-rich
|
||||
languages like Swift, Kotlin, C#, or TypeScript.
|
||||
|
||||
### Foundations of API design
|
||||
|
||||
|
Reference in New Issue
Block a user