Skip to content

建立 2026-09-19 更新 2026-09-19

借用與參照

借用(borrowing)讓你不必取走所有權,就能使用別人擁有的值。做法是建立參照(reference):&T 是唯讀參照,&mut T 是可修改的參照。本頁說明它們的規則,以及切片(slice)這種特殊的參照。

唯讀參照 &T

在值前面加 & 建立參照,函式簽名的型別也寫成 &String。參照只是「看一眼」,不會取走所有權,用完原本的變數仍然可用:

fn calculate_length(s: &String) -> usize {
    s.len()
} // s 只是參照,離開作用域時不會釋放它指向的字串

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("{s1} 的長度是 {len}");
}

可修改參照 &mut T

要透過參照修改資料,原變數必須是 mut,且要傳 &mut

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

fn main() {
    let mut s = String::from("hello");
    add_world(&mut s);
    println!("{s}");
}

借用規則

在任何時間點,你只能擁有以下其中一種:

  • 任意多個唯讀參照(&T),或
  • 恰好一個可修改參照(&mut T)。

而且參照必須永遠指向有效的資料。

這相當於「讀寫鎖」:很多人可以同時讀,但要寫的時候只能有一個人,且不能有人同時在讀。編譯器在編譯時就檢查這件事,因此不會發生資料競爭。

fn main() {
    // ❌ 無法編譯:同時存在兩個可修改參照
    let mut s = String::from("hello");
    let r1 = &mut s;
    let r2 = &mut s;
    println!("{r1}, {r2}");
}
fn main() {
    // ❌ 無法編譯:有唯讀參照時不能再建立可修改參照
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &mut s;
    println!("{r1}, {r2}");
}

參照的作用範圍到「最後一次使用」為止

參照從建立開始,到最後一次被使用就結束(稱為 NLL, non-lexical lifetimes),不是等到大括號結束。所以下面的寫法是合法的:

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 已經結束,可以建立可修改參照
    r3.push_str("!");
    println!("{r3}");
}

懸空參照

Rust 保證參照不會指向已被釋放的資料。下面這個函式試圖回傳區域變數的參照,編譯器會拒絕:

// ❌ 無法編譯:回傳指向區域變數的參照
fn dangle() -> &String {
    let s = String::from("hello");
    &s
} // s 在這裡被丟棄,參照就會指向無效記憶體

fn main() {
    let _r = dangle();
}

解法是直接回傳擁有的值,把所有權交給呼叫者:

fn no_dangle() -> String {
    String::from("hello")
}

fn main() {
    let s = no_dangle();
    println!("{s}");
}

編譯器這裡會提示需要「生命週期標註」,這是下一頁的主題。

切片(slice)

切片是指向一段連續資料的參照,不擁有資料。字串切片型別是 &str,陣列與 Vec 的切片型別是 &[T]

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

    let nums = [1, 2, 3, 4, 5];
    let part = &nums[1..4];
    println!("{part:?}");
}

字串字面值(如 "hello")的型別本身就是 &str,指向程式執行檔中的資料。

函式參數優先使用 &str 與 &[T]

fn f(s: &str)fn f(s: &String) 更通用:同一個函式可以接收 &String 也可以接收字串字面值。同理,&[T] 可以接收陣列與 Vec

fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

fn main() {
    let owned = String::from("hello world");
    println!("{}", first_word(&owned));
    println!("{}", first_word("rust lang"));
}

該傳什麼?

我想要 參數型別 呼叫端
只讀取 &T f(&x)
修改但不取走 &mut T f(&mut x)
取走所有權(例如要存起來) T f(x)

推薦影音

借用

簡述:微軟《Beginner's Series to Rust》第 27 集,說明參照與借用的規則,可對照本頁的「借用規則」。

下一頁:生命週期