อธิบาย Lifetime ใน Rust: Annotation, Elision และคำถามสัมภาษณ์ 2026
คู่มือครบถ้วนเกี่ยวกับ lifetime ใน Rust สำหรับการสัมภาษณ์งาน เรียนรู้ lifetime annotation, กฎ elision, borrow checker และคำถามทางเทคนิคที่พบบ่อยพร้อมตัวอย่างโค้ดจริง

Lifetime ใน Rust คือกลไกของ compiler ในการติดตามว่า reference ยังคงใช้งานได้นานแค่ไหน ต่างจากภาษาที่ใช้ garbage collection ที่การจัดการหน่วยความจำเกิดขึ้นใน runtime นั้น borrow checker ของ Rust จะตรวจสอบความถูกต้องของ reference ในเวลา compile ซึ่งกำจัดข้อผิดพลาดเกี่ยวกับหน่วยความจำทั้งหมดก่อนที่โค้ดจะถูกรัน
Lifetime ใน Rust คือโครงสร้างในเวลา compile ที่อธิบายขอบเขตที่ reference ยังคงใช้งานได้ Borrow checker ใช้ lifetime เพื่อให้แน่ใจว่า reference จะไม่มีชีวิตยืนยาวกว่าข้อมูลที่มันชี้ไป ป้องกัน dangling pointer โดยไม่มี overhead ใน runtime
Lifetime แทนอะไรในหน่วยความจำ
Lifetime ไม่ได้เกี่ยวกับว่าค่ามีอยู่นานแค่ไหน แต่อธิบายว่า reference ไปยังค่ายังคงใช้งานได้นานเท่าไหร่ ทุก reference ใน Rust มี lifetime แม้ว่า annotation จะถูกละเว้น
พิจารณาฟังก์ชันต่อไปนี้ที่ compiler ปฏิเสธ:
fn create_dangling() -> &String {
let s = String::from("hello");
&s // ERROR: `s` is dropped at end of function
}String s มีชีวิตอยู่เฉพาะภายใน scope ของฟังก์ชัน การ return reference ไปยังมันจะสร้าง dangling pointer เนื่องจากหน่วยความจำของ String จะถูก deallocate เมื่อฟังก์ชัน return Borrow checker จับสิ่งนี้ได้ในเวลา compile
การแก้ไขต้อง return ค่าที่เป็นเจ้าของหรือให้แน่ใจว่าข้อมูลที่ถูก reference มีชีวิตยืนยาวกว่าการเรียกฟังก์ชัน:
// Option 1: Return owned value
fn create_owned() -> String {
String::from("hello")
}
// Option 2: Reference data that outlives the function
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}ใน first_word นั้น &str ที่ถูก return ยืมจาก input s ดังนั้นมันยังคงใช้งานได้ตราบเท่าที่ s ยังใช้งานได้ ผู้เรียกควบคุม lifetime ของ input
ไวยากรณ์และความหมายของ Lifetime Annotation
Lifetime annotation ที่ชัดเจนใช้ไวยากรณ์ 'a, 'b และอื่นๆ สิ่งเหล่านี้ไม่ใช่คำสั่งไปยัง compiler เกี่ยวกับว่า reference ควรมีชีวิตอยู่นานแค่ไหน แต่อธิบายความสัมพันธ์ระหว่าง lifetime ของหลาย reference
เอกสาร Rust Reference กำหนด lifetime annotation เป็นพารามิเตอร์ generic ที่จำกัดว่า reference ต้องยังคงใช้งานได้นานเท่าไหร่เมื่อเทียบกัน
// Both inputs and output share the same lifetime 'a
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("short");
result = longest(&string1, &string2);
println!("Longest: {}", result); // Valid: both strings alive
}
// println!("{}", result); // ERROR: string2 dropped
}Annotation 'a บอก compiler ว่า: reference ที่ถูก return จะใช้งานได้สำหรับ ส่วนตัดกัน ของ lifetime ของ x และ y เนื่องจาก string2 มี lifetime สั้นกว่า result จึงไม่สามารถใช้หลังจาก string2 ถูก drop
หลาย lifetime ที่แตกต่างกันแสดงความสัมพันธ์ที่ซับซ้อนกว่า:
// Output tied only to first parameter's lifetime
fn first_only<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str {
x
}
fn main() {
let owned = String::from("owned");
let result;
{
let temporary = String::from("temporary");
result = first_only(&owned, &temporary);
}
// result still valid: only depends on owned
println!("{}", result);
}กฎ Lifetime Elision ใน Rust 2024
Compiler ใช้กฎ elision สามข้อเพื่อสรุป lifetime เมื่อ annotation ถูกละเว้น กฎเหล่านี้ซึ่งบันทึกไว้ใน Rustonomicon ลด boilerplate โดยไม่เสียสละความปลอดภัย
- แต่ละ reference input ได้รับพารามิเตอร์ lifetime ของตัวเอง
- ถ้ามี input lifetime เพียงหนึ่งตัว มันจะถูกใช้กับ reference output ทั้งหมด
- ถ้า
&selfหรือ&mut selfมีอยู่ lifetime ของมันจะถูกใช้กับ reference output ทั้งหมด
กฎเหล่านี้จัดการกับ pattern ที่พบบ่อยที่สุด:
// Rule 1: Each input gets own lifetime
fn takes_two(x: &str, y: &str) {}
// Compiler reads: fn takes_two<'a, 'b>(x: &'a str, y: &'b str) {}
// Rule 2: Single input lifetime propagates to output
fn first_char(s: &str) -> &str {
&s[0..1]
}
// Compiler reads: fn first_char<'a>(s: &'a str) -> &'a str
// Rule 3: &self lifetime propagates to output
impl Parser {
fn peek(&self) -> &Token {
&self.tokens[self.position]
}
// Compiler reads: fn peek<'a>(&'a self) -> &'a Token
}เมื่อ elision ล้มเหลว compiler จะต้องการ annotation ที่ชัดเจน สิ่งนี้เกิดขึ้นบ่อยที่สุดกับฟังก์ชันที่ return reference ที่ได้มาจากหลาย input:
// ERROR: Can't determine output lifetime
fn ambiguous(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
// FIX: Explicit annotation resolves ambiguity
fn unambiguous<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Lifetime ของ Struct และความสัมพันธ์ Outlives
Struct ที่มี reference ต้องการ lifetime annotation เพื่อแสดงว่าข้อมูลที่ยืมต้องมีชีวิตยืนยาวกว่า struct:
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
// new borrows from input, so Excerpt can't outlive the source
fn new(source: &'a str, start: usize, end: usize) -> Self {
Excerpt { text: &source[start..end] }
}
// level() returns owned data, no lifetime in signature
fn level(&self) -> u32 {
self.text.len() as u32 / 10
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let excerpt = Excerpt::new(&novel, 0, 16);
println!("Excerpt: {}", excerpt.text);
} // novel dropped, but excerpt already out of scope'a ใน Excerpt<'a> ผูกความถูกต้องของ struct กับ lifetime ของ text ที่ถูกยืม การพยายามใช้ Excerpt หลังจาก String ต้นทางถูก drop จะทำให้เกิด compile error
สำหรับ struct ที่มีหลาย reference แต่ละตัวอาจมี lifetime ที่แตกต่างกัน:
struct Comparison<'a, 'b> {
baseline: &'a str,
candidate: &'b str,
}
impl<'a, 'b> Comparison<'a, 'b> {
fn new(baseline: &'a str, candidate: &'b str) -> Self {
Comparison { baseline, candidate }
}
fn baseline_only(&self) -> &'a str {
self.baseline
}
}พร้อมที่จะพิชิตการสัมภาษณ์ Rust แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
Lifetime 'static และเมื่อไหร่ควรใช้
Lifetime 'static บ่งชี้ว่าข้อมูลมีชีวิตตลอดการทำงานของโปรแกรม String literal มี lifetime นี้เพราะถูกฝังอยู่ใน binary:
let s: &'static str = "I live forever";
// Constants are implicitly 'static
const CONFIG_VERSION: &str = "2.0.0";
// Thread-safe globals require 'static
static COUNTER: AtomicU64 = AtomicU64::new(0);Pattern ที่พบบ่อยเกี่ยวข้องกับ bound T: 'static ซึ่งมักทำให้ developer สับสน Bound นี้หมายความว่า T ไม่มี reference ที่ไม่ใช่ static ไม่ใช่ว่า T ต้องเป็น reference:
use std::thread;
fn spawn_task<T: Send + 'static>(data: T) {
thread::spawn(move || {
// data moved into thread, must live independently
println!("Processing in thread");
});
}
fn main() {
let owned = String::from("owned data");
spawn_task(owned); // OK: String is 'static (owns its data)
let reference = "borrowed";
// spawn_task(reference); // OK: &'static str
}Thread อาจมีชีวิตยืนยาวกว่าฟังก์ชันที่สร้างมัน ดังนั้นข้อมูลที่ถูกย้ายเข้าไปใน thread ต้องไม่มี reference ไปยังตัวแปร stack-local
ข้อผิดพลาด Lifetime ที่พบบ่อยและวิธีแก้ไข
หลาย pattern ทำให้ developer สะดุดอย่างสม่ำเสมอ การเข้าใจสิ่งเหล่านี้ช่วยเร่งการ debug
การ return reference ไปยังตัวแปร local:
fn bad_split(text: &str, delimiter: char) -> (&str, &str) {
let parts: Vec<&str> = text.split(delimiter).collect();
(parts[0], parts[1]) // OK: parts[i] borrow from text, not from Vec
}
fn truly_bad() -> &str {
let local = String::from("local");
&local // ERROR: local dropped at end of function
}การเก็บ reference ใน struct ที่มี lifetime ไม่ตรงกัน:
struct Cache {
data: String,
// view: &str, // ERROR: needs lifetime parameter
}
// Self-referential structs require unsafe or crates like ouroboros
struct CacheWithView<'a> {
data: String,
view: Option<&'a str>, // Can't point to self.data safely
}Struct ที่อ้างอิงตัวเอง ที่ field หนึ่ง reference field อื่น ต้องการ Pin กับ unsafe code หรือ crate เช่น ouroboros โมดูล Rust smart pointers ครอบคลุม pattern ขั้นสูงเหล่านี้
Lifetime bound บน trait implementation:
trait Processor {
fn process<'a>(&self, input: &'a str) -> &'a str;
}
struct Prefixer {
prefix: String,
}
impl Processor for Prefixer {
// Can't return &format!(...) - would be temporary
fn process<'a>(&self, input: &'a str) -> &'a str {
input // Must return input or part of it
}
}Higher-Ranked Trait Bounds (HRTBs) สำหรับ Lifetime Generic
Higher-ranked trait bounds ใช้ไวยากรณ์ for<'a> เพื่อแสดงว่า type ต้องทำตาม trait สำหรับ lifetime ใดก็ได้ ไม่ใช่แค่ตัวเฉพาะ:
use std::fmt::Debug;
// F must be callable with any lifetime 'a
fn apply_to_refs<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let owned = String::from("test");
let result = f(&owned);
println!("{}", result);
}
fn identity(s: &str) -> &str { s }
fn main() {
apply_to_refs(identity);
}HRTBs ปรากฏบ่อยใน API ที่รับ closure และ ecosystem Rust async/await ที่ future ต้องทำงานกับ reference ของ lifetime ที่หลากหลาย
คำถามสัมภาษณ์เกี่ยวกับ Lifetime ใน Rust
การสัมภาษณ์ทางเทคนิคทดสอบความเข้าใจเกี่ยวกับ lifetime ในหลายระดับความลึก คำถามเหล่านี้ปรากฏเป็นประจำในตำแหน่ง Rust
คำถาม: ทำไมโค้ดนี้ compile ไม่ได้?
fn get_str() -> &str {
"hello"
}คำตอบ: Return type ต้องการ lifetime annotation ที่ชัดเจน แม้ว่า string literal จะมี lifetime 'static แต่ function signature ไม่ได้แสดงสิ่งนี้ วิธีแก้: fn get_str() -> &'static str
คำถาม: อธิบายว่าทำไมสิ่งนี้ compile ได้และมันปลอดภัยหรือไม่:
fn longest<'a>(x: &'a str, _y: &str) -> &'a str {
x
}คำตอบ: สิ่งนี้ compile ได้เพราะ output lifetime ผูกกับ x เท่านั้น พารามิเตอร์ _y สามารถมี lifetime ใดก็ได้เนื่องจากค่าที่ return ไม่ได้ขึ้นอยู่กับมัน สิ่งนี้ปลอดภัย: ความถูกต้องของ reference ที่ return ขึ้นอยู่กับ lifetime ของ x เท่านั้น
คำถาม: เกิดอะไรขึ้นเมื่อพยายามเก็บ reference พร้อมกับข้อมูลที่เป็นเจ้าของ?
struct Config<'a> {
name: String,
description: &'a str,
}คำตอบ: Pattern นี้ถูกต้องแต่จำกัดวิธีที่ Config สามารถใช้ได้ Struct ไม่สามารถมีชีวิตยืนยาวกว่าสิ่งที่ description reference สำหรับข้อมูลที่เป็นเจ้าของที่ต้อง reference ตัวเอง ให้พิจารณาใช้ String สำหรับทั้งสอง field หรือเทคนิคที่กล่าวถึงใน Rust ownership and borrowing
คำถาม: Lifetime ทำงานอย่างไรกับ trait object?
trait Formatter {
fn format(&self, input: &str) -> String;
}
fn get_formatter<'a>() -> Box<dyn Formatter + 'a> {
// ...
}คำตอบ: Trait object มี bound lifetime 'static โดยนัยเป็นค่าเริ่มต้น การเขียน Box<dyn Formatter + 'a> อย่างชัดเจนอนุญาตให้ trait object มี reference ที่มี lifetime 'a โดยไม่มี bound ที่ชัดเจน Box<dyn Formatter> เท่ากับ Box<dyn Formatter + 'static>
Lifetime Variance: Covariance และ Contravariance
Variance กำหนดว่า lifetime สัมพันธ์กันอย่างไรเมื่อ type ซ้อนกัน Reference ใน Rust ปฏิบัติตามกฎเหล่านี้:
&'a Tเป็น covariant ใน'a: lifetime ที่ยาวกว่าสามารถแทนที่สั้นกว่าได้&'a mut Tเป็น invariant ในT: type ต้องตรงกันพอดีfn(&'a T)เป็น contravariant ใน'a: lifetime ที่สั้นกว่าสามารถแทนที่ยาวกว่าได้
fn covariant_example() {
let s: &'static str = "static";
let r: &str = s; // OK: 'static lives longer than any 'a
}
fn invariant_example() {
let mut vec: Vec<&'static str> = vec!["a"];
// let s = String::from("local");
// vec.push(&s); // ERROR: &s is not &'static str
}การเข้าใจ variance สำคัญเมื่อออกแบบ API generic ที่รับหรือ return reference บทความ Rust traits and generics ครอบคลุม pattern generic ขั้นสูง
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
Lifetime Annotation ในทางปฏิบัติ: ประเด็นหลัก
- Lifetime อธิบายความถูกต้องของ reference ไม่ใช่การมีอยู่ของค่า Borrow checker ใช้มันเพื่อป้องกัน dangling pointer ในเวลา compile
- กฎ elision จัดการกรณีส่วนใหญ่โดยอัตโนมัติ Annotation ที่ชัดเจนจำเป็นเมื่อ return reference ที่ได้มาจากหลาย input
- Lifetime ของ struct แสดงความสัมพันธ์ outlives: struct ใดก็ตามที่มี reference ต้องถูก parameterize ด้วย lifetime ของ reference เหล่านั้น
- Bound
'staticบน generic หมายถึง "ไม่มี reference ที่ไม่ใช่ static" ไม่ใช่ "ต้องเป็น reference" - Struct ที่อ้างอิงตัวเองต้องการการจัดการพิเศษผ่าน
Pin, unsafe code หรือ crate ช่วยเหลือ - คำถามสัมภาษณ์มุ่งเน้นที่การเข้าใจว่าทำไมโค้ด compile ไม่ได้และ lifetime annotation เปลี่ยนการรับประกันความถูกต้องอย่างไร
คุณหาบั๊กใน Rust เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

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

Thinkful vs Bloc สำหรับการเรียน Rust ปี 2026: เปรียบเทียบ Bootcamp และคู่มือการเรียนด้วยตัวเอง
เปรียบเทียบ Thinkful vs Bloc สำหรับเรียน Rust ปี 2026 วิเคราะห์หลักสูตร bootcamp ค่าใช้จ่าย และคู่มือเรียน Rust ด้วยตัวเองพร้อมทรัพยากรฟรีคุณภาพสูง

Rust และ SQLx ในปี 2026: การตรวจสอบ Query ขณะ Compile และคำถามสัมภาษณ์งาน
เรียนรู้วิธีที่ SQLx 0.9 ตรวจสอบ SQL query ขณะ compile, การกำหนดค่า sqlx.toml และเตรียมตัวสัมภาษณ์งานตำแหน่ง backend Rust

เรียน Rust 2026: เปรียบเทียบ Bootcamp และแหล่งเรียนรู้ด้วยตนเอง
คู่มือครบถ้วนเปรียบเทียบ bootcamp Rust เช่น RareSkills และ Rustify กับแหล่งเรียนรู้ฟรีสำหรับนักพัฒนาในปี 2026