借用(Borrowing)與參考#

上一章介紹了所有權的移動機制。若每次傳入函數都要移動所有權,程式碼會變得非常繁瑣。 借用(Borrowing) 透過 參考(Reference) 讓我們在不取得所有權的情況下,使用某個值。


1. 參考(Reference)#

參考使用 & 符號建立,讓你「借用」某個值,而不取走它的所有權:

1
2
3
4
5
6
7
8
9
fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s); // 借用 s
    println!("{s} 的長度是 {len}"); // s 仍有效
}

fn calculate_length(s: &String) -> usize {
    s.len()
} // s 在此離開作用域,但因為它沒有所有權,所以不會釋放資料

參考預設也是 不可變 的。


2. 可變參考(Mutable Reference)#

若需要透過參考修改資料,使用 &mut

1
2
3
4
5
6
7
8
9
fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{s}"); // hello, world
}

fn change(s: &mut String) {
    s.push_str(", world");
}

3. 可變參考的限制#

同一時間,對同一個值只能有一個可變參考:

1
2
3
4
5
6
fn main() {
    let mut s = String::from("hello");
    let r1 = &mut s;
    // let r2 = &mut s; // 錯誤!不能同時有兩個可變參考
    println!("{r1}");
}

這個限制可以在編譯時期防止 資料競爭(data race)


4. 不可變與可變參考不能並存#

當有不可變參考存在時,不能同時建立可變參考:

1
2
3
4
5
6
7
fn main() {
    let mut s = String::from("hello");
    let r1 = &s;     // 不可變參考
    let r2 = &s;     // 可以有多個不可變參考
    // let r3 = &mut s; // 錯誤!已有不可變參考
    println!("{r1}, {r2}");
}

Rust 的 非詞彙作用域生命週期(NLL) 讓編譯器追蹤參考最後一次使用的位置,不再使用後就允許新參考:

1
2
3
4
5
6
7
8
9
fn main() {
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &s;
    println!("{r1}, {r2}"); // r1、r2 最後使用在此

    let r3 = &mut s; // 此處 r1、r2 已結束,可以建立可變參考
    println!("{r3}");
}

5. 懸空參考(Dangling Reference)#

Rust 編譯器保證參考 永遠不會懸空 (指向已釋放的記憶體):

1
2
3
4
fn dangle() -> &String { // 錯誤!回傳懸空參考
    let s = String::from("hello");
    &s
} // s 在此被釋放,&s 指向無效記憶體

正確做法是回傳值本身(轉移所有權):

1
2
3
4
fn no_dangle() -> String {
    let s = String::from("hello");
    s // 所有權移出函數,記憶體不會被釋放
}

6. 字串切片(String Slice)#

切片(Slice)是對集合某一部分的參考,型別為 &str

1
2
3
4
5
6
fn main() {
    let s = String::from("hello world");
    let hello = &s[0..5];  // "hello"
    let world = &s[6..11]; // "world"
    println!("{hello}, {world}");
}

字串字面值 "hello" 的型別就是 &str,是指向程式二進位檔中的切片,因此字面值永遠是不可變的。

函數接受字串參數時,慣用 &str 以同時接受 String&str

1
2
3
4
5
6
7
8
9
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

Reference#

https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html