# สมาร์ตพอยน์เตอร์ใน Rust อธิบายครบ: Box, Rc, Arc และ RefCell ปี 2026 > อธิบายสมาร์ตพอยน์เตอร์ Box, Rc, Arc และ RefCell ใน Rust พร้อมตัวอย่างที่คอมไพล์ได้ปี 2026 ตารางตัดสินใจ และคำถามสัมภาษณ์ที่พบบ่อย - Published: 2026-07-05 - Updated: 2026-07-07 - Author: SharpSkill - Tags: rust, smart-pointers, memory-management, interview, rust-2024-edition - Reading time: 11 min --- สมาร์ตพอยน์เตอร์ใน Rust คือเครื่องมือที่ปลดล็อกรูปแบบการเป็นเจ้าของซึ่ง borrow checker เพียงลำพังไม่อาจแสดงออกได้ ได้แก่ การจองหน่วยความจำบนฮีป การเป็นเจ้าของร่วมกัน และการกลายพันธุ์ค่าผ่านการอ้างอิงที่ใช้ร่วมกัน ในขณะที่การอ้างอิงธรรมดา (`&T`) เพียงยืมค่ามาเท่านั้น สมาร์ตพอยน์เตอร์เป็นเจ้าของข้อมูลของมันและเพิ่มพฤติกรรมพิเศษทับลงไป คู่มือนี้แจกแจงสี่ชนิดที่นักพัฒนา Rust ทุกคนพบทั้งในระบบจริงและในการสัมภาษณ์ ได้แก่ `Box`, `Rc`, `Arc` และ `RefCell` พร้อมตัวอย่างที่คอมไพล์ได้ซึ่งมุ่งไปที่ Rust รุ่นปี 2024 > **สรุปในบรรทัดเดียว** > > ใช้ `Box` สำหรับการจองฮีปที่มีเจ้าของคนเดียว, `Rc` สำหรับการเป็นเจ้าของร่วมบนเธรดเดียว, `Arc` สำหรับการเป็นเจ้าของร่วมข้ามเธรด และ `RefCell` เพื่อกลายพันธุ์ค่าผ่านการอ้างอิงที่ใช้ร่วมกัน คู่ `Rc>` และ `Arc>` ครอบคลุมสถานะที่ใช้ร่วมและแก้ไขได้ ## สมาร์ตพอยน์เตอร์ใน Rust คืออะไร สมาร์ตพอยน์เตอร์คือ struct ที่มีพฤติกรรมเหมือนพอยน์เตอร์แต่พกพาเมทาดาทาหรือความสามารถเพิ่มเติมมาด้วย ส่วนใหญ่จะอิมพลีเมนต์เทรต `Deref` ทำให้ `*pointer` และการเรียกเมท็อดทำงานเสมือนว่าพอยน์เตอร์เป็นการอ้างอิงธรรมดา และเทรต `Drop` ทำให้การเก็บกวาดทำงานอัตโนมัติเมื่อค่านั้นออกจากขอบเขต ไลบรารีมาตรฐานมาพร้อมสี่ชนิดที่กล่าวถึงในที่นี้ และการเข้าใจมันขึ้นอยู่กับความเข้าใจที่แน่นในเรื่อง[การเป็นเจ้าของและการยืม](/blog/rust/ownership-borrowing-rust-complete-guide) ซึ่งเป็นตัวกำหนดว่าใครจะคืนหน่วยความจำแต่ละก้อนและเมื่อใด ความแตกต่างสำคัญจากการอ้างอิงคือการเป็นเจ้าของ `&T` ไม่เคยเป็นเจ้าของข้อมูลที่มันชี้ไป จึงไม่อาจอยู่ได้นานกว่าตัวค่า สมาร์ตพอยน์เตอร์เป็นเจ้าของข้อมูล ควบคุมช่วงอายุของมัน และคืนหน่วยความจำอย่างแน่นอนคาดเดาได้ [บทว่าด้วยสมาร์ตพอยน์เตอร์ใน Rust Book](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) จัดพวกมันเป็นหมวดหมู่เดียวกันก็เพราะมันมีรูปแบบการเป็นเจ้าของบวกพฤติกรรมร่วมกันนี้เอง ## `Box`: การจองฮีปสำหรับชนิดเวียนเกิดและชนิดที่รู้ขนาด `Box` คือสมาร์ตพอยน์เตอร์ที่ง่ายที่สุด มันเก็บค่าไว้บนฮีปและถือพอยน์เตอร์ที่ชี้ไปยังค่านั้นไว้บนสแตก โดยมีเจ้าของเพียงคนเดียวและไม่มีภาระตอนรันไทม์นอกจากการจองหน่วยความจำเอง งานที่พบบ่อยที่สุดของมันคือการให้ชนิดเวียนเกิดมีขนาดที่รู้แน่ชัด ชนิดที่บรรจุตัวเองโดยตรงจะมีขนาดใหญ่ไม่รู้จบ แต่ `Box` เป็นเพียงพอยน์เตอร์ ขนาดของมันจึงคงที่ไม่ว่าจะชี้ไปยังอะไร ตัวอย่างด้านล่างนิยามต้นไม้ทวิภาค แต่ละ `Node` ถือลูกสองตัว และหากไม่มีการอ้างอิงทางอ้อม คอมไพเลอร์จะคำนวณขนาดของ `Tree` ไม่ได้ การห่อลูกแต่ละตัวไว้ใน `Box` จะตัดการเวียนเกิดออกที่ระดับชนิด ```rust // tree.rs #[derive(Debug)] enum Tree { Leaf(i32), // Box puts each child on the heap, so Node has a fixed size (two pointers). Node(Box, Box), } fn sum(tree: &Tree) -> i32 { match tree { Tree::Leaf(value) => *value, // base case: return the leaf Tree::Node(left, right) => sum(left) + sum(right), // recurse into both children } } fn main() { // Build (1) + ((2) + (3)) = 6 let tree = Tree::Node( Box::new(Tree::Leaf(1)), Box::new(Tree::Node( Box::new(Tree::Leaf(2)), Box::new(Tree::Leaf(3)), )), ); println!("sum = {}", sum(&tree)); // sum = 6 } ``` `Box` ยังสำคัญเมื่อการย้ายค่าขนาดใหญ่จะมีต้นทุนสูง หรือเมื่อคืนค่าเป็น trait object อย่าง `Box` ในทุกกรณีกฎเหมือนเดิม คือมีเจ้าของคนเดียว และถูกคืนอัตโนมัติเมื่อ `Box` ถูก drop ## `Rc`: การเป็นเจ้าของร่วมในโค้ดเธรดเดียว บางครั้งค่าหนึ่งต้องการเจ้าของหลายราย และไม่มีรายใดชัดเจนว่าเป็นรายสุดท้ายที่ใช้มัน `Rc` (นับการอ้างอิง) แก้ปัญหานี้ด้วยการเก็บจำนวนนับแบบเข้ม (strong count) ว่ามีเจ้าของอยู่กี่ราย `Rc::clone` เพิ่มจำนวนนับนั้นโดยไม่คัดลอกข้อมูลที่อยู่เบื้องล่าง และการ drop แต่ละครั้งจะลดมันลง เมื่อจำนวนนับแตะศูนย์ ค่านั้นจะถูกคืน ลองพิจารณาออบเจ็กต์การตั้งค่าที่เวิร์กเกอร์หลายตัวอ่านร่วมกัน เวิร์กเกอร์แต่ละตัวควรถือการตั้งค่าไว้นานเท่าที่จำเป็น และหน่วยความจำที่จองไว้ควรหายไปก็ต่อเมื่อเวิร์กเกอร์ตัวสุดท้ายหมดไปแล้ว ```rust // shared_config.rs use std::rc::Rc; #[derive(Debug)] struct Config { endpoint: String, timeout_ms: u32, } fn main() { // Rc::new moves Config onto the heap with a strong count of 1. let config = Rc::new(Config { endpoint: "https://api.example.com".to_string(), timeout_ms: 5000, }); // Rc::clone only bumps the reference count; it does not deep-copy Config. let worker_a = Rc::clone(&config); let worker_b = Rc::clone(&config); // All three handles point to the same allocation. println!("endpoint: {}", worker_a.endpoint); println!("timeout: {}", worker_b.timeout_ms); // strong_count reports how many owners currently hold the value. println!("owners: {}", Rc::strong_count(&config)); // owners: 3 } ``` การเรียก `Rc::clone` อย่างชัดแจ้ง (แทนที่จะเป็น `config.clone()`) เป็นสำนวนที่เหมาะสม มันส่งสัญญาณให้ผู้อ่านรู้ว่าการดำเนินการนี้ราคาถูกและแตะเพียงตัวนับเท่านั้น ข้อควรระวังคือ `Rc` แจกจ่ายเฉพาะการอ้างอิงที่ใช้ร่วมและเปลี่ยนแปลงไม่ได้เท่านั้น มันไม่สามารถกลายพันธุ์ค่าที่แบ่งปัน และไม่ปลอดภัยที่จะส่งข้ามเธรด [เอกสารของ `Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) ระบุข้อจำกัดทั้งสองอย่างชัดเจน ## `RefCell` และการเปลี่ยนแปลงภายในใน Rust โดยปกติ Rust บังคับว่าค่าหนึ่งจะถูกแบ่งปันผ่านการอ้างอิงแบบเปลี่ยนแปลงไม่ได้หลายตัว หรือถูกกลายพันธุ์ผ่านการอ้างอิงแบบเปลี่ยนแปลงได้เพียงตัวเดียวเท่านั้น และมันตรวจสอบสิ่งนี้ในเวลาคอมไพล์ `RefCell` มอบการเปลี่ยนแปลงภายใน (interior mutability) มันยอมให้โค้ดกลายพันธุ์ค่าผ่านการอ้างอิงที่ใช้ร่วมกันได้ โดยเลื่อนการตรวจสอบเดียวกันนั้นไปไว้ที่เวลารันไทม์ `borrow()` คืนตัวยาม (guard) สำหรับอ่านแบบใช้ร่วม ส่วน `borrow_mut()` คืนตัวยามสำหรับเขียนแบบเอกสิทธิ์ กฎเหมือนกันทุกประการ แต่การละเมิดจะทำให้เกิด panic แทนที่จะคอมไพล์ไม่ผ่าน เมื่อรวมกับ `Rc` จะได้รูปแบบใช้ร่วมและแก้ไขได้บนเธรดเดียวอันเป็นแบบฉบับ คือ `Rc>` เจ้าของหลายรายที่ทุกรายสามารถอัปเดตค่าเดียวกันได้ ```rust // counter.rs use std::rc::Rc; use std::cell::RefCell; // Rc gives shared ownership; RefCell allows mutation through a shared reference. type SharedCounter = Rc>; fn increment(counter: &SharedCounter) { // borrow_mut() hands out an exclusive reference, checked at runtime. *counter.borrow_mut() += 1; } fn main() { let counter: SharedCounter = Rc::new(RefCell::new(0)); let handle_a = Rc::clone(&counter); let handle_b = Rc::clone(&counter); increment(&handle_a); increment(&handle_b); increment(&counter); // borrow() gives a shared read guard; the value is now 3. println!("count = {}", counter.borrow()); // count = 3 } ``` > **RefCell เลื่อนการตรวจสอบการยืมไปที่รันไทม์** > > การเรียก `borrow_mut()` ขณะที่ตัวยาม `borrow()` หรือ `borrow_mut()` อีกตัวยังมีชีวิตอยู่ จะเกิด panic พร้อมข้อความ `already borrowed: BorrowMutError` การรับประกันความปลอดภัยยังคงอยู่ แต่บั๊กเชิงตรรกะที่คอมไพเลอร์น่าจะจับได้กลับกลายเป็นการแครชในเวลารันไทม์ ควรทำให้ตัวยามมีอายุสั้นและหลีกเลี่ยงการถือการยืมข้ามการเรียกฟังก์ชันที่อาจเข้าสู่ `RefCell` เดิมซ้ำ การเปลี่ยนแปลงภายในยังเป็นที่ซ่อนตัวของวงจรการอ้างอิงด้วย ค่า `Rc>` สองตัวที่ชี้หากันจะทำให้จำนวนนับแบบเข้มของกันและกันอยู่เหนือศูนย์ตลอดไป ทำให้ไม่มีตัวใดถูกคืนเลย ทางแก้คือ `Weak` แฮนเดิลที่ไม่เป็นเจ้าของและไม่กระทบต่อจำนวนนับแบบเข้ม คำอธิบายคลาสสิกอยู่ที่ [Learn Rust With Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/) ## `Arc`: การนับการอ้างอิงที่ปลอดภัยต่อเธรดสำหรับงานพร้อมกัน `Arc` (นับการอ้างอิงแบบอะตอม) คือพี่น้องฝั่งหลายเธรดของ `Rc` API สาธารณะแทบจะเหมือนกัน แต่ตัวนับใช้ปฏิบัติการแบบอะตอม ทำให้การโคลนและ drop `Arc` จากหลายเธรดพร้อมกันยังคงถูกต้อง ความเป็นอะตอมนี้เองคือเหตุผลที่เลือก `Arc` เฉพาะเมื่อการแบ่งปันข้ามขอบเขตเธรด การเพิ่มค่าแบบอะตอมช้ากว่าการเพิ่มธรรมดาที่ `Rc` ใช้อย่างวัดได้ เนื่องจาก `Arc` ยังคงแจกจ่ายการอ้างอิงที่เปลี่ยนแปลงไม่ได้ การกลายพันธุ์ข้ามเธรดจึงต้องใช้พรีมิทีฟการซิงโครไนซ์ `Mutex` เป็นคู่หูที่พบบ่อย ให้รูปแบบ `Arc>` ซึ่งสะท้อน `Rc>` สำหรับโค้ดที่ทำงานพร้อมกัน ตัวอย่างต่อไปนี้สร้างสี่เธรดที่แต่ละเธรดเพิ่มค่าผลรวมที่ใช้ร่วมกันหนึ่งพันครั้ง ```rust // parallel_sum.rs use std::sync::{Arc, Mutex}; use std::thread; fn main() { // Arc is safe to share across threads; Mutex serializes access to the inner value. let total = Arc::new(Mutex::new(0u64)); let mut handles = Vec::new(); for _ in 0..4 { // Each thread gets its own Arc handle (an atomic count bump). let total = Arc::clone(&total); handles.push(thread::spawn(move || { for _ in 0..1000 { // lock() blocks until the mutex is free, then returns a guard. let mut value = total.lock().unwrap(); *value += 1; } })); } for handle in handles { handle.join().unwrap(); // wait for every thread before reading } println!("total = {}", *total.lock().unwrap()); // total = 4000 } ``` คอมไพเลอร์บังคับใช้ขอบเขตนี้ให้โดยอัตโนมัติ `Rc` ไม่ใช่ `Send` การพยายามย้ายมันเข้าไปใน `thread::spawn` จึงคอมไพล์ไม่ผ่าน ผลักดันให้หันไปใช้ `Arc` การหยิบ `Arc` มาใช้ภายในเธรดเดียวเป็นเพียงการจ่ายค่าปฏิบัติการอะตอมที่ไม่เคยได้ใช้ [เอกสารของ `Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) อธิบายการรับประกันเรื่องลำดับหน่วยความจำโดยละเอียด และ `Arc>` เป็นรากฐานของสถานะที่ใช้ร่วมส่วนใหญ่ใน [Rust แบบอะซิงโครนัสด้วย Tokio](/blog/rust/rust-async-await-tokio-futures-concurrency) ## Box vs Rc vs Arc vs RefCell: ควรใช้ตัวไหนเมื่อไหร่ ทั้งสี่ชนิดประกอบเข้าด้วยกันตามสองแกน คือ ค่าหนึ่งมีเจ้าของกี่ราย และมันสามารถถูกกลายพันธุ์ผ่านแฮนเดิลที่ใช้ร่วมได้หรือไม่ ตารางนี้สรุปข้อแลกเปลี่ยน | ชนิด | การเป็นเจ้าของ | กลายพันธุ์ผ่านแฮนเดิลที่ใช้ร่วม | ปลอดภัยต่อเธรด | ภาระ | |------|-----------|----------------------------|-------------|----------| | `Box` | รายเดียว | ไม่ | ได้ ถ้า `T: Send` | จองฮีปเท่านั้น | | `Rc` | ร่วมกัน | ไม่ | ไม่ | นับแบบไม่อะตอม | | `Arc` | ร่วมกัน | ไม่ | ได้ | นับแบบอะตอม | | `RefCell` | รายเดียว | ได้ (ตรวจตอนรันไทม์) | ไม่ | ธงการยืมตอนรันไทม์ | ต้นไม้การตัดสินใจในทางปฏิบัตินั้นสั้น ต้องการเจ้าของรายเดียวบนฮีป ใช้ `Box` ต้องการเจ้าของหลายรายบนเธรดเดียว ใช้ `Rc` ต้องการเจ้าของหลายรายข้ามเธรด ใช้ `Arc` ต้องการกลายพันธุ์ค่าที่ใช้ร่วม ให้ห่อชนิดภายในด้วย `RefCell` (เธรดเดียว) หรือ `Mutex` (หลายเธรด) คำแนะนำใน Effective Rust ว่าด้วย[ชนิดของการอ้างอิงและพอยน์เตอร์](https://www.lurklurk.org/effective-rust/) ก็ไปถึงการจัดชั้นแบบเดียวกัน เริ่มจากตัวเลือกที่มีอำนาจน้อยที่สุดและเพิ่มความสามารถเฉพาะเมื่อคอมไพเลอร์บังคับเท่านั้น ## คำถามสัมภาษณ์เรื่องสมาร์ตพอยน์เตอร์ Rust สมาร์ตพอยน์เตอร์เป็นหัวข้อสัมภาษณ์ที่เชื่อถือได้ เพราะมันทดสอบว่าผู้สมัครเข้าใจการเป็นเจ้าของหรือเข้าใจเพียงไวยากรณ์ คำถามด้านล่างปรากฏบ่อยครั้ง และยังมีอีกมากที่รวบรวมไว้ใน [โมดูลสัมภาษณ์สมาร์ตพอยน์เตอร์ Rust](/technologies/rust/interview-questions/smart-pointers) **`Rc` กับ `Arc` ต่างกันอย่างไร?** ทั้งคู่ให้การเป็นเจ้าของร่วมผ่านการนับการอ้างอิง `Rc` ใช้ตัวนับแบบไม่อะตอมและถูกจำกัดอยู่บนเธรดเดียว ส่วน `Arc` ใช้ปฏิบัติการแบบอะตอมและสามารถแบ่งปันข้ามเธรดได้ด้วยต้นทุนรันไทม์เล็กน้อย คอมไพเลอร์บังคับการแยกนี้ `Rc` ไม่ใช่ทั้ง `Send` และ `Sync` จึงข้ามขอบเขตเธรดไม่ได้ **ทำไมต้องรวม `Rc` กับ `RefCell`?** `Rc` ให้การเป็นเจ้าของร่วมแต่เข้าถึงได้แบบเปลี่ยนแปลงไม่ได้เท่านั้น `RefCell` เพิ่มการเปลี่ยนแปลงภายใน ให้เจ้าของกลายพันธุ์ค่าผ่านการอ้างอิงที่ใช้ร่วมพร้อมการตรวจสอบการยืมตอนรันไทม์ เมื่อรวมกัน `Rc>` คือหน่วยประกอบใช้-ร่วม-แก้ไข-ได้บนเธรดเดียวที่เป็นสำนวนมาตรฐาน **การนับการอ้างอิงทำให้หน่วยความจำรั่วใน Rust ได้อย่างไร?** ค่า `Rc` (หรือ `Arc`) สองตัวที่อ้างอิงกันและกันก่อเป็นวงจรที่จำนวนนับแบบเข้มไม่มีวันแตะศูนย์ หน่วยความจำที่จองไว้จึงไม่ถูกคืนเลย การตัดวงจรด้วย `Weak` ซึ่งถือการอ้างอิงแบบไม่เป็นเจ้าของ จะฟื้นการเก็บกวาดที่ถูกต้องกลับมา **เมื่อไรที่ `Box` ดีกว่า `Rc`?** เมื่อใดก็ตามที่เจ้าของรายเดียวก็เพียงพอ `Box` ไม่มีภาระของการนับการอ้างอิง จึงเป็นค่าเริ่มต้นสำหรับการจองฮีป ชนิดเวียนเกิด และ trait object หยิบ `Rc` มาใช้เฉพาะเมื่อจำเป็นต้องมีการเป็นเจ้าของร่วมอย่างแท้จริงเท่านั้น ## บทสรุป สมาร์ตพอยน์เตอร์ใน Rust เปลี่ยนการเป็นเจ้าของจากข้อจำกัดให้กลายเป็นชุดหน่วยประกอบที่นำมาต่อกันได้ การเลือกใช้ระหว่างพวกมันเป็นไปตามรูปร่างของปัญหาโดยตรง: - ใช้ `Box` เมื่อค่าหนึ่งต้องการเจ้าของบนฮีปเพียงรายเดียว เมื่อชนิดเวียนเกิดต้องการขนาดที่คงที่ หรือเมื่อฟังก์ชันคืนค่าเป็น trait object - ใช้ `Rc` สำหรับเจ้าของหลายรายบนเธรดเดียว และเปลี่ยนไปใช้ `Arc` ทันทีที่การเป็นเจ้าของข้ามขอบเขตเธรด - เพิ่ม `RefCell` สำหรับการเปลี่ยนแปลงภายในในโค้ดเธรดเดียว และ `Mutex` สำหรับสิ่งที่เทียบเท่าในงานพร้อมกัน โดยทำให้ตัวยามการยืมและการล็อกมีอายุสั้น - รวมพวกมันอย่างจงใจ `Rc>` สำหรับสถานะที่ใช้ร่วมและแก้ไขได้บนเธรดเดียว `Arc>` สำหรับข้ามเธรด - ระวังวงจรการอ้างอิงระหว่างพอยน์เตอร์ที่มีการนับ และตัดมันด้วย `Weak` เพื่อเลี่ยงการรั่ว - ตั้งค่าเริ่มต้นเป็นชนิดที่มีอำนาจน้อยที่สุด และปล่อยให้คอมไพเลอร์บอกว่าเมื่อใดจึงจำเป็นต้องมีความสามารถมากขึ้น --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/rust/rust-smart-pointers-box-rc-arc-refcell