You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-08-08 08:22:52 +02:00
improve RAII intro segment further based on Luca's feedbacl
This commit is contained in:
@ -4,120 +4,110 @@ minutes: 30
|
|||||||
|
|
||||||
# RAII: `Drop` trait
|
# RAII: `Drop` trait
|
||||||
|
|
||||||
RAII (Resource Acquisition Is Initialization) means tying the lifetime of a
|
RAII (**R**esource **A**cquisition **I**s **I**nitialization) ties the lifetime
|
||||||
resource to the lifetime of a value.
|
of a resource to the lifetime of a value.
|
||||||
|
|
||||||
[Rust uses RAII to manage memory](https://doc.rust-lang.org/rust-by-example/scope/raii.html),
|
[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
|
and the `Drop` trait allows you to extend this to other resources, such as file
|
||||||
descriptors or locks.
|
descriptors or locks.
|
||||||
|
|
||||||
```rust,editable
|
```rust,editable
|
||||||
struct FileLock;
|
pub struct File(std::os::fd::RawFd);
|
||||||
pub struct File {
|
|
||||||
stub: Option<u8>,
|
|
||||||
lock: FileLock,
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error;
|
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
pub fn open(path: &str) -> Result<Self, Error> {
|
pub fn open(path: &str) -> Result<Self, std::io::Error> {
|
||||||
println!("acquire file descriptor: {path}");
|
// [...]
|
||||||
Ok(Self { stub: Some(1), lock: FileLock })
|
Ok(Self(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&mut self) -> Result<u8, Error> {
|
pub fn read_to_end(&mut self) -> Result<Vec<u8>, std::io::Error> {
|
||||||
self.stub.take().ok_or(Error)
|
// [...]
|
||||||
|
Ok(b"example".to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(self) -> Result<(), Error> {
|
pub fn close(self) -> Result<(), std::io::Error> {
|
||||||
self.lock.release()
|
// [...]
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileLock {
|
|
||||||
fn release(self) -> Result<(), Error> {
|
|
||||||
println!("release file descriptor");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), std::io::Error> {
|
||||||
let mut file = File::open("example.txt").unwrap();
|
let mut file = File::open("example.txt")?;
|
||||||
|
println!("content: {:?}", file.read_to_end()?);
|
||||||
let mut content = Vec::new();
|
Ok(())
|
||||||
while let Ok(byte) = file.read() {
|
|
||||||
content.push(byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("content: {content:?}");
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
- This 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. The code as written does not call `file.close()`. Did
|
||||||
Did anyone notice that `file.close()` is missing?
|
anyone in the class notice?
|
||||||
|
|
||||||
- Try inserting `file.close().unwrap();` at the end of `main`. Then try moving
|
- To release the file descriptor correctly, `file.close()` must be called after
|
||||||
it before the loop. Rust will reject this: once `file` is moved, it can no
|
the last use — and also in early-return paths in case of errors.
|
||||||
longer be accessed. The borrow checker enforces this statically.
|
|
||||||
|
|
||||||
- Instead of relying on the user to remember to call `close()`, we can implement
|
- Instead of relying on the user to call `close()`, we can implement the `Drop`
|
||||||
the `Drop` trait to release the resource automatically. This ties cleanup to
|
trait to release the resource automatically. This ties cleanup to the lifetime
|
||||||
the lifetime of the `File` value. Note that `Drop` cannot return errors, so
|
of the `File` value.
|
||||||
any fallible logic must be handled internally or avoided.
|
|
||||||
|
|
||||||
```rust,compile_fail
|
```rust,compile_fail
|
||||||
impl Drop for FileLock {
|
impl Drop for File {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
println!("release file descriptor automatically");
|
println!("release file descriptor automatically");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- If both `drop()` and `close()` are present, the file descriptor is released
|
- Note that `Drop::drop` cannot return errors. Any fallible logic must be
|
||||||
|
handled internally or ignored. In the standard library, errors returned while
|
||||||
|
closing an owned file descriptor during `Drop` are silently discarded:
|
||||||
|
<https://doc.rust-lang.org/src/std/os/fd/owned.rs.html#169-196>
|
||||||
|
|
||||||
|
- If both `drop()` and `close()` exist, the file descriptor may be released
|
||||||
twice. To avoid this, remove `close()` and rely solely on `Drop`.
|
twice. To avoid this, remove `close()` and rely solely on `Drop`.
|
||||||
|
|
||||||
This also illustrates that when a parent type is dropped, the `drop()` method
|
- When is `Drop::drop` called?
|
||||||
of its fields (such as `FileLock`) is automatically called — no extra code is
|
|
||||||
needed.
|
|
||||||
|
|
||||||
- Demonstrate ownership transfer by moving the file into a `read_all()`
|
Normally, when the `file` variable in `main` goes out of scope (either on
|
||||||
function. The file is dropped when the local variable inside that function
|
return or due to a panic), `drop()` is called automatically.
|
||||||
goes out of scope, not in `main`.
|
|
||||||
|
|
||||||
This differs from C++, where destructors are tied to the original scope, even
|
If the file is moved into another function, for example `read_all()`, the
|
||||||
for moved-from values.
|
value is dropped when that function returns — not in `main`.
|
||||||
|
|
||||||
The same mechanism underlies `std::mem::drop`, which lets you drop a value
|
In contrast, C++ runs destructors in the original scope even for moved-from
|
||||||
early:
|
values.
|
||||||
|
|
||||||
|
- The same mechanism powers `std::mem::drop`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub fn drop<T>(_x: T) {}
|
pub fn drop<T>(_x: T) {}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Insert `panic!("oops")` at the start of `read_all()` to show that `drop()` is
|
You can use it to force early destruction of a value before its natural end of
|
||||||
still called during unwinding. Rust ensures this unless the panic strategy is
|
scope.
|
||||||
|
|
||||||
|
- Insert `panic!("oops")` at the start of `read_to_end()` to show that `drop()`
|
||||||
|
still runs during unwinding. Rust guarantees this unless the panic strategy is
|
||||||
set to `abort`.
|
set to `abort`.
|
||||||
|
|
||||||
- There are exceptions where destructors will not run:
|
- There are cases where destructors will not run:
|
||||||
- If a destructor panics during unwinding, the program aborts immediately.
|
- If a destructor itself panics during unwinding, the program aborts
|
||||||
- The program also aborts when using `std::process::exit()` or when compiled
|
immediately.
|
||||||
with the `abort` panic strategy.
|
- If the program exits with `std::process::exit()` or is compiled with the
|
||||||
|
`abort` panic strategy, destructors are skipped.
|
||||||
|
|
||||||
### 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`.
|
||||||
|
|
||||||
You cannot `await` inside a destructor, which is often needed when cleaning up
|
This means you cannot `await` inside a destructor, which is often needed when
|
||||||
asynchronous resources like sockets, database connections, or tasks that must
|
cleaning up asynchronous resources like sockets, database connections, or tasks
|
||||||
signal completion to another system.
|
that must 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>
|
||||||
- Available on nightly:
|
- There is an experimental `AsyncDrop` trait available on nightly:
|
||||||
<https://doc.rust-lang.org/nightly/std/future/trait.AsyncDrop.html>
|
<https://doc.rust-lang.org/nightly/std/future/trait.AsyncDrop.html>
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
Reference in New Issue
Block a user