Ownership และ Borrowing ใน Rust: คู่มือฉบับสมบูรณ์
เชี่ยวชาญระบบ ownership และ borrowing ของ Rust กฎความเป็นเจ้าของ การอ้างอิง lifetime และรูปแบบการจัดการหน่วยความจำขั้นสูง

ระบบ ownership คือสิ่งที่ทำให้ Rust แตกต่างจากภาษาโปรแกรมอื่นๆ ทุกภาษา แนวทางที่เป็นเอกลักษณ์นี้รับประกันความปลอดภัยของหน่วยความจำโดยไม่ต้องใช้ garbage collector โดยจับ bug ในเวลาคอมไพล์แทนที่จะเป็นเวลารันไทม์ คู่มือเชิงลึกนี้สำรวจกลไก ownership และ borrowing ตั้งแต่พื้นฐานไปจนถึงรูปแบบการใช้งานในระดับ production ขั้นสูง
คอมไพเลอร์ Rust ทำหน้าที่เป็นผู้ช่วยโปรแกรมเมอร์ที่เข้มงวด: ทุกข้อผิดพลาด ownership ที่ถูกบล็อกในเวลาคอมไพล์เป็นตัวแทนของ bug ที่อาจเกิดขึ้นและถูกป้องกันใน production
สามกฎพื้นฐานของ Ownership
ระบบ ownership ตั้งอยู่บนกฎที่เรียบง่ายแต่เข้มงวดสามข้อ เมื่อกฎเหล่านี้ถูกซึมซับแล้ว แบบจำลองทางความคิดของ Rust จะกลายเป็นเรื่องธรรมชาติและคาดเดาได้
// Demonstration of the three fundamental rules
fn main() {
// Rule 1: Each value has exactly ONE owner
let s1 = String::from("hello"); // s1 is the sole owner
// Rule 2: There can only be one owner at a time
let s2 = s1; // Ownership transferred (moved) from s1 to s2
// println!("{}", s1); // Compile ERROR: s1 no longer exists
println!("s2 = {}", s2); // Only s2 is valid now
// Rule 3: When the owner goes out of scope, the value is dropped
{
let s3 = String::from("temporary");
println!("s3 inside block = {}", s3);
} // s3 is automatically freed here (drop is called)
// println!("{}", s3); // ERROR: s3 no longer exists
}กฎทั้งสามนี้ขจัด bug ทั้งหมวดหมู่ออกไป: use-after-free, double-free และการรั่วไหลของหน่วยความจำ คอมไพเลอร์ตรวจสอบแบบสถิตว่ากฎเหล่านี้ถูกปฏิบัติตาม
Move vs Copy: เข้าใจความหมายของการถ่ายโอน
พฤติกรรมของการกำหนดค่าขึ้นอยู่กับชนิดข้อมูล ชนิดที่ implement trait Copy จะถูกทำสำเนา ในขณะที่ชนิดอื่นๆ จะถูกถ่ายโอนผ่าน move
// Distinction between Copy types and Move types
fn main() {
// Copy types: values stored on the stack, known size
let x: i32 = 42;
let y = x; // x is COPIED, not moved
println!("x = {}, y = {}", x, y); // Both are valid
// Other Copy types: f64, bool, char, tuples of Copy types
let point = (3.0, 4.0);
let point_copy = point; // Tuple copy
println!("Original: {:?}, Copy: {:?}", point, point_copy);
// Move types: values on the heap, dynamic size
let s1 = String::from("owned");
let s2 = s1; // s1 is MOVED to s2
// println!("{}", s1); // ERROR: value moved
println!("s2 = {}", s2);
// Vec, HashMap, Box are also Move types
let vec1 = vec![1, 2, 3];
let vec2 = vec1; // Move, not copy
// println!("{:?}", vec1); // ERROR
println!("vec2 = {:?}", vec2);
}
// Explicit clone to duplicate Move types
fn explicit_clone() {
let original = String::from("important data");
let clone = original.clone(); // Explicit duplication (memory cost)
println!("Original: {}", original); // Still valid
println!("Clone: {}", clone); // Independent copy
}ความแตกต่าง Move/Copy เป็นพื้นฐาน: กำหนดว่าการกำหนดค่าจะถ่ายโอนความเป็นเจ้าของหรือสร้างสำเนาอิสระ
การเรียก .clone() ควรเป็นไปด้วยความตั้งใจ โค้ดที่เต็มไปด้วย clone อาจชี้ให้เห็นปัญหาด้านการออกแบบ Borrowing มักเป็นทางแก้ที่ดีกว่า
Borrowing: การอ้างอิงแบบไม่เปลี่ยนแปลงและเปลี่ยนแปลงได้
Borrowing อนุญาตให้เข้าถึงค่าโดยไม่ต้องรับความเป็นเจ้าของ กลไกนี้ทำให้โค้ด Rust ปลอดภัยและมีประสิทธิภาพในเวลาเดียวกัน
// Immutable and mutable references
fn main() {
let s = String::from("hello");
// Immutable reference: read-only, multiple allowed
let len = calculate_length(&s); // Immutable borrow
println!("'{}' has {} characters", s, len); // s still valid
// Multiple simultaneous immutable references: OK
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("r1={}, r2={}, r3={}", r1, r2, r3);
}
fn calculate_length(s: &String) -> usize {
// s is a reference, not the owner
s.len()
} // s goes out of scope but doesn't drop anything (not owner)
// Mutable references: modification allowed
fn mutable_borrowing() {
let mut s = String::from("hello");
change(&mut s); // Mutable borrow
println!("After modification: {}", s);
}
fn change(s: &mut String) {
s.push_str(", world!"); // Modification via mutable reference
}กฎทองของ borrowing: หลายการอ้างอิงแบบไม่เปลี่ยนแปลง หรือ การอ้างอิงแบบเปลี่ยนแปลงได้เพียงหนึ่งเดียว ไม่เคยทั้งสองพร้อมกัน
กฎของ Borrow Checker
Borrow checker คือส่วนประกอบของคอมไพเลอร์ที่ตรวจสอบกฎ borrowing การเข้าใจข้อผิดพลาดของมันช่วยให้แก้ปัญหาได้รวดเร็ว
// Strict borrow checker rules
fn main() {
// RULE 1: No mutable reference with immutable references
let mut s = String::from("hello");
let r1 = &s; // Immutable reference: OK
let r2 = &s; // Another immutable reference: OK
// let r3 = &mut s; // ERROR: cannot borrow as mutable
println!("{} and {}", r1, r2);
// AFTER using r1 and r2, they are "dead"
let r3 = &mut s; // Now OK: r1 and r2 no longer used
r3.push_str(" world");
println!("{}", r3);
// RULE 2: Only one mutable reference at a time
let mut data = String::from("exclusive");
let ref1 = &mut data;
// let ref2 = &mut data; // ERROR: already borrowed mutably
ref1.push_str("!");
println!("{}", ref1);
}
// RULE 3: References cannot outlive the data
fn dangling_reference_prevented() {
let reference;
{
let s = String::from("short-lived");
// reference = &s; // ERROR: s doesn't live long enough
}
// s is dropped here, reference would be invalid
// Solution: move the value out of the scope
let owned_outside;
{
let s = String::from("moved out");
owned_outside = s; // Move, not reference
}
println!("{}", owned_outside); // OK: owned_outside is the owner
}Borrow checker ใช้ Non-Lexical Lifetimes (NLL): การอ้างอิงถือว่าทำงานอยู่จนถึงการใช้งานครั้งสุดท้ายเท่านั้น ไม่ใช่จนถึงสิ้นสุด scope
พร้อมที่จะพิชิตการสัมภาษณ์ Rust แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
Lifetime: การอธิบายระยะเวลาของการอ้างอิง
Lifetime คือคำอธิบายที่ช่วยให้คอมไพเลอร์ตรวจสอบว่าการอ้างอิงยังคงถูกต้อง โดยส่วนใหญ่แล้วระบบจะอนุมานให้โดยอัตโนมัติ
// Explicit lifetime annotations
// Without annotation: compiler infers lifetimes
fn first_word(s: &str) -> &str {
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}
// With explicit annotation: same function
fn first_word_explicit<'a>(s: &'a str) -> &'a str {
// 'a means: returned reference lives as long as the input
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}
// When annotations are necessary: multiple references
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// Compiler cannot guess which reference is returned
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
println!("Longest: {}", result); // OK here
}
// println!("{}", result); // ERROR if uncommented: string2 dropped
}Lifetime ไม่เปลี่ยนระยะเวลาที่ข้อมูลมีอยู่ แต่อธิบายความสัมพันธ์ระหว่างระยะเวลาของการอ้างอิงต่างๆ
Lifetime ใน Struct
เมื่อ struct มีการอ้างอิง ต้องระบุ lifetime เพื่อรับประกันว่า struct จะไม่อยู่นานกว่าข้อมูลที่อ้างอิง
// Structs containing references
// Struct with reference: lifetime required
struct ImportantExcerpt<'a> {
part: &'a str, // This reference must live at least as long as the struct
}
impl<'a> ImportantExcerpt<'a> {
// Method returning a reference with the same lifetime
fn level(&self) -> i32 {
3
}
// Elision rule: &self implies the output lifetime
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part // Returns with 'a lifetime from self
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence, // OK: novel outlives excerpt
};
println!("Excerpt: {}", excerpt.part);
println!("Level: {}", excerpt.level());
}
// Static lifetime: reference valid for the entire program duration
fn static_lifetime_example() {
let s: &'static str = "This string is in the binary";
// String literals always have 'static lifetime
println!("{}", s);
}กฎการตัด lifetime มักช่วยให้สามารถละเว้นคำอธิบายในกรณีทั่วไปได้ ทำให้โค้ดอ่านง่ายขึ้น
รูปแบบขั้นสูง: Interior Mutability
บางครั้งความเปลี่ยนแปลงต้องถูกตรวจสอบในเวลารันไทม์แทนที่จะเป็นเวลาคอมไพล์ Rust จัดเตรียมชนิดสำหรับรูปแบบนี้: RefCell และ Cell
// Interior mutability with RefCell and Cell
use std::cell::{Cell, RefCell};
// Cell: for Copy types, replaces the entire value
struct Counter {
count: Cell<u32>, // Mutable despite &self
}
impl Counter {
fn new() -> Counter {
Counter { count: Cell::new(0) }
}
fn increment(&self) {
// Modification via immutable reference!
self.count.set(self.count.get() + 1);
}
fn get(&self) -> u32 {
self.count.get()
}
}
// RefCell: for non-Copy types, checks at runtime
struct CachedValue {
value: RefCell<Option<String>>,
}
impl CachedValue {
fn new() -> CachedValue {
CachedValue { value: RefCell::new(None) }
}
fn get_or_compute(&self, compute: impl FnOnce() -> String) -> String {
// borrow() for reading, borrow_mut() for writing
if self.value.borrow().is_none() {
*self.value.borrow_mut() = Some(compute());
}
self.value.borrow().as_ref().unwrap().clone()
}
}
fn main() {
let counter = Counter::new();
counter.increment();
counter.increment();
println!("Counter: {}", counter.get()); // 2
let cache = CachedValue::new();
let result = cache.get_or_compute(|| {
println!("Expensive computation...");
String::from("result")
});
println!("Value: {}", result);
// Second call: no recomputation
let result2 = cache.get_or_compute(|| String::from("never executed"));
println!("Cache hit: {}", result2);
}RefCell และ Cell ย้ายการตรวจสอบ borrowing ไปยังรันไทม์ การละเมิดกฎทำให้เกิด panic แทนที่จะเป็นข้อผิดพลาดในการคอมไพล์
RefCell::borrow_mut() จะ panic หากค่าถูกยืมไปแล้ว ควรใช้ try_borrow_mut() สำหรับการจัดการข้อผิดพลาดที่ชัดเจน
Smart Pointer และ Ownership
Smart pointer เช่น Box, Rc และ Arc นำเสนอกลยุทธ์ ownership ที่แตกต่างกันสำหรับกรณีการใช้งานเฉพาะ
// Box, Rc, and Arc for different ownership patterns
use std::rc::Rc;
use std::sync::Arc;
use std::thread;
// Box: single owner, data on the heap
fn box_example() {
let boxed = Box::new(vec![1, 2, 3, 4, 5]);
println!("Boxed vec: {:?}", boxed);
// Useful for: recursive types, large objects, trait objects
}
// Rc: reference counting, multiple owners (single-thread)
fn rc_example() {
let data = Rc::new(String::from("shared data"));
let clone1 = Rc::clone(&data); // Increments the counter
let clone2 = Rc::clone(&data);
println!("Count: {}", Rc::strong_count(&data)); // 3
println!("All share: {}, {}, {}", data, clone1, clone2);
} // Freed when counter reaches 0
// Arc: thread-safe Rc (Atomic Reference Counting)
fn arc_example() {
let data = Arc::new(vec![1, 2, 3]);
let handles: Vec<_> = (0..3).map(|i| {
let data_clone = Arc::clone(&data);
thread::spawn(move || {
println!("Thread {}: {:?}", i, data_clone);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
}
fn main() {
box_example();
rc_example();
arc_example();
}การเลือก smart pointer ขึ้นอยู่กับรูปแบบ ownership: เฉพาะตัว (Box) แชร์แบบ single-thread (Rc) หรือแชร์แบบ multi-thread (Arc)
รูปแบบ Ownership ที่ใช้ได้จริง
ต่อไปนี้เป็นรูปแบบทั่วไปสำหรับการจัดโครงสร้างโค้ดรอบระบบ ownership
// Practical patterns for ownership management
// Pattern 1: Builder pattern with chained ownership
struct RequestBuilder {
url: String,
headers: Vec<(String, String)>,
timeout: Option<u64>,
}
impl RequestBuilder {
fn new(url: &str) -> Self {
RequestBuilder {
url: url.to_string(),
headers: Vec::new(),
timeout: None,
}
}
// Consumes self and returns the new self
fn header(mut self, key: &str, value: &str) -> Self {
self.headers.push((key.to_string(), value.to_string()));
self // Returns ownership
}
fn timeout(mut self, seconds: u64) -> Self {
self.timeout = Some(seconds);
self
}
fn build(self) -> Request {
Request {
url: self.url,
headers: self.headers,
timeout: self.timeout.unwrap_or(30),
}
}
}
struct Request {
url: String,
headers: Vec<(String, String)>,
timeout: u64,
}
// Pattern 2: Cow (Copy-on-Write) to avoid allocations
use std::borrow::Cow;
fn process_text(input: &str) -> Cow<str> {
if input.contains("REPLACE") {
// Allocation only if modification needed
Cow::Owned(input.replace("REPLACE", "NEW"))
} else {
// No allocation, returns a reference
Cow::Borrowed(input)
}
}
// Pattern 3: Take to extract from an Option
fn extract_value(data: &mut Option<String>) -> String {
data.take().unwrap_or_else(|| String::from("default"))
// take() replaces with None and returns ownership of the value
}
fn main() {
// Builder pattern
let request = RequestBuilder::new("https://api.example.com")
.header("Authorization", "Bearer token")
.header("Content-Type", "application/json")
.timeout(60)
.build();
println!("URL: {}, Timeout: {}s", request.url, request.timeout);
// Cow pattern
let text1 = process_text("hello world"); // No allocation
let text2 = process_text("hello REPLACE"); // Allocation
println!("{} | {}", text1, text2);
// Take pattern
let mut optional = Some(String::from("extracted"));
let value = extract_value(&mut optional);
println!("Value: {}, Option: {:?}", value, optional); // None
}รูปแบบเหล่านี้ใช้ประโยชน์จากระบบ ownership เพื่อสร้าง API ที่ใช้งานง่ายและมีประสิทธิภาพ
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
บทสรุป
ระบบ ownership และ borrowing ของ Rust เป็นการเปลี่ยนกระบวนทัศน์ในการจัดการหน่วยความจำ เมื่อเชี่ยวชาญแล้ว มันจะกลายเป็นพันธมิตรที่ทรงพลังในการเขียนโค้ดที่ทั้งมีประสิทธิภาพและปลอดภัย
ประเด็นสำคัญที่ต้องจำ:
✅ สามกฎ ownership: เจ้าของเดียว การถ่ายโอนความเป็นเจ้าของ การ drop อัตโนมัติ
✅ Borrowing: หลายการอ้างอิงไม่เปลี่ยนแปลง หรือ การอ้างอิงเปลี่ยนแปลงได้แบบเฉพาะตัว
✅ Lifetime: อธิบายความสัมพันธ์ระหว่างระยะเวลาของการอ้างอิง
✅ Interior mutability: RefCell และ Cell สำหรับความเปลี่ยนแปลงที่ตรวจสอบในรันไทม์
✅ Smart pointer: Box (เฉพาะตัว) Rc (แชร์) Arc (thread-safe)
✅ รูปแบบที่ใช้ได้จริง: Builder, Cow, Take สำหรับ API แบบ idiomatic
Borrow checker อาจดูเข้มงวดในตอนแรก แต่ทุกข้อผิดพลาดที่รายงานเป็นตัวแทนของ bug ที่อาจเกิดขึ้นและถูกหลีกเลี่ยง ด้วยการฝึกฝน การคิดในแง่ของ ownership จะกลายเป็นเรื่องธรรมชาติและปรับปรุงคุณภาพโค้ดในทุกภาษา
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
คุณหาบั๊กใน Rust เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 29 เมษายน 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

ทำความเข้าใจ Ownership และ Borrowing ใน Rust อย่างลึกซึ้ง สำหรับนักพัฒนาและผู้เตรียมสอบสัมภาษณ์
บทความเจาะลึกระบบ Ownership และ Borrowing ของ Rust ตั้งแต่พื้นฐานจนถึงขั้นสูง พร้อมตัวอย่างโค้ดจริงและแนวทางแก้ปัญหา Compiler Error ที่พบบ่อย เหมาะสำหรับการเตรียมสัมภาษณ์งาน

คำถามสัมภาษณ์ Rust: คู่มือฉบับสมบูรณ์ 2026
25 คำถามสัมภาษณ์ Rust ที่พบบ่อยที่สุด Ownership, borrowing, lifetime, trait, async และ concurrency พร้อมคำตอบละเอียดและตัวอย่างโค้ด

Rust: พื้นฐานสำหรับนักพัฒนาที่มีประสบการณ์ในปี 2026
เรียนรู้ Rust ได้อย่างรวดเร็วโดยอาศัยความรู้ด้านการเขียนโปรแกรมที่มีอยู่ Ownership, borrowing, lifetimes และ pattern ที่จำเป็นอธิบายไว้สำหรับนักพัฒนาที่มาจาก C++, Java หรือ Python