# ทำความเข้าใจ Ownership และ Borrowing ใน Rust อย่างลึกซึ้ง สำหรับนักพัฒนาและผู้เตรียมสอบสัมภาษณ์ > บทความเจาะลึกระบบ Ownership และ Borrowing ของ Rust ตั้งแต่พื้นฐานจนถึงขั้นสูง พร้อมตัวอย่างโค้ดจริงและแนวทางแก้ปัญหา Compiler Error ที่พบบ่อย เหมาะสำหรับการเตรียมสัมภาษณ์งาน - Published: 2026-03-31 - Updated: 2026-03-31 - Author: Anthony Fillion-Maillet - Tags: rust, ownership, borrowing, memory-management, interview - Reading time: 8 min --- ระบบ Ownership ของ Rust ถือเป็นหัวใจสำคัญที่ทำให้ภาษานี้แตกต่างจากภาษาโปรแกรมอื่นอย่างสิ้นเชิง ระบบนี้ช่วยให้โปรแกรมปลอดภัยจากปัญหาหน่วยความจำ (Memory Safety) โดยไม่ต้องพึ่งพา Garbage Collector ซึ่งเป็นเหตุผลหลักที่ทำให้ Rust ได้รับความนิยมอย่างสูงในอุตสาหกรรมซอฟต์แวร์ ไม่ว่าจะเป็นการพัฒนาระบบ (Systems Programming) หรือ Web Assembly สำหรับผู้ที่กำลังเตรียมตัวสัมภาษณ์งานในตำแหน่งที่เกี่ยวข้องกับ Rust การเข้าใจ Ownership และ Borrowing อย่างถ่องแท้ถือเป็นสิ่งจำเป็นอย่างยิ่ง เพราะคำถามเกี่ยวกับหัวข้อนี้ปรากฏในการสัมภาษณ์เกือบทุกครั้ง > **เคล็ดลับสำหรับการสัมภาษณ์** > > ในการสัมภาษณ์งาน Rust ผู้สัมภาษณ์มักจะถามให้อธิบายความแตกต่างระหว่าง Move, Copy และ Clone รวมถึงกฎของ Borrowing หากสามารถอธิบายได้พร้อมยกตัวอย่างโค้ดประกอบ จะสร้างความประทับใจได้อย่างมาก ## Ownership คืออะไร? กฎสามข้อที่ต้องจำ ระบบ Ownership ของ Rust ตั้งอยู่บนกฎสามข้อที่เรียบง่ายแต่ทรงพลัง: 1. **ค่าทุกค่ามีเจ้าของ (Owner) เพียงหนึ่งเดียว** ในแต่ละขณะเวลา 2. **เจ้าของมีได้เพียงหนึ่งเดียวเท่านั้น** ในเวลาเดียวกัน 3. **เมื่อเจ้าของออกจาก Scope** ค่านั้นจะถูกทำลาย (Drop) โดยอัตโนมัติ กฎเหล่านี้ถูกตรวจสอบโดย Compiler ในขั้นตอนการ Compile ทำให้ไม่มี Runtime Overhead ใดเลย นี่คือเหตุผลที่ Rust สามารถรับประกันความปลอดภัยของหน่วยความจำได้โดยไม่สูญเสียประสิทธิภาพ ## Move Semantics: การโอนความเป็นเจ้าของ เมื่อกำหนดค่าจากตัวแปรหนึ่งไปยังอีกตัวแปรหนึ่ง สำหรับ Type ที่ไม่ได้ Implement `Copy` Trait ความเป็นเจ้าของจะถูกย้าย (Move) ไปยังตัวแปรใหม่ ตัวแปรเดิมจะใช้งานไม่ได้อีกต่อไป ```rust // move_semantics.rs fn main() { let original = String::from("interview prep"); let moved = original; // ownership transfers here // println!("{}", original); // compile error: value moved println!("{}", moved); // works fine } ``` ในตัวอย่างข้างต้น เมื่อ `original` ถูก Assign ให้กับ `moved` ความเป็นเจ้าของของข้อมูล String จะถูกโอนไปยัง `moved` ทั้งหมด หากพยายามใช้งาน `original` อีกครั้ง Compiler จะแจ้งข้อผิดพลาดทันที พฤติกรรมนี้แตกต่างจากภาษาอื่นอย่าง Java หรือ Python ที่การ Assign เป็นเพียงการคัดลอก Reference เท่านั้น ใน Rust การ Move เป็นการเปลี่ยนความเป็นเจ้าของอย่างแท้จริง ## Copy กับ Clone: ความแตกต่างที่สำคัญ Type พื้นฐาน เช่น `i32`, `f64`, `bool` จะ Implement `Copy` Trait โดยอัตโนมัติ ซึ่งหมายความว่าการ Assign จะเป็นการคัดลอกค่าโดยไม่มีการ Move เกิดขึ้น สำหรับ Type ที่ซับซ้อนกว่า เช่น `String` จำเป็นต้องใช้ `.clone()` เพื่อทำ Deep Copy อย่างชัดเจน ```rust // copy_vs_move.rs fn main() { let x: i32 = 42; let y = x; // copy, not move -- i32 is Copy println!("x = {}, y = {}", x, y); // both valid let s1 = String::from("hello"); let s2 = s1.clone(); // explicit deep copy println!("s1 = {}, s2 = {}", s1, s2); // both valid after clone } ``` > **Copy Trait กับ Clone Trait** > > `Copy` เป็นการคัดลอกแบบ Bitwise ที่เกิดขึ้นโดยอัตโนมัติและมีต้นทุนต่ำ ส่วน `Clone` เป็นการคัดลอกแบบ Deep Copy ที่ต้องเรียกใช้อย่างชัดเจน และอาจมีต้นทุนสูงกว่า Type ใดที่ Implement `Copy` จะต้อง Implement `Clone` ด้วยเสมอ แต่ไม่จำเป็นต้องเป็นในทิศทางตรงกันข้าม ## Immutable Borrowing: การยืมแบบอ่านอย่างเดียว การ Borrowing คือการอนุญาตให้ฟังก์ชันอื่นเข้าถึงข้อมูลโดยไม่ต้องโอนความเป็นเจ้าของ Immutable Borrow (`&T`) อนุญาตให้อ่านข้อมูลได้แต่แก้ไขไม่ได้ และสามารถมี Immutable Borrow ได้หลายตัวพร้อมกัน ```rust // immutable_borrowing.rs fn calculate_length(s: &String) -> usize { s.len() // read access through the reference } // s goes out of scope, but doesn't drop the String (not the owner) fn main() { let greeting = String::from("hello, Rust"); let len = calculate_length(&greeting); // borrow, don't move println!("'{}' has {} characters", greeting, len); // greeting still valid } ``` ในตัวอย่างนี้ ฟังก์ชัน `calculate_length` รับ Immutable Reference (`&String`) เป็นพารามิเตอร์ ทำให้สามารถอ่านค่าของ `greeting` ได้โดยไม่ต้องรับโอนความเป็นเจ้าของ เมื่อฟังก์ชันทำงานเสร็จ `greeting` ยังคงใช้งานได้ตามปกติ ## Mutable Borrowing: การยืมแบบแก้ไขได้ Mutable Borrow (`&mut T`) อนุญาตให้แก้ไขข้อมูลที่ยืมมาได้ แต่มีข้อจำกัดสำคัญคือ ในขณะใดขณะหนึ่ง สามารถมี Mutable Borrow ได้เพียงหนึ่งเดียวเท่านั้น ```rust // mutable_borrowing.rs fn append_greeting(s: &mut String) { s.push_str(", welcome to Rust!"); // modify through mutable ref } fn main() { let mut message = String::from("Hello"); append_greeting(&mut message); println!("{}", message); // "Hello, welcome to Rust!" } ``` การจำกัดให้มี Mutable Borrow เพียงหนึ่งเดียวในแต่ละขณะเวลา เป็นกลไกสำคัญที่ป้องกัน Data Race ตั้งแต่ขั้นตอนการ Compile ## กฎ Exclusivity: หนึ่ง Mutable หรือหลาย Immutable กฎนี้เป็นหัวใจของระบบ Borrowing ของ Rust ในขณะใดขณะหนึ่ง ข้อมูลสามารถมีได้เพียงอย่างใดอย่างหนึ่ง: - Mutable Reference หนึ่งตัว **หรือ** - Immutable Reference กี่ตัวก็ได้ แต่ไม่สามารถมีทั้งสองอย่างพร้อมกัน ```rust // exclusivity_rule.rs fn main() { let mut data = String::from("shared state"); let r1 = &mut data; // let r2 = &mut data; // compile error: second mutable borrow println!("{}", r1); // After r1's last usage, a new mutable borrow is allowed let r3 = &mut data; // this works -- non-lexical lifetimes r3.push_str(" updated"); println!("{}", r3); } ``` สิ่งที่น่าสนใจคือ Rust ใช้ระบบ Non-Lexical Lifetimes (NLL) ซึ่งหมายความว่า Borrow จะสิ้นสุดลงหลังจากการใช้งานครั้งสุดท้าย ไม่ใช่เมื่อออกจาก Scope ดังนั้นหลังจากที่ `r1` ถูกใช้ครั้งสุดท้ายใน `println!` แล้ว สามารถสร้าง Mutable Borrow ใหม่ (`r3`) ได้ทันที ## Lifetime Annotation: การระบุอายุของ Reference Lifetime เป็นอีกแนวคิดหนึ่งที่เกี่ยวข้องกับ Borrowing อย่างแน่นแฟ้น Lifetime Annotation ช่วยให้ Compiler ตรวจสอบได้ว่า Reference ทุกตัวยังคงชี้ไปยังข้อมูลที่ถูกต้อง (Valid) อยู่เสมอ ```rust // lifetime_annotation.rs // 'a declares: the returned reference lives as long as // the shortest-lived input reference fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let result; let string1 = String::from("Rust ownership"); { let string2 = String::from("borrowing"); result = longest(string1.as_str(), string2.as_str()); println!("Longest: {}", result); // valid: both strings alive } // println!("{}", result); // would fail: string2 dropped } ``` ในตัวอย่างนี้ `'a` เป็น Lifetime Parameter ที่บอก Compiler ว่า Reference ที่ Return กลับมาจะมีอายุเท่ากับ Reference ที่มีอายุสั้นที่สุดในพารามิเตอร์ นี่คือวิธีที่ Rust ป้องกัน Dangling Reference ได้อย่างสมบูรณ์ ## Lifetime ใน Struct เมื่อ Struct เก็บ Reference ไว้เป็นฟิลด์ จำเป็นต้องระบุ Lifetime Annotation เพื่อรับประกันว่า Struct จะไม่มีอายุยืนยาวกว่าข้อมูลที่ถูกยืมมา ```rust // struct_lifetime.rs struct Excerpt<'a> { text: &'a str, // this struct borrows a string slice } impl<'a> Excerpt<'a> { fn summary(&self) -> &str { let end = self.text.len().min(20); &self.text[..end] // return a slice of the borrowed text } } fn main() { let article = String::from("Rust ownership model eliminates memory bugs"); let excerpt = Excerpt { text: article.as_str(), }; println!("Summary: {}", excerpt.summary()); } ``` > **ข้อควรระวังเรื่อง Lifetime** > > หนึ่งในข้อผิดพลาดที่พบบ่อยในหมู่ผู้เริ่มต้นคือการพยายามคืน Reference ไปยังข้อมูลที่สร้างขึ้นภายในฟังก์ชัน ซึ่ง Compiler จะปฏิเสธเสมอเพราะข้อมูลจะถูก Drop เมื่อฟังก์ชันสิ้นสุด วิธีแก้ไขคือคืนค่าเป็น Owned Type แทน ## Design Pattern สำหรับ Ownership ในการเขียนโปรแกรม Rust จริง มีรูปแบบการจัดการ Ownership ที่ใช้กันบ่อยสามรูปแบบหลัก ซึ่งนักพัฒนาควรเลือกใช้ให้เหมาะสมกับสถานการณ์ ```rust // ownership_patterns.rs // Pattern 1: Take ownership, return a new value fn process_and_return(mut input: String) -> String { input.push_str(" -- processed"); input // ownership transfers to caller } // Pattern 2: Borrow for read-only inspection fn contains_keyword(text: &str, keyword: &str) -> bool { text.to_lowercase().contains(&keyword.to_lowercase()) } // Pattern 3: Borrow mutably for in-place modification fn sanitize(input: &mut String) { *input = input.trim().to_string(); } fn main() { // Pattern 1 let raw = String::from("user input"); let processed = process_and_return(raw); // raw is now invalid, processed owns the data // Pattern 2 let found = contains_keyword(&processed, "input"); println!("Contains 'input': {}", found); // Pattern 3 let mut padded = String::from(" spaces everywhere "); sanitize(&mut padded); println!("Sanitized: '{}'", padded); } ``` **Pattern 1** เหมาะสำหรับกรณีที่ฟังก์ชันต้องการแปลงข้อมูลและส่งคืนเจ้าของใหม่ **Pattern 2** เป็นแนวทางที่ใช้บ่อยที่สุด เหมาะกับการตรวจสอบหรืออ่านข้อมูลโดยไม่จำเป็นต้องแก้ไข **Pattern 3** ใช้เมื่อต้องการแก้ไขข้อมูลโดยตรงในตำแหน่งเดิม ## Compiler Error ที่พบบ่อยและวิธีแก้ไข การทำงานกับ Ownership และ Borrowing ย่อมเจอข้อผิดพลาดจาก Compiler อยู่เสมอ โดยเฉพาะในช่วงแรกของการเรียนรู้ ต่อไปนี้คือ Error ยอดนิยมสามประการพร้อมวิธีแก้ไข ```rust // common_fixes.rs fn main() { // Error E0502: cannot borrow as mutable because also borrowed as immutable let mut scores = vec![90, 85, 78]; // Fix: finish using the immutable borrow before mutating let first = scores[0]; // copy (i32 is Copy), no active borrow scores.push(95); // mutable borrow -- no conflict println!("First: {}, All: {:?}", first, scores); // Error E0382: use of moved value let name = String::from("Alice"); let greeting = format!("Hello, {}", name); // format! borrows, doesn't move println!("{} says {}", name, greeting); // both valid // Error E0597: borrowed value does not live long enough let outer; { let inner = String::from("temporary"); outer = inner; // move instead of borrow -- extends lifetime } println!("{}", outer); // works: outer owns the value } ``` **E0502** แก้ไขได้โดยการแยกการใช้งาน Immutable Borrow และ Mutable Borrow ออกจากกัน ให้แน่ใจว่า Immutable Borrow สิ้นสุดก่อนที่จะเริ่ม Mutable Borrow **E0382** มักเกิดจากการเข้าใจผิดว่า Macro หรือฟังก์ชันใดทำการ Move ทั้งที่จริงแล้วเป็นเพียง Borrow เช่น `format!` จะ Borrow เท่านั้น ไม่ได้ Move **E0597** แก้ไขได้โดยการ Move ข้อมูลแทนการ Borrow เพื่อขยายอายุของข้อมูลให้ครอบคลุม Scope ที่ต้องการ ## สรุป: แนวทางการเตรียมตัวสัมภาษณ์ ระบบ Ownership และ Borrowing เป็นรากฐานที่สำคัญที่สุดของ Rust และเป็นหัวข้อที่ถูกถามบ่อยที่สุดในการสัมภาษณ์งาน การทำความเข้าใจอย่างลึกซึ้งในหัวข้อต่อไปนี้จะช่วยให้ผ่านการสัมภาษณ์ได้อย่างมั่นใจ: - **กฎสามข้อของ Ownership** และเหตุผลที่ Rust ออกแบบมาเช่นนี้ - **ความแตกต่างระหว่าง Move, Copy และ Clone** รวมถึงเงื่อนไขที่แต่ละกรณีเกิดขึ้น - **กฎ Exclusivity ของ Borrowing** ที่ป้องกัน Data Race - **Lifetime Annotation** และวิธีที่ Compiler ใช้ตรวจสอบความถูกต้องของ Reference - **รูปแบบการจัดการ Ownership ที่นิยมใช้** ในโปรเจกต์จริง การฝึกฝนเขียนโค้ดจริงและอ่าน Compiler Error จะช่วยสร้างความชำนาญได้เร็วที่สุด Rust Compiler มีข้อความแจ้งข้อผิดพลาดที่ละเอียดและเป็นประโยชน์อย่างยิ่ง การใช้ประโยชน์จากข้อความเหล่านี้เป็นทักษะสำคัญที่ผู้สัมภาษณ์มักให้ความสำคัญ --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/rust/rust-ownership-borrowing-demystified