Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Unrecoverable Error ด้วย panic!

บางครั้งสิ่งร้ายเกิดในโค้ดของคุณ และไม่มีอะไรคุณทำได้เกี่ยวกับมัน ใน กรณีเหล่านี้ Rust มี macro panic! มีสองวิธีที่ทำให้เกิด panic ในทาง ปฏิบัติ — โดยทำ action ที่ทำให้โค้ดเรา panic (เช่นเข้าถึง array หลังท้าย) หรือเรียก macro panic! แบบ explicit ในทั้งสองกรณี เราทำให้เกิด panic ในโปรแกรมของเรา โดย default panic เหล่านี้จะพิมพ์ failure message, unwind, cleanup stack และออก ผ่าน environment variable คุณยังให้ Rust แสดง call stack เมื่อ panic เกิดได้ เพื่อทำให้ง่ายขึ้นในการตามหาแหล่งของ panic

Unwind Stack หรือ Abort เมื่อเกิด Panic

โดย default เมื่อ panic เกิด โปรแกรมเริ่ม unwind ซึ่งหมายความว่า Rust เดินกลับขึ้น stack และ cleanup ข้อมูลจากแต่ละฟังก์ชันที่มันเจอ อย่างไรก็ตาม การเดินกลับและ cleanup เป็นงานเยอะ Rust จึงให้คุณเลือก ทางเลือกของการ abort ทันที ซึ่งจบโปรแกรมโดยไม่ cleanup

หน่วยความจำที่โปรแกรมใช้จะต้องถูก cleanup โดย OS ถ้าในโปรเจกต์ของคุณ ต้องทำให้ binary ผลลัพธ์เล็กที่สุดเท่าที่ทำได้ คุณเปลี่ยนจาก unwind เป็น abort เมื่อ panic ได้ โดยเพิ่ม panic = 'abort' เข้าใน section [profile] ที่เหมาะสมในไฟล์ Cargo.toml ของคุณ เช่น ถ้าคุณอยาก abort ตอน panic ใน release mode เพิ่มนี้:

[profile.release]
panic = 'abort'

ลองเรียก panic! ในโปรแกรมง่าย ๆ:

Filename: src/main.rs
fn main() {
    panic!("crash and burn");
}

เมื่อคุณรันโปรแกรม คุณจะเห็นสิ่งคล้ายนี้:

$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/panic`

thread 'main' panicked at src/main.rs:2:5:
crash and burn
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

การเรียก panic! ทำให้เกิด error message ที่มีในสองบรรทัดสุดท้าย บรรทัด แรกแสดง panic message ของเราและตำแหน่งใน source code ที่ panic เกิด — src/main.rs:2:5 บ่งบอกว่ามันคือบรรทัดที่ 2 อักขระที่ 5 ของไฟล์ src/main.rs ของเรา

ในกรณีนี้ บรรทัดที่ระบุเป็นส่วนของโค้ดเรา และถ้าเราไปที่บรรทัดนั้น เราเห็น การเรียก panic! macro ในกรณีอื่น การเรียก panic! อาจอยู่ในโค้ดที่โค้ด เราเรียก และชื่อไฟล์และเลขบรรทัดที่ error message รายงาน จะเป็นโค้ดของคน อื่นที่ panic! macro ถูกเรียก ไม่ใช่บรรทัดของโค้ดเราที่นำไปสู่การเรียก panic! ในที่สุด

เราใช้ backtrace ของฟังก์ชันที่การเรียก panic! มาจาก เพื่อหาส่วนของ โค้ดเราที่เป็นต้นเหตุได้ ในการเข้าใจวิธีใช้ backtrace ของ panic! มาดู ตัวอย่างอีกตัวอย่างหนึ่ง และดูว่าเป็นอย่างไรเมื่อ panic! มาจาก library เพราะ bug ในโค้ดเรา แทนที่โค้ดของเราเรียก macro ตรง ๆ Listing 9-1 มี โค้ดที่พยายามเข้าถึง index ใน vector นอก range ของ index ที่ valid

Filename: src/main.rs
fn main() {
    let v = vec![1, 2, 3];

    v[99];
}
Listing 9-1: พยายามเข้าถึง element หลังท้าย vector ซึ่งจะทำให้เกิดการเรียก panic!

ที่นี่ เรากำลังพยายามเข้าถึง element ที่ 100 ของ vector (ที่ index 99 เพราะ indexing เริ่มที่ศูนย์) แต่ vector มีแค่สาม element ในสถานการณ์นี้ Rust จะ panic การใช้ [] ควรจะ return element แต่ถ้าคุณส่ง index invalid ไม่มี element ที่ Rust ใน return ที่นี่ที่ถูกต้อง

ใน C การพยายามอ่านหลังท้ายของโครงสร้างข้อมูลเป็น undefined behavior คุณ อาจได้อะไรก็ตามที่อยู่ในตำแหน่งหน่วยความจำที่จะสอดคล้องกับ element นั้น ในโครงสร้างข้อมูล แม้หน่วยความจำจะไม่เป็นของโครงสร้างนั้น นี่เรียกว่า buffer overread และนำไปสู่ security vulnerability ได้ ถ้า attacker จัดการ index ในแบบที่อ่านข้อมูลที่ไม่ควรได้รับอนุญาตที่เก็บหลังโครงสร้าง ข้อมูล

เพื่อปกป้องโปรแกรมของคุณจาก vulnerability ชนิดนี้ ถ้าคุณลองอ่าน element ที่ index ไม่มี Rust จะหยุด execution และปฏิเสธที่จะดำเนินต่อ ลองดู:

$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/panic`

thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Error นี้ชี้ที่บรรทัดที่ 4 ของ main.rs ของเรา ที่เราพยายามเข้าถึง index 99 ของ vector ใน v

บรรทัด note: บอกว่าเราตั้ง environment variable RUST_BACKTRACE เพื่อ รับ backtrace ของสิ่งที่เกิดขึ้นเป๊ะ ๆ ที่ทำให้เกิด error ได้ backtrace คือ list ของฟังก์ชันทั้งหมดที่ถูกเรียกเพื่อมาถึงจุดนี้ Backtrace ใน Rust ทำงานเหมือนในภาษาอื่น — กุญแจของการอ่าน backtrace คือเริ่มจากด้านบนและ อ่านจนคุณเห็นไฟล์ที่คุณเขียน นั่นคือจุดที่ปัญหาเริ่ม บรรทัดเหนือจุดนั้น คือโค้ดที่โค้ดของคุณเรียก บรรทัดข้างใต้คือโค้ดที่เรียกโค้ดของคุณ บรรทัด ก่อน-หลังเหล่านี้อาจรวมโค้ดแกน Rust, โค้ด standard library หรือ crate ที่คุณใช้ ลองรับ backtrace โดย set environment variable RUST_BACKTRACE เป็นค่าใด ๆ ยกเว้น 0 Listing 9-2 แสดง output คล้ายกับที่คุณจะเห็น

$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
stack backtrace:
   0: rust_begin_unwind
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/std/src/panicking.rs:692:5
   1: core::panicking::panic_fmt
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:75:14
   2: core::panicking::panic_bounds_check
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:273:5
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:274:10
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:16:9
   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
             at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3361:9
   6: panic::main
             at ./src/main.rs:4:6
   7: core::ops::function::FnOnce::call_once
             at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Listing 9-2: backtrace ที่ generate โดยการเรียก panic! แสดงเมื่อ environment variable RUST_BACKTRACE ถูกตั้ง

นั่นเป็น output เยอะ! output ที่คุณเห็นอาจต่างกันขึ้นกับ OS และ Rust version ในการรับ backtrace พร้อมข้อมูลนี้ debug symbol ต้องเปิด Debug symbol เปิดโดย default เมื่อใช้ cargo build หรือ cargo run โดยไม่มี flag --release อย่างที่เราทำที่นี่

ใน output ใน Listing 9-2 บรรทัดที่ 6 ของ backtrace ชี้ที่บรรทัดในโปรเจกต์ เราที่เป็นต้นเหตุ — บรรทัดที่ 4 ของ src/main.rs ถ้าเราไม่อยากให้ โปรแกรม panic เราควรเริ่มการสืบสวนที่ตำแหน่งที่ชี้โดยบรรทัดแรกที่กล่าวถึง ไฟล์ที่เราเขียน ใน Listing 9-1 ที่เราเขียนโค้ดตั้งใจให้ panic วิธีแก้ panic คือไม่ขอ element นอก range ของ index ของ vector เมื่อโค้ดของคุณ panic ในอนาคต คุณจะต้องคิดว่าโค้ดกำลังทำ action อะไรกับค่าอะไรที่ทำให้ panic และโค้ดควรทำอะไรแทน

เราจะกลับไปที่ panic! และเมื่อเราควรและไม่ควรใช้ panic! จัดการเงื่อนไข error ในส่วน panic! หรือไม่ panic! ทีหลังในบทนี้ ถัดไป เราจะดูวิธี recover จาก error โดยใช้ Result