CRust 學習筆記:丟棄檢查 -Drop Check--1
本系列文章是 Jon Gjengset 發佈的 CRust of Rust 系列視頻的學習筆記,CRust of Rust 是一系列持續更新的 Rust 中級教程。
在這篇文章中,我們講解丟棄檢查。在程序中我們定義的變量都有其作用域,當變量超出作用域時,就會被丟棄。多個變量則按其定義的相反順序丟棄,結構體和元組中的字段按其定義的順序丟棄。
正常情況下,編譯器的借用檢查器會做相應的檢查。那我們爲什麼還需要研究丟棄檢查,是因爲如果類型系統不小心,它可能會意外地產生懸垂指針。
下面我們通過一些實際的例子來深入學習丟棄檢查,新建一個項目:
cargo new boks
在 main.rs 中寫入如下代碼:
pub struct Boks<T> {
p: *mut T,
}
impl<T> Boks<T> {
pub fn ny(t: T) -> Self {
Boks {
p: Box::into_raw(Box::new(t)),
}
}
}
fn main() {
let x = 42;
let b = Boks::ny(x);
}
Boks 的成員 p 是一個可變原生指針,在 ny(t: T) 函數中通過 Box::into_raw() 獲取了 t 的原生指針。
在 main 函數中,當變量 b 超出作用域後,並不會自動釋放原生指針指向的內存,會產生內存泄漏。因此需要我們手動去實現 Drop trait,來釋放內存。
impl<T> Drop for Boks<T> {
fn drop(&mut self) {
// unsafe { std::ptr::drop_in_place(self.p) };
unsafe { Box::from_raw(self.p) };
}
}
要想使用 p,我們還需要實現解引用的兩個 trait:Deref 和 DerefMut。
impl<T> std::ops::Deref for Boks<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.p }
}
}
impl<T> std::ops::DerefMut for Boks<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.p }
}
}
我們測試一下:
fn main() {
let x = 42;
let b = Boks::ny(x);
println!("{:?}", *b);
}
結果打印出 42。
現在我們有了可以工作的基礎代碼,下一步我們繼續研究丟棄檢查。看如下代碼:
fn main() {
......
let mut y = 42;
let b = Boks::ny(&mut y);
println!("{y}");
}
運行 cargo check:
error[E0502]: cannot borrow `y` as immutable because it is also borrowed as mutable
--> src/main.rs:44:16
|
42 | let b = Boks::ny(&mut y);
| ------ mutable borrow occurs here
43 |
44 | println!("{y}");
| ^ immutable borrow occurs here
45 | }
| - mutable borrow might be used here, when `b` is dropped and runs the `Drop` code for type `Boks`
我們看最後一行的錯誤信息,提示當 b 超出作用域時執行 Drop 特徵的 drop 方法時,編譯器會認爲可能會在 drop 方法中有可變借用的情況,與上一行的不可變借用衝突。這就是編譯器的丟棄檢查 (Drop Check)。
由於 drop 方法是我們自己寫的,難免會在 drop 方法中訪問內部變量的可變引用,如:
impl<T> Drop for Boks<T> {
fn drop(&mut self) {
let _ = unsafe { std::ptr::read(self.p as *const u8) };
// unsafe { std::ptr::drop_in_place(self.p) };
unsafe { Box::from_raw(self.p) };
}
}
但是編譯器不知道,所以編譯器會進行丟棄檢查,即無論何種情況,都假設認爲訪問了內部變量的可變引用。
如果換成標準庫的 Box 就不會報錯:
fn main() {
......
let mut y = 42;
// let b = Boks::ny(&mut y);
let b = Box::new(&mut y);
println!("{y}");
}
執行 cargo run,結果爲 42。
這是因爲編譯器知道標準庫的 Box 超出作用域執行 drop 方法時,會釋放潛在的內部變量內存,不會訪問內部變量的可變引用。
如果不實現 Drop 特徵的 drop 方法,編譯器就不會報錯,但是會造成內存泄漏。在下一篇文章中我們將解決這個問題。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/vT3fczBF6G8G0nmsEqbqPg