借用與參照
借用(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。
該傳什麼?
| 我想要 | 參數型別 | 呼叫端 |
|---|---|---|
| 只讀取 | &T |
f(&x) |
| 修改但不取走 | &mut T |
f(&mut x) |
| 取走所有權(例如要存起來) | T |
f(x) |
推薦影音
借用
簡述:微軟《Beginner's Series to Rust》第 27 集,說明參照與借用的規則,可對照本頁的「借用規則」。
下一頁:生命週期。