Texas0295 @Texas0295[RUST] 所有權簡介總結用,已盡力保證正確性

堆疊(Stack) 與堆積(Heap)

相同點:都是程式碼執行時可使用的記憶體部分

堆疊特有:

  • 遵循後入先出(LIFO)原則
  • 資料必須是已知固定大小
  • 相對堆積而言更快的速度

堆積特有:

  • 在資料實際存入前需要預先配置(allocating) 一個足夠大小的空間
  • 存取要經過指針(pointer) ,配置行為本身也會返回一個指針
  • 指針有固定已知的大小,因而可以存放在堆疊上

所有權規則

  • Rust 中每個數值都有一個擁有者(owner)
  • 同時間只能擁有一個擁有者
  • 擁有者離開作用域時,數值會被丟棄

記憶體與配置

對於未知大小的資料,需要記憶體配置器在堆積上配置一塊編譯時大小未知的記憶體來存放它

這代表:

  • 記憶體配置器必須在執行時請求記憶體
  • 今後不再需要這個資料時,請求的記憶體需要歸還給配置器

呼叫者需要關注第二部分的處理

如何釋放不再需要的資料

Java採用垃圾回收機制保證及時檢測不再使用的記憶體,但執行的時機不取決於開發者,造成垃圾回收的不確定性

C將記憶體釋放的操作下放給開發者,有利於效能。同時意味著若操作不當會增加安全風險

Rust採用所有權系統管理記憶體,保證高效的同時也不會造成由不正操作導致的安全風險

Rust會在變數離開其作用域時呼叫drop方法來釋放記憶體 (RAII,資源取得即初始化)

下列程式可視為RAII在rust中應用的一個典型例子:

struct Resource {
    name: String,
}

impl Resource {
    fn new(name: &str) -> Resource {
        println!("Acquiring resource: {}", name);
        Resource {
            name: name.to_string(),
        }
    }
}

impl Drop for Resource {
    fn drop(&mut self) {
        println!("Releasing resource: {}", self.name);
    }
}

fn main() {
    {
        let r = Resource::new("File Handle");
        // Resource r 被使用
    } // 作用域結束,r 被自動釋放,並調用了 drop()
    println!("Out of scope, resource has been cleaned up.");
}

變數同資料的互動方式

移動(Move)

考慮如下程式,其中String型別的實作是一個堆積資料

let s1 = String::from("hello");
let s2 = s1;

如上圖,當let s2 = s1; 執行時,rust 會在淺拷貝之上無效化第一個變數,其被稱為移動(move)

無效化第一個變數是為了防止雙重釋放

克隆(Clone)

執行深拷貝,效果如下圖

拷貝(Copy)

拷貝僅對在堆疊上的資料有效,其會複製一份新值給目標變數

rust使用Cpoy特徵來標記這些只存在堆疊上的型別

drop特徵與Copy特徵互斥

所有權與函式

如同變數一樣,所有權也可以進出函式,在函式間轉移

下列程式展示了變數的所有權如何進入函式:

fn main() {
    let s = String::from("hello");  // s 進入作用域

    takes_ownership(s);             // s 的值進入函式
                                    // 所以 s 也在此無效

    let x = 5;                      // x 進入作用域

    makes_copy(x);                  // x 本該移動進函式裡
                                    // 但 i32 有 Copy,所以 x 可繼續使用

} // x 在此離開作用域,接著是 s。但因為 s 的值已經被移動了
  // 它不會有任何動作

fn takes_ownership(some_string: String) { // some_string 進入作用域
    println!("{}", some_string);
} // some_string 在此離開作用域並呼叫 `drop`
  // 佔用的記憶體被釋放

fn makes_copy(some_integer: i32) { // some_integer 進入作用域
    println!("{}", some_integer);
} // some_integer 在此離開作用域,沒有任何動作發生

以下程式展示了函式如何回傳所有權:

fn main() {
    let s1 = gives_ownership();         // gives_ownership 移動它的回傳值給 s1

    let s2 = String::from("哈囉");     // s2 進入作用域

    let s3 = takes_and_gives_back(s2);  // s2 移入 takes_and_gives_back
                                        // 該函式又將其回傳值移到 s3
} // s3 在此離開作用域並釋放
  // s2 已被移走,所以沒有任何動作發生
  // s1 離開作用域並釋放

fn gives_ownership() -> String {             // gives_ownership 會將他的回傳值
                                             // 移動給呼叫它的函式

    let some_string = String::from("你的字串"); // some_string 進入作用域

    some_string                              // 回傳 some_string 並移動給
                                             // 呼叫它的函式
}

// 此函式會取得一個 String 然後回傳它
fn takes_and_gives_back(a_string: String) -> String { // a_string 進入作用域

    a_string  // 回傳 a_string 並移動給呼叫的函式
}

未被指派

考慮以下程式

fn main() {
    let s1 = String::from("hello");
    ret(s1);
}

fn ret(x: String) -> String {
    x
}

在沒有指派變數給ret(s1)回傳的資料所有權時,rust會針對這個呼叫返回的資料建立一個臨時變數(Temporary value) ,該變數的生命週期會持續到該行結束,相當於此資料會被立即釋放

2 replies, 2 reactionsAn article posted on 3/22/2025, 6:41:23 AM
Solar Network Post Web PreviewTo get full view of this post, open it on Solian
Replies
All replies of this post