恭喜你完成了第一部分的學習!經過前七天的基礎訓練,我們已經掌握了 Rust 的基本語法和 Cargo 工具鏈。今天,我們要進入所有權系統(Ownership System)。
在開始之前,讓我們先理解為什麼 Rust 需要這套獨特的系統:
垃圾回收(Garbage Collection)
手動管理
Rust 選擇了第三條路:編譯時期的記憶體管理,透過所有權系統在編譯時就確保記憶體安全。
// 規則 1:每個值都有一個擁有者(owner)
// 規則 2:同一時間只能有一個擁有者
// 規則 3:當擁有者離開作用域,值會被丟棄
fn main() {
{ // s 在這裡無效,它尚未聲明
let s = "hello"; // s 從這裡開始有效
// 使用 s
println!("{}", s);
} // 作用域結束,s 不再有效
}
fn main() {
// 基本型別實作了 Copy trait,會自動複製
let x = 5;
let y = x; // x 的值被複製給 y
println!("x = {}, y = {}", x, y); // 兩個都可以使用!
}
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有權轉移給 s2
// println!("{}", s1); // 錯誤!s1 已經無效
println!("{}", s2); // 只有 s2 可以使用
}
讓我們視覺化這個過程:
堆疊(Stack) 堆積(Heap)
┌─────────┐ ┌─────────────┐
│ s1 │ ───X──> │ "hello" │
├─────────┤ └─────────────┘
│ s2 │ ───────────────┘
└─────────┘
fn main() {
let s = String::from("hello");
takes_ownership(s); // s 的所有權移動到函式中
// println!("{}", s); // 錯誤!s 已經無效
let x = 5;
makes_copy(x); // x 是 i32,會複製
println!("x = {}", x); // x 仍然有效
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 離開作用域並被丟棄
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer 離開作用域,沒什麼特別的
fn main() {
let s1 = gives_ownership(); // 函式返回值的所有權移動給 s1
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); // s2 移動到函式,返回值移動給 s3
println!("s1 = {}, s3 = {}", s1, s3);
}
fn gives_ownership() -> String {
let some_string = String::from("yours");
some_string // 返回並移動所有權
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // 返回並移動所有權
}
讓我們用昨天學到的 Cargo 建立一個新專案,實踐所有權概念:
cargo new task_manager
cd task_manager
// src/main.rs
#[derive(Debug)]
struct Task {
title: String,
completed: bool,
}
fn main() {
let task = Task {
title: String::from("學習 Rust 所有權"),
completed: false,
};
process_task(task);
// println!("{:?}", task); // 錯誤!task 已經被移動
}
fn process_task(task: Task) {
println!("處理任務: {}", task.title);
// task 在函式結束時被丟棄
}
fn main() {
let task = Task {
title: String::from("學習 Rust 所有權"),
completed: false,
};
let task = process_task(task); // 取回所有權
println!("任務狀態: {:?}", task); // 現在可以使用了!
}
fn process_task(mut task: Task) -> Task {
println!("處理任務: {}", task.title);
task.completed = true;
task // 返回所有權
}
fn main() {
let task = Task {
title: String::from("學習 Rust 所有權"),
completed: false,
};
let task_copy = task.clone(); // 明確地複製
process_task(task);
println!("複製的任務: {:?}", task_copy); // 可以使用複製品
}
fn process_task(task: Task) {
println!("處理任務: {}", task.title);
}
Rust 編譯器會在編譯時追蹤每個值的所有權:
fn main() {
let s1 = String::from("hello"); // s1 擁有 "hello"
let s2 = s1; // 所有權轉移: s1 -> s2
let s3 = s2.clone(); // s3 擁有一個複製品
drop(s2); // s2 的值被提前釋放
// println!("{}", s2); // 錯誤!s2 已經無效
println!("{}", s3); // s3 仍然有效
}
struct TaskBuilder {
title: String,
description: Option<String>,
}
impl TaskBuilder {
fn new(title: String) -> Self {
TaskBuilder {
title,
description: None,
}
}
fn description(mut self, desc: String) -> Self {
self.description = Some(desc);
self // 返回 self 的所有權
}
fn build(self) -> Task {
Task {
title: self.title,
completed: false,
}
}
}
impl Task {
// 這個方法會消耗 self
fn complete(mut self) -> Self {
self.completed = true;
self
}
// 這個方法不會消耗 self(我們下一章會學到 &self)
fn is_completed(&self) -> bool {
self.completed
}
}
當遇到所有權錯誤時,編譯器會給出詳細的提示:
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:20
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}", s1);
| ^^ value borrowed here after move
Vec<String>
,返回最長的字串(移動所有權)fn find_longest(strings: Vec<String>) -> String {
// 實作這個函式
}
struct Stack {
items: Vec<String>,
}
impl Stack {
fn new() -> Self {
// 實作
}
fn push(&mut self, item: String) {
// 實作
}
fn pop(&mut self) -> Option<String> {
// 實作
}
}