Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Box<T>

本集目標

學會用 Box<T> 把資料放在 heap 上,理解它在遞迴型別中的必要性。

概念說明

還記得第 4 章的保險箱比喻嗎?鑰匙圈上掛著鑰匙,鑰匙可以打開保險箱,保險箱裡放著真正的東西。

Box<T> 就是那個保險箱的鑰匙——資料住在 heap 上,而 stack 上的 Box 值讓 Rust 能找到它。

為什麼需要 Box?

大部分時候,Rust 把資料直接放在 stack 上就好了。但有兩種情況需要 Box:

1. 資料太大

如果一個 struct 有很多欄位、佔很多空間,放在 stack 上可能不太好(stack 空間有限)。用 Box 把它移到 heap 上,stack 上只留下那把「鑰匙」。這種用來找到存放在其他位置之資料的資訊稱為指標。

2. 遞迴型別

這是更重要的原因。假設你想定義一個連結串列(linked list):

enum List {
    Node(i32, List), // 編譯錯誤!
    Empty,
}

fn main() {}

Rust 需要在編譯時期知道每個型別的大小。但這裡有個問題:要知道 List 的大小,你需要知道 Node 有多大。Node 包含一個 i32 和一個 List——所以你需要知道 List 有多大。但 List 裡面又有 List⋯⋯

展開來看:List 的大小 = i32 + List 的大小 = i32 + i32 + List 的大小 = ⋯⋯ 永遠算不完。編譯器在這裡直接報錯:「recursive type has infinite size(遞迴型別大小無限大)」。

解法就是用 Box:

enum List {
    Node(i32, Box<List>),
    Empty,
}

fn main() {}

Box<List> 的大小是固定的(就是一個指標的大小),問題就解決了。

Box 的使用

fn main() {
    let x = Box::new(42);
    println!("{}", x); // 可以直接用,Rust 會自動拿裡面的值
}

Box::new(value) 把值搬到 heap 上。Box 擁有裡面的值,離開作用域時會自動釋放(因為 Box 實作了 Drop)。

範例程式碼

// 用 Box 的遞迴型別:連結串列
enum List {
    Node(i32, Box<List>),
    Empty,
}

// 印出串列
fn print_list(list: &List) {
    match list {
        List::Node(value, next) => {
            print!("{} -> ", value);
            print_list(next);
        }
        List::Empty => {
            println!("end");
        }
    }
}

fn main() {
    // 基本的 Box 使用
    let x = Box::new(42);
    println!("Box 裡的值:{}", x);

    // 一步一步建立連結串列:3 -> 2 -> 1 -> end
    // 從最後面開始建立
    let list = List::Empty;                   // end
    let list = List::Node(1, Box::new(list)); // 1 -> end
    let list = List::Node(2, Box::new(list)); // 2 -> 1 -> end
    let list = List::Node(3, Box::new(list)); // 3 -> 2 -> 1 -> end

    print_list(&list);

    // Box 是單獨的一把鑰匙——鑰匙不是 Copy,所以 let b = a 是 move
    let a = Box::new(String::from("hello"));
    let b = a; // 鑰匙從 a 交給 b,a 就空了
    // println!("{}", a); // 編譯錯誤!a 已經被 move 了
    println!("{}", b);
}

重點整理

  • Box<T> 把資料放在 heap 上,stack 上只留一個指標(保險箱比喻的「鑰匙」)。
  • 最重要的用途:遞迴型別(如連結串列)需要 Box 來打破無限大小的問題。
  • Box::new(value) 建立 Box,離開作用域時自動釋放。
  • Box 是單獨的一把鑰匙;move 它的規則和 move 其他非 Copy 的值一樣。