You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-08-08 00:12:51 +02:00
further improvements to RAII intro chapter
This commit is contained in:
@ -13,24 +13,24 @@ descriptors or locks.
|
|||||||
|
|
||||||
```rust,editable
|
```rust,editable
|
||||||
struct FileLock;
|
struct FileLock;
|
||||||
struct File {
|
pub struct File {
|
||||||
stub: Option<u8>,
|
stub: Option<u8>,
|
||||||
lock: FileLock,
|
lock: FileLock,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Error;
|
pub struct Error;
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
fn open(path: &str) -> Result<Self, Error> {
|
pub fn open(path: &str) -> Result<Self, Error> {
|
||||||
println!("acquire file descriptor: {path}");
|
println!("acquire file descriptor: {path}");
|
||||||
Ok(Self { stub: Some(1), lock: FileLock })
|
Ok(Self { stub: Some(1), lock: FileLock })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&mut self) -> Result<u8, Error> {
|
pub fn read(&mut self) -> Result<u8, Error> {
|
||||||
self.stub.take().ok_or(Error)
|
self.stub.take().ok_or(Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(self) -> Result<(), Error> {
|
pub fn close(self) -> Result<(), Error> {
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,20 +56,18 @@ fn main() {
|
|||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
- The example shows how easy it is to forget releasing a file descriptor when
|
- This 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.
|
managing it manually. In fact, the current code does not release it at all.
|
||||||
Did anyone notice that `file.close()` is missing?
|
Did anyone notice that `file.close()` is missing?
|
||||||
|
|
||||||
- Try inserting `file.close().unwrap();` at the end of `main`. Then try moving
|
- 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
|
it before the loop. Rust will reject this: once `file` is moved, it can no
|
||||||
longer be used. The borrow checker ensures we cannot access a `File` after it
|
longer be accessed. The borrow checker enforces this statically.
|
||||||
has been closed.
|
|
||||||
|
|
||||||
- Instead of relying on the programmer to remember to call `close()`, we can
|
- Instead of relying on the user to remember to call `close()`, we can implement
|
||||||
implement the `Drop` trait to handle cleanup automatically. This ties the
|
the `Drop` trait to release the resource automatically. This ties cleanup to
|
||||||
resource to the lifetime of the `File` value. But note: `Drop` cannot return
|
the lifetime of the `File` value. Note that `Drop` cannot return errors, so
|
||||||
errors. Anything fallible must be handled inside the `drop()` method or
|
any fallible logic must be handled internally or avoided.
|
||||||
avoided altogether.
|
|
||||||
|
|
||||||
```rust,compile_fail
|
```rust,compile_fail
|
||||||
impl Drop for FileLock {
|
impl Drop for FileLock {
|
||||||
@ -79,30 +77,43 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- If we keep both `drop()` and `close()`, the file descriptor is released twice.
|
- If both `drop()` and `close()` are present, the file descriptor is released
|
||||||
To avoid this, remove `close()` and rely on `Drop` alone.
|
twice. To avoid this, remove `close()` and rely solely on `Drop`.
|
||||||
|
|
||||||
- Demonstrate ownership transfer by moving the file into a separate `read_all()`
|
This also illustrates that when a parent type is dropped, the `drop()` method
|
||||||
function. The file is dropped when that local variable goes out of scope — not
|
of its fields (such as `FileLock`) is automatically called — no extra code is
|
||||||
in `main`. This contrasts with C++, where the original scope always runs the
|
needed.
|
||||||
destructor, even after moves.
|
|
||||||
|
|
||||||
- Add `panic!("oops")` at the start of `read_all()` to illustrate that `drop()`
|
- Demonstrate ownership transfer by moving the file into a `read_all()`
|
||||||
still runs during unwinding. Rust guarantees that destructors run during a
|
function. The file is dropped when the local variable inside that function
|
||||||
panic unless the panic strategy is set to abort.
|
goes out of scope, not in `main`.
|
||||||
|
|
||||||
|
This differs from C++, where destructors are tied to the original scope, even
|
||||||
|
for moved-from values.
|
||||||
|
|
||||||
|
The same mechanism underlies `std::mem::drop`, which lets you drop a value
|
||||||
|
early:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn drop<T>(_x: T) {}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Insert `panic!("oops")` at the start of `read_all()` to show that `drop()` is
|
||||||
|
still called during unwinding. Rust ensures this unless the panic strategy is
|
||||||
|
set to `abort`.
|
||||||
|
|
||||||
- There are exceptions where destructors will not run:
|
- There are exceptions where destructors will not run:
|
||||||
- If a destructor panics during unwinding, the program aborts immediately.
|
- If a destructor panics during unwinding, the program aborts immediately.
|
||||||
- The program also aborts immediately when using `std::process::exit()` or
|
- The program also aborts when using `std::process::exit()` or when compiled
|
||||||
when the panic strategy is set to `abort`.
|
with the `abort` panic strategy.
|
||||||
|
|
||||||
### More to Explore
|
### More to Explore
|
||||||
|
|
||||||
The `Drop` trait has another important limitation: it is not `async`.
|
The `Drop` trait has another important limitation: it is not `async`.
|
||||||
|
|
||||||
This means you cannot `await` inside a destructor, which is often needed when
|
You cannot `await` inside a destructor, which is often needed when cleaning up
|
||||||
cleaning up asynchronous resources like sockets, database connections, or tasks
|
asynchronous resources like sockets, database connections, or tasks that must
|
||||||
that must notify another system before shutdown.
|
signal completion to another system.
|
||||||
|
|
||||||
- Learn more:
|
- Learn more:
|
||||||
<https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html>
|
<https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html>
|
||||||
|
Reference in New Issue
Block a user