Smart Pointers
JavaScript references vs Rust smart pointers
หัวข้อที่มีชื่อว่า “JavaScript references vs Rust smart pointers”ใน JavaScript และ TypeScript ทุก object อยู่บน heap และ garbage collector ดูแล lifetime ให้ คุณไม่ต้องคิดว่าใครเป็นเจ้าของอะไร GC จัดการเอง ส่วน Rust ไม่มี GC แต่ให้ smart pointer มาเป็นชุดเครื่องมือที่บอก ownership และ aliasing rule อย่างชัดเจน โดยมีต้นทุน runtime เป็นศูนย์
| Smart pointer | วัตถุประสงค์ | คู่เทียบ JS/TS |
|---|---|---|
Box<T> | Heap allocation, single owner | new MyClass() — เจ้าของคนเดียว ถูก drop เมื่อออกจาก scope |
Rc<T> | Reference-counted shared ownership (single-thread) | GC reference counting |
Arc<T> | เหมือนกับ Rc<T> แต่ thread-safe | Shared reference ข้าม async tasks |
RefCell<T> | Interior mutability (runtime borrow checking) | { mutable: T } wrapper |
Rc<RefCell<T>> | Shared + mutable (single-thread) | Mutable shared state ข้ามหลาย holders |
Box<T> — heap allocation ด้วย single ownership
หัวข้อที่มีชื่อว่า “Box<T> — heap allocation ด้วย single ownership”Box<T> คือ smart pointer ที่ง่ายที่สุด: ย้ายค่าไปไว้บน heap แล้วคืน pointer แบบ single-owner ให้ พอ Box หลุด scope หน่วยความจำบน heap จะถูกคืนอัตโนมัติ
// TypeScript — every object is heap-allocated by defaultclass Node { constructor( public value: number, public next: Node | null = null ) {}}
const list = new Node(1, new Node(2, new Node(3)));console.log(list.value); // 1console.log(list.next?.value); // 2// Box<T>: explicit heap allocation in Rust// Required for recursive types (the compiler needs a fixed-size pointer)#[derive(Debug)]enum List { Cons(i32, Box<List>), Nil,}
fn main() { let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil)))))); println!("{:?}", list);
// Simple Box usage let b = Box::new(5); println!("b = {b}"); // b is freed here (end of scope)}Rc<T> — shared ownership ด้วย reference counting
หัวข้อที่มีชื่อว่า “Rc<T> — shared ownership ด้วย reference counting”เมื่อหลายส่วนของ program ต้องการ อ่าน ค่าเดียวกัน และคุณกำหนดตอน compile time ไม่ได้ว่าตัวไหนจะเลิกใช้เป็นตัวสุดท้าย ให้ใช้ Rc<T> ทุกครั้งที่เรียก Rc::clone reference count จะเพิ่มขึ้นหนึ่ง และเมื่อ Rc ตัวสุดท้าย drop ค่านั้นถึงจะถูกคืน นี่คือสิ่งที่ GC ของ JavaScript ทำให้อัตโนมัติ — ส่วน Rc ทำให้เห็นชัด ๆ
// TypeScript — two variables can reference the same objectconst config = { maxRetries: 3 };const serviceA = { config };const serviceB = { config }; // same object, no copyconfig.maxRetries = 5;console.log(serviceA.config.maxRetries); // 5 — shared referenceuse std::rc::Rc;
fn main() { let config = Rc::new(String::from("max_retries=3"));
let service_a = Rc::clone(&config); // increments ref count let service_b = Rc::clone(&config); // increments ref count
println!("config = {config}"); println!("service_a = {service_a}"); println!("service_b = {service_b}"); println!("ref count = {}", Rc::strong_count(&config)); // 3
drop(service_a); // decrements ref count println!("after drop: ref count = {}", Rc::strong_count(&config)); // 2}RefCell<T> — interior mutability
หัวข้อที่มีชื่อว่า “RefCell<T> — interior mutability”borrow checker ของ Rust บังคับว่าคุณจะมี mutable reference หนึ่งตัว หรือ immutable reference หลายตัวก็ได้ — แต่ห้ามมีพร้อมกัน ส่วน RefCell<T> ย้ายการตรวจนี้ไปไว้ที่ runtime: เรียก .borrow() เมื่อต้องการ shared access และ .borrow_mut() เมื่อต้องการ exclusive access ถ้าละเมิดกฎตอน runtime จะเกิด panic แทนที่จะโดนปฏิเสธตั้งแต่ compile
use std::cell::RefCell;
let data = RefCell::new(vec![1, 2, 3]);{ let mut v = data.borrow_mut(); // exclusive borrow v.push(4);} // borrow released hereprintln!("{:?}", data.borrow()); // shared borrow: [1, 2, 3, 4]Rc<RefCell<T>> — shared mutable state
หัวข้อที่มีชื่อว่า “Rc<RefCell<T>> — shared mutable state”รวม Rc (หลาย owners) กับ RefCell (interior mutability) เพื่อให้ได้คู่เทียบที่ใกล้เคียงที่สุดกับ JavaScript’s mutable shared references
ลองเล่นดู
หัวข้อที่มีชื่อว่า “ลองเล่นดู”use std::rc::Rc;use std::cell::RefCell;
fn main() { // Box<T>: heap allocation, single owner let b = Box::new(5); println!("b = {b}");
// Rc<T>: reference-counted shared ownership let a = Rc::new(String::from("hello")); let b2 = Rc::clone(&a); println!("a = {a}, b2 = {b2}, count = {}", Rc::strong_count(&a));
// RefCell<T>: interior mutability - borrow checking at runtime let data = RefCell::new(vec![1, 2, 3]); { let mut v = data.borrow_mut(); v.push(4); } println!("data = {:?}", data.borrow());
// Rc<RefCell<T>>: shared mutable state let shared = Rc::new(RefCell::new(0)); let clone1 = Rc::clone(&shared); *clone1.borrow_mut() += 10; println!("shared = {}", shared.borrow());}Compiling…