You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-08-08 08:22:52 +02:00
integrate feedback of first reviews (RAII)
- directly where possible - otherwise as inline feedback as notes to take into account for next draft
This commit is contained in:
@ -7,17 +7,17 @@ minutes: 30
|
|||||||
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.
|
resource to the lifetime of a value.
|
||||||
|
|
||||||
Rust applies RAII automatically for memory management. The `Drop` trait lets you
|
Rust uses RAII for managing heap memory. The `Drop` trait lets you
|
||||||
extend this pattern to anything else.
|
extend this pattern to anything else.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mux = Mutex::new(vec![1, 2, 3]);
|
let mutex = Mutex::new(vec![1, 2, 3]);
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut data = mux.lock().unwrap();
|
let mut data = mutex.lock().unwrap();
|
||||||
data.push(4); // lock held here
|
data.push(4); // lock held here
|
||||||
} // lock automatically released here
|
} // lock automatically released here
|
||||||
}
|
}
|
||||||
@ -33,10 +33,13 @@ fn main() {
|
|||||||
[`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html),
|
[`MutexGuard`](https://doc.rust-lang.org/std/sync/struct.MutexGuard.html),
|
||||||
which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) to
|
which [dereferences](https://doc.rust-lang.org/std/ops/trait.DerefMut.html) to
|
||||||
the data and implements
|
the data and implements
|
||||||
[`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html).
|
[`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
|
- You may recall from
|
||||||
[the Memory Management chapter](../../memory-management/drop.md) that the
|
[the Memory Management segment](../../memory-management/drop.md) that the
|
||||||
[`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html) lets you
|
[`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html) lets you
|
||||||
define what should happen when a resource is dropped.
|
define what should happen when a resource is dropped.
|
||||||
|
|
||||||
@ -77,11 +80,15 @@ fn main() {
|
|||||||
|
|
||||||
- It also holds for unexpected scenarios where a `panic` is triggered, if:
|
- It also holds for unexpected scenarios where a `panic` is triggered, if:
|
||||||
|
|
||||||
- The stack unwinds on panic (which is the default), allowing for graceful
|
- The stack is unwound on panic (which is the default), allowing for graceful
|
||||||
cleanup of resources.
|
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
|
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).
|
[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
|
- No panic occurs within any of the `drop` methods invoked before reaching
|
||||||
the `drop` call of the object `a`.
|
the `drop` call of the object `a`.
|
||||||
@ -92,6 +99,16 @@ fn main() {
|
|||||||
words, the stack is not unwound in this case, and the `drop` method will not
|
words, the stack is not unwound in this case, and the `drop` method will not
|
||||||
be called.
|
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`.
|
- `Drop` is a great fit for use cases like `Mutex`.
|
||||||
|
|
||||||
When the guard goes out of scope,
|
When the guard goes out of scope,
|
||||||
@ -102,15 +119,48 @@ fn main() {
|
|||||||
`lock/unlock` pattern, Rust ensures the lock _cannot_ be forgotten, thanks to
|
`lock/unlock` pattern, Rust ensures the lock _cannot_ be forgotten, thanks to
|
||||||
the compiler.
|
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
|
- In other scenarios, the `Drop` trait shows its limitations. Next, we'll look
|
||||||
at what those are and how we can address them.
|
at what those are and how we can address them.
|
||||||
|
|
||||||
## More to explore
|
TODO: apply feedback from @gribozavr when refactoring the RAII content:
|
||||||
|
|
||||||
To learn more about building synchronization primitives, consider reading
|
```
|
||||||
[_Rust Atomics and Locks_ by Mara Bos](https://marabos.nl/atomics/).
|
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.
|
||||||
|
|
||||||
The book demonstrates, among other topics, how `Drop` and RAII work together in
|
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.
|
||||||
constructs like `Mutex`.
|
|
||||||
|
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>
|
</details>
|
||||||
|
@ -3,6 +3,19 @@
|
|||||||
Use `Drop` to enforce invariants and detect incorrect API usage. A "drop bomb"
|
Use `Drop` to enforce invariants and detect incorrect API usage. A "drop bomb"
|
||||||
panics if not defused.
|
panics if not defused.
|
||||||
|
|
||||||
|
---
|
||||||
|
TODO: apply feedback
|
||||||
|
|
||||||
|
I think this slide should also mention that drop bombs are useful in cases
|
||||||
|
where the finalizing operation (e.g. commit and rollback in the slide's
|
||||||
|
example) needs to return some kind of output, which means it can't be
|
||||||
|
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
|
```rust
|
||||||
struct Transaction {
|
struct Transaction {
|
||||||
active: bool,
|
active: bool,
|
||||||
@ -27,7 +40,7 @@ impl Transaction {
|
|||||||
impl Drop for Transaction {
|
impl Drop for Transaction {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.active {
|
if self.active {
|
||||||
panic!("Transaction dropped without commit or roll back!");
|
panic!("Transaction dropped without commit or rollback!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,15 +56,69 @@ impl Drop for Transaction {
|
|||||||
either by committing it or rolling it back to undo any changes.
|
either by committing it or rolling it back to undo any changes.
|
||||||
|
|
||||||
- In the context of FFI, where cross-boundary references are involved, it is
|
- In the context of FFI, where cross-boundary references are involved, it is
|
||||||
often necessary to ensure that manually allocated memory from the guest
|
often necessary to ensure that manually allocated memory from the foreign
|
||||||
language is cleaned up through an explicit call to a safe API function.
|
language is cleaned up through an explicit call to a safe API function.
|
||||||
|
|
||||||
- Similar to unsafe code, it is recommended that APIs with expectations like
|
TODO: this is a bit far-fetched, better examples suggested by reviewers:
|
||||||
these are clearly documented under a Panic section. This helps ensure that
|
|
||||||
users of the API are aware of the consequences of misuse.
|
|
||||||
|
|
||||||
Ideally, drop bombs should be used only in internal APIs to catch bugs early,
|
```
|
||||||
without placing implicit runtime obligations on library users.
|
Let's imagine what an implementation of an SSH server could look like. An SshServer object
|
||||||
|
that manages all connections, and an SshConnection object that represents a single connected user.
|
||||||
|
The connection needs to be unregistered from the server object before it can be dropped -
|
||||||
|
or else we would be leaking resources like socket descriptors.
|
||||||
|
|
||||||
|
Say, for efficiency the connection does not have a pointer back to the server -
|
||||||
|
so the connection can't deregister itself within its drop.
|
||||||
|
Therefore, SshConnection::drop should panic to catch coding bugs.
|
||||||
|
Instead, we would provide an API like SshServer::deregister(&mut self, conn: SshConnection)
|
||||||
|
that consumes the connection, deregisters and correctly destroys it.
|
||||||
|
|
||||||
|
-- @randomPoison agrees, this can be dropped:
|
||||||
|
|
||||||
|
I don't think we need to mention FFI at all here, I think the decision
|
||||||
|
of whether or not to use a drop bomb has more to do with whether or not
|
||||||
|
there's a reasonable way to cleanup in Drop or if the finalizing
|
||||||
|
operation needs to return anything (see also my comment on the file).
|
||||||
|
That situation may come up in FFI, but it's not the FFI specifically
|
||||||
|
that makes a drop bomb necessary.
|
||||||
|
```
|
||||||
|
|
||||||
|
more feedback:
|
||||||
|
|
||||||
|
```
|
||||||
|
It's worth noting that a very common reason you can't drop the thing is that
|
||||||
|
the deallocation method is async and there's no async drop.
|
||||||
|
Database transactions are an excellent example of this, in fact --
|
||||||
|
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.
|
||||||
|
|
||||||
|
(^ 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:
|
||||||
|
|
||||||
|
```
|
||||||
|
Either edit the example directly (for it to appear with the comment from the beginning),
|
||||||
|
or add a suggested comment here for the instructor to type or paste.
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO: Also more feedback:
|
||||||
|
|
||||||
|
```
|
||||||
|
Why should this only be used in internal APIs? For example, a library
|
||||||
|
providing a transactional API (like in the example on this slide) would
|
||||||
|
want to use this pattern in its public API to help users catch cases
|
||||||
|
where they forget to finish the transaction.
|
||||||
|
```
|
||||||
|
|
||||||
- If there is a way to restore the system to a valid state using a fallback in
|
- If there is a way to restore the system to a valid state using a fallback in
|
||||||
the Drop implementation, it is advisable to restrict the use of drop bombs to
|
the Drop implementation, it is advisable to restrict the use of drop bombs to
|
||||||
@ -64,9 +131,19 @@ impl Drop for Transaction {
|
|||||||
This allows you to move out the resource in a controlled way, preventing
|
This allows you to move out the resource in a controlled way, preventing
|
||||||
accidental double cleanup or use-after-drop errors.
|
accidental double cleanup or use-after-drop errors.
|
||||||
|
|
||||||
|
TODO: apply feedback
|
||||||
|
|
||||||
|
```
|
||||||
|
I think we should provide an example of this.
|
||||||
|
I find that it's a common pattern when doing complex logic in Drop.
|
||||||
|
This might even be worth pulling out into its own slide.
|
||||||
|
```
|
||||||
|
|
||||||
- [`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html):
|
- [`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html):
|
||||||
A zero-cost wrapper that disables the automatic drop behavior of a value,
|
A zero-cost wrapper that disables the automatic drop behavior of a value,
|
||||||
making manual cleanup required and explicit.
|
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
|
- 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
|
a way to enforce that certain values are not dropped unless explicitly
|
||||||
@ -74,18 +151,14 @@ impl Drop for Transaction {
|
|||||||
method to make dropping safe. The crate also includes a `DebugDropBomb`
|
method to make dropping safe. The crate also includes a `DebugDropBomb`
|
||||||
variant for use in debug-only builds.
|
variant for use in debug-only builds.
|
||||||
|
|
||||||
## More to explore
|
TODO: apply feedback:
|
||||||
|
|
||||||
Rust does not currently support full linear types or typestate programming in
|
```
|
||||||
the core language. This means the compiler cannot guarantee that a resource was
|
I don't love the wording "it is advisable to restrict the use of drop
|
||||||
used exactly once or finalized before being dropped.
|
bombs to Debug mode". I think the decision of whether to panic in
|
||||||
|
release builds is heavily dependent on the specifics of the API in
|
||||||
Drop bombs serve as a runtime mechanism to enforce such usage invariants
|
question, and panicking in release builds is an entirely valid decision
|
||||||
manually. This is typically done in a Drop implementation that panics if a
|
imo.
|
||||||
required method, such as `.commit()`, was not called before the value went out
|
```
|
||||||
of scope.
|
|
||||||
|
|
||||||
There is an open RFC issue and discussion about linear types in Rust:
|
|
||||||
<https://github.com/rust-lang/rfcs/issues/814>.
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
@ -19,7 +19,7 @@ fn write_log() -> io::Result<()> {
|
|||||||
<details>
|
<details>
|
||||||
|
|
||||||
- In the earlier example, our `File` resource owns a file handle provided by the
|
- In the earlier example, our `File` resource owns a file handle provided by the
|
||||||
operating system.
|
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):
|
[As stated in the documentation](https://doc.rust-lang.org/std/fs/struct.File.html):
|
||||||
|
|
||||||
@ -44,9 +44,25 @@ fn write_log() -> io::Result<()> {
|
|||||||
automatically when a value is popped off the stack during unwinding, leaving
|
automatically when a value is popped off the stack during unwinding, leaving
|
||||||
no opportunity for error handling.
|
no opportunity for error handling.
|
||||||
|
|
||||||
|
TODO: apply feedback:
|
||||||
|
|
||||||
|
```
|
||||||
|
This last sentence suggests that there was no other design choice because of unwinding.
|
||||||
|
That's not true: in C++, for example, one can throw an exception from a destructor while uwinding
|
||||||
|
because of another exception. Throwing from a destructor is messy and error-prone
|
||||||
|
(and pretty much every style guide tells you not to do it),
|
||||||
|
however that is an existence proof that Rust's design choice here was not entirely forced.
|
||||||
|
It is a good pragmatic choice for sure, but not the only one possible.
|
||||||
|
|
||||||
|
I'd suggest to rewrite this sentence in a way that talks about infallibility
|
||||||
|
of drop as a pragmatic design choice to keep the complexity of error handling under control
|
||||||
|
(not as the only possible choice).
|
||||||
|
```
|
||||||
|
|
||||||
- One workaround is to panic inside `drop` when a failure occurs. However, this
|
- 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
|
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.
|
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
|
While panicking in `drop` can serve certain purposes (see
|
||||||
[the next chapter on "drop bombs"](./drop_bomb.md)), it should be used
|
[the next chapter on "drop bombs"](./drop_bomb.md)), it should be used
|
||||||
@ -57,6 +73,10 @@ fn write_log() -> io::Result<()> {
|
|||||||
dropped. And in fact as discussed in previous slide it might never even run at
|
dropped. And in fact as discussed in previous slide it might never even run at
|
||||||
all, leaving the external resource in an undefined state.
|
all, leaving the external resource in an undefined state.
|
||||||
|
|
||||||
|
(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)
|
||||||
|
|
||||||
This matters particularly for I/O: normally you might set a timeout on
|
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
|
blocking operations, but when I/O occurs in a `drop` implementation, you have
|
||||||
no way to enforce such constraints.
|
no way to enforce such constraints.
|
||||||
@ -66,11 +86,32 @@ fn write_log() -> io::Result<()> {
|
|||||||
indefinitely. Since the call to `drop` happens implicitly and outside your
|
indefinitely. Since the call to `drop` happens implicitly and outside your
|
||||||
control, there's no way to apply a timeout or fallback mechanism.
|
control, there's no way to apply a timeout or fallback mechanism.
|
||||||
|
|
||||||
|
TODO: apply feedback
|
||||||
|
|
||||||
|
```
|
||||||
|
I see what you mean. I'd suggest to first say that drop is special because it terminates
|
||||||
|
the object lifetime, so it is inherently a "one-shot" API.
|
||||||
|
That has consequences: things like caller-driven timeouts or retries
|
||||||
|
simply don't make sense - there's no object anymore after the first
|
||||||
|
call. (Emphasizing caller-driven is important)
|
||||||
|
|
||||||
|
This equally applies to all APIs that consume the object by value.
|
||||||
|
|
||||||
|
The fact that drop is usually called implicitly though is not important
|
||||||
|
here. For one, we can call it explicitly (std::mem::drop()); but if that
|
||||||
|
wasn't available, we could have wrapped the object with drop in an
|
||||||
|
Option, and then trigger drop by assigning None.
|
||||||
|
```
|
||||||
|
|
||||||
- For smart pointers and synchronization primitives, none of these drawbacks
|
- For smart pointers and synchronization primitives, none of these drawbacks
|
||||||
matter, since the operations are nearly instant and a program panic does not
|
matter, since the operations are nearly instant and a program panic does not
|
||||||
cause undefined behavior. The poisoned state disappears along with the
|
cause undefined behavior. The poisoned state disappears along with the
|
||||||
termination of the program.
|
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)
|
||||||
|
|
||||||
- For use cases such as I/O or FFI, it may be preferable to let the user clean
|
- 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.
|
up resources explicitly using a close function.
|
||||||
|
|
||||||
|
@ -21,12 +21,35 @@ fn main() {
|
|||||||
// Write something to the file
|
// Write something to the file
|
||||||
writeln!(file, "temporary data").unwrap();
|
writeln!(file, "temporary data").unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Apply Feedbak:
|
||||||
|
*
|
||||||
|
* Consider making the example a bit more realistic: set it up
|
||||||
|
* as if we are downloading a file through HTTP, and
|
||||||
|
* if the connection gets interrupted, we don't want to
|
||||||
|
* leave behind an incomplete file. I'm not asking to
|
||||||
|
* add/change code, only to update comments,
|
||||||
|
* and names of functions and variables.
|
||||||
|
*/
|
||||||
|
|
||||||
// Create a scope guard to clean up the file unless we defuse it
|
// Create a scope guard to clean up the file unless we defuse it
|
||||||
let cleanup = guard(path, |path| {
|
let cleanup = guard(path, |path| {
|
||||||
|
println!("Operation failed, deleting file: {:?}", path);
|
||||||
// Errors must be handled inside the guard,
|
// Errors must be handled inside the guard,
|
||||||
// but cannot be propagated.
|
// but cannot be propagated.
|
||||||
let _ = fs::remove_file(path);
|
let _ = fs::remove_file(path);
|
||||||
});
|
});
|
||||||
|
/*
|
||||||
|
* TODO: apply feedback
|
||||||
|
*
|
||||||
|
* I think this should be put right after let mut file, in order to:
|
||||||
|
*
|
||||||
|
* + emphasize that file and cleanup go together,
|
||||||
|
*
|
||||||
|
* + to ensure that file gets deleted even if we
|
||||||
|
* have a problem in writeln!().unwrap().
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
if conditional_success() {
|
if conditional_success() {
|
||||||
// Success path: we want to keep the file
|
// Success path: we want to keep the file
|
||||||
@ -103,6 +126,27 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
TODO apply feedback:
|
||||||
|
|
||||||
|
```
|
||||||
|
For large chunks of code in the speaker notes like this, it'd be nicer
|
||||||
|
to put this in the playground and include the playground link in the
|
||||||
|
speaker notes instead. That makes it easier to pull up the example on
|
||||||
|
screen, instead of having to copy-paste it into a playground window
|
||||||
|
manually.
|
||||||
|
|
||||||
|
That said, I'm not sure this is the example we want to use. This
|
||||||
|
re-implements the scopeguard crate's functionality, i.e. it's a general
|
||||||
|
purpose scope guard that calls a closure. If you're implementing a scope
|
||||||
|
guard manually, you're more likely to be implementing a guard that does
|
||||||
|
something specific since scopeguard already exists for doing general
|
||||||
|
purpose scope guards.
|
||||||
|
|
||||||
|
Maybe rework this example to use the "delete a file on failure" example
|
||||||
|
that's in the slide? I can slap together a sketch in the playground if
|
||||||
|
it's not clear what I'm suggesting.
|
||||||
|
```
|
||||||
|
|
||||||
- The `ScopeGuard` type in the `scopeguard` crate also includes a `Debug`
|
- The `ScopeGuard` type in the `scopeguard` crate also includes a `Debug`
|
||||||
implementation and a third parameter: a
|
implementation and a third parameter: a
|
||||||
[`Strategy`](https://docs.rs/scopeguard/latest/scopeguard/trait.Strategy.html)
|
[`Strategy`](https://docs.rs/scopeguard/latest/scopeguard/trait.Strategy.html)
|
||||||
@ -115,11 +159,15 @@ fn main() {
|
|||||||
You can also implement your own `Strategy` trait to define custom
|
You can also implement your own `Strategy` trait to define custom
|
||||||
conditions for when the cleanup should occur.
|
conditions for when the cleanup should occur.
|
||||||
|
|
||||||
- Remark also that the crates' `ScopeGuard` makes use of
|
TODO: again... more concise, e.g. reduce the above to:
|
||||||
[`ManuallyDrop`](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html)
|
|
||||||
instead of `Option` to avoid automatic or premature dropping of values,
|
```
|
||||||
giving precise manual control and preventing double-drops. This avoids the
|
- `scopeguard` also supports selecting a
|
||||||
runtime overhead and semantic ambiguity that comes with using Option.
|
[`Strategy`](https://docs.rs/scopeguard/latest/scopeguard/trait.Strategy.html)
|
||||||
|
to determine when the cleanup logic should run, i.e. always,
|
||||||
|
only on successful exit, or only on unwind. The crate
|
||||||
|
also supports defining custom strategies.
|
||||||
|
```
|
||||||
|
|
||||||
- Recalling the transaction example from
|
- Recalling the transaction example from
|
||||||
[the drop bombs chapter](./drop_bomb.md), we can now combine both concepts:
|
[the drop bombs chapter](./drop_bomb.md), we can now combine both concepts:
|
||||||
@ -131,4 +179,12 @@ fn main() {
|
|||||||
drop logic, this pattern at least allows us to orchestrate fallbacks
|
drop logic, this pattern at least allows us to orchestrate fallbacks
|
||||||
explicitly and with whatever guarantees or limits we require.
|
explicitly and with whatever guarantees or limits we require.
|
||||||
|
|
||||||
|
TODO: apply feedback for the above paragraph:
|
||||||
|
|
||||||
|
```
|
||||||
|
Maybe move this point to the top, since it most directly explains what
|
||||||
|
this example is doing and why. You may want to reword/reorganize this to
|
||||||
|
merge it with the current first bullet point.
|
||||||
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
@ -32,6 +32,14 @@ decisions within the context and constraints of your own projects.
|
|||||||
The course will cover the topics listed below. Each topic may be covered in one
|
The course will cover the topics listed below. Each topic may be covered in one
|
||||||
or more slides, depending on its complexity and relevance.
|
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.
|
||||||
|
|
||||||
### Foundations of API design
|
### Foundations of API design
|
||||||
|
|
||||||
- Golden rule: prioritize clarity and readability at the callsite. People will
|
- Golden rule: prioritize clarity and readability at the callsite. People will
|
||||||
|
Reference in New Issue
Block a user