อธิบาย Lifetime ใน Rust: Annotation, Elision และคำถามสัมภาษณ์ 2026

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

อธิบาย Lifetime ใน Rust: Annotation, Elision และคำถามสัมภาษณ์ 2026

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 ปฏิเสธ:

dangling_reference.rsrust
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 มีชีวิตยืนยาวกว่าการเรียกฟังก์ชัน:

valid_return.rsrust
// 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 ต้องยังคงใช้งานได้นานเท่าไหร่เมื่อเทียบกัน

lifetime_annotations.rsrust
// 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 ที่แตกต่างกันแสดงความสัมพันธ์ที่ซับซ้อนกว่า:

multiple_lifetimes.rsrust
// 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 โดยไม่เสียสละความปลอดภัย

กฎ Elision สามข้อ
  1. แต่ละ reference input ได้รับพารามิเตอร์ lifetime ของตัวเอง
  2. ถ้ามี input lifetime เพียงหนึ่งตัว มันจะถูกใช้กับ reference output ทั้งหมด
  3. ถ้า &self หรือ &mut self มีอยู่ lifetime ของมันจะถูกใช้กับ reference output ทั้งหมด

กฎเหล่านี้จัดการกับ pattern ที่พบบ่อยที่สุด:

elision_examples.rsrust
// 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:

elision_fails.rsrust
// 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_lifetimes.rsrust
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 ที่แตกต่างกัน:

multiple_struct_lifetimes.rsrust
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:

static_lifetime.rsrust
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:

static_bound.rsrust
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:

error_local_ref.rsrust
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 ไม่ตรงกัน:

error_struct_lifetime.rsrust
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_lifetime_bounds.rsrust
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 ใดก็ได้ ไม่ใช่แค่ตัวเฉพาะ:

hrtb.rsrust
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 ไม่ได้?

interview_q1.rsrust
fn get_str() -> &str {
    "hello"
}

คำตอบ: Return type ต้องการ lifetime annotation ที่ชัดเจน แม้ว่า string literal จะมี lifetime 'static แต่ function signature ไม่ได้แสดงสิ่งนี้ วิธีแก้: fn get_str() -> &'static str

คำถาม: อธิบายว่าทำไมสิ่งนี้ compile ได้และมันปลอดภัยหรือไม่:

interview_q2.rsrust
fn longest<'a>(x: &'a str, _y: &str) -> &'a str {
    x
}

คำตอบ: สิ่งนี้ compile ได้เพราะ output lifetime ผูกกับ x เท่านั้น พารามิเตอร์ _y สามารถมี lifetime ใดก็ได้เนื่องจากค่าที่ return ไม่ได้ขึ้นอยู่กับมัน สิ่งนี้ปลอดภัย: ความถูกต้องของ reference ที่ return ขึ้นอยู่กับ lifetime ของ x เท่านั้น

คำถาม: เกิดอะไรขึ้นเมื่อพยายามเก็บ reference พร้อมกับข้อมูลที่เป็นเจ้าของ?

interview_q3.rsrust
struct Config<'a> {
    name: String,
    description: &'a str,
}

คำตอบ: Pattern นี้ถูกต้องแต่จำกัดวิธีที่ Config สามารถใช้ได้ Struct ไม่สามารถมีชีวิตยืนยาวกว่าสิ่งที่ description reference สำหรับข้อมูลที่เป็นเจ้าของที่ต้อง reference ตัวเอง ให้พิจารณาใช้ String สำหรับทั้งสอง field หรือเทคนิคที่กล่าวถึงใน Rust ownership and borrowing

คำถาม: Lifetime ทำงานอย่างไรกับ trait object?

interview_q4.rsrust
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 ที่สั้นกว่าสามารถแทนที่ยาวกว่าได้
variance.rsrust
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

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 27 สิงหาคม 2569

แชร์

บทความที่เกี่ยวข้อง