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! ในโปรแกรมง่าย ๆ:
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
fn main() {
let v = vec![1, 2, 3];
v[99];
}
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.
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