在前面幾篇中,我們深入理解了 Rust 的所有權系統:移動 (Move)、借用 (Borrow)、Copy 和 Clone。
今天把這些概念應用到實際的 API 設計中,看看如何寫出既安全又優雅的函式。
理解了所有權系統後,我們可以設計出更優雅的 API。函式簽名是技術規格,更是設計意圖的體現。
// 糟糕的設計:只是想計算長度,卻奪走了 String 的所有權
fn calculate_length(s: String) -> usize {
s.len()
}
fn main() {
let my_data = String::from("hello");
let length = calculate_length(my_data); // my_data 在此被移動
println!("長度是: {}", length);
// println!("{}", my_data); // ❌ 編譯錯誤!my_data 已失效
}
// 好的設計:函式簽名明確表達「我只讀取資料,不會拿走它」
fn calculate_length(s: &str) -> usize {
s.len()
}
fn main() {
let my_data = String::from("hello");
let length = calculate_length(&my_data); // 傳遞借用
println!("原始資料: {}", my_data); // ✅ 仍然有效
println!("長度是: {}", length);
}
為什麼 &str
比 &String
更好?
因為 &str
是一種字串切片 (string slice),它更加通用:
fn process_text(text: &str) {
println!("處理: {}", text);
}
fn main() {
let owned_string = String::from("hello");
let string_literal = "world";
let string_slice = &owned_string[0..3];
// 一個函式,三種用法
process_text(&owned_string); // 借用 String
process_text(string_literal); // 字串字面值
process_text(string_slice); // 字串切片
}
技術補充:為什麼
&String
能傳給需要&str
的函式?簡單來說,
String
類型有一個「自動轉換」的能力。當你有一個&String
,但函式需要&str
時,Rust 編譯器會自動幫你轉換。這背後的原理是
String
實現了一個特殊的 trait,Deref
。String
為了能被當作&str
使用,實現了Deref<Target=str>
。
告訴編譯器:「如果你需要&str
,但手上只有&String
,你可以把我當作&str
來使用。」這樣我們的 API 就能同時接受字串字面值 ("hello"
) 和String
的借用 (&my_string
),非常方便!
// 好的設計:只讀取資料
fn get_length(s: &str) -> usize {
s.len()
}
fn is_empty(s: &str) -> bool {
s.is_empty()
}
fn contains(s: &str, pattern: &str) -> bool {
s.contains(pattern)
}
// 好的設計:修改現有資料
fn append_text(s: &mut String, text: &str) {
s.push_str(text);
}
fn clear_string(s: &mut String) {
s.clear();
}
fn reverse_string(s: &mut String) {
*s = s.chars().rev().collect();
}
// 好的設計:轉換資料並回傳新值
fn to_uppercase(s: String) -> String {
s.to_uppercase()
}
fn add_prefix(s: String, prefix: &str) -> String {
format!("{}{}", prefix, s)
}
fn reverse(s: String) -> String {
s.chars().rev().collect()
}
// 好的設計:支援多種輸入類型
fn process_text<T: AsRef<str>>(text: T) -> String {
let s = text.as_ref();
s.to_uppercase()
}
fn main() {
let owned = String::from("hello");
let literal = "world";
// 兩種用法都支援
println!("{}", process_text(&owned)); // 借用
println!("{}", process_text(owned)); // 移動
println!("{}", process_text(literal)); // 字串字面值
}
通過理解所有權系統在 API 設計中的應用,我們看到了 Rust 的設計哲學:
T
:我要所有權&T
:我只讀取&mut T
:我要修改如果函式只需要讀取資料,就用 &str
去「借」。
這是最常用、也最推薦的方式。
如果函式需要修改資料,就用 &mut String
去「借來改」。
記得變數本身也要宣告成 mut
。
如果函式是要消耗掉資料來創造新東西,或者是要把資料的所有權轉移走,才用 String
把東西「拿走」。
安全性 (Safety):編譯期就根除記憶體問題
效率 (Efficiency):零成本抽象
可預測性 (Predictability):資源生命週期明確
對習慣了「隱式共享」或「垃圾回收」的開發者來說,這套系統可能看起來過於嚴格。
但正是這種「先緊後鬆」的哲學,才能夠編寫出既安全又高效的程式碼。
在下一篇我們會深入探討生命週期 (Lifetimes),理解 Rust 如何確保「借用 Borrowing 」的時間安全性。