สรุป The Rust Programming Language ฉบับมือเก่า ตอนที่ 9
บทความนี้สรุปจากหนังสือ The Rust Programming Language chapter 9 ด้วยความคิดเห็นของผมเอง อาจมีการตีความที่มีโอกาสผิดพลาดได้ ทางที่ดีถ้าอยากได้ reference จริงๆแนะนำต้นทางครับ
Error Handling
Chapter นี้พูดถึงการ handle error โดย Rust แบ่งเป็น recoverable
กับ unrecoverable
errors ซึ่งเป็นเรื่องที่ดีมากๆ เพราะทำให้เราไม่ต้องมานั่งแยกเองว่าเราควรจะ retry error แบบไหน
แน่นอนว่า unrecoverable
ก็จะ panic
ที่ runtime ส่วน recoverable
จะ return Result<T, E>
ออกมาให้
ตัวอย่าง code ที่ panic ก็อย่างเช่นเราพยายาม access element ที่ไม่มีของ array หรือ vector ด้วย indexing
เรายังสามารถ panic เองโดยเรียก panic!()
ส่วน type ของ Result ก็คือ
enum Result<T, E> {
Ok(T),
Err(E),
}
และ Result<T, E> มาพร้อมกับ prelude
ซึ่งเราก็น่าจะเดาได้แหล่ะเนอะว่าเอามา handle ด้วย match ได้แบบนี้
let a_file = match result {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {error:?}"),
};
แต่เรายังสามารถมี nested match เพื่อ handle ตาม type ของ error ได้แบบนี้ด้วย
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {e:?}"),
},
other_error => {
panic!("Problem opening the file: {other_error:?}");
}
},
};
}
หรือใช้ unwrap_or_else ซึ่งถ้า Result ไม่มี error ก็จะ return ค่าออกมาให้ แต่ถ้าเป็น error ก็จะ invoke callback ที่เรา provide ให้ด้วย error ที่มีแบบนี้
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {error:?}");
})
} else {
panic!("Problem opening the file: {error:?}");
}
});
}
หรือใช้ unwrap()
แต่คราวนี้ถ้า Result เป็น error จะ panic แทน
ถ้าเราอยาก custom ตัว panic message ก็สามารถใช้ expect(message)
แทน unwrap()
ได้
Propagating Errors
ต่อมาก็คือเรื่องของการโยน error ใน function ไปให้ caller handle
ที่จริงมันก็คล้ายๆกับ unwrap()
หรือ expect()
แต่ในกรณีนั้นมันก็การ panic ที่ caller ไม่สามารถ handle อะไรได้แล้ว
แต่สมมติว่าเราต้องการ return Error กลับออกไปให้ caller เราจะสามารถทำได้ยังไง?
คิดแบบ naive ก็คงได้โค้ดประมาณนี้
fn a_func(id: &str) -> Result<SomeType, SomeError> {
match check(&id) {
Ok(result) => Ok(&id),
Err(e) => Err(e),
}
}
ซึ่ง Rust ก็มี operator ที่ช่วยให้ชีวิตเราดีขึ้นอย่าง ?
แล้วโค้ดเราก็จะเหลือแค่นี้
fn a_func(id: &str) -> Result<SomeType, SomeError> {
check(&id)?;
}
แล้วทำงานเหมือนข้างบนเป๊ะ!
ข้อจำกัดก็คือมันทำงานได้เฉพาะใน function ที่ return Result
หรือ Option
หรือ type ที่ implement FromResidual
(แล้วของที่ใช้ ?
ด้วยก็ต้อง return type ตรงกับ function นั้นๆด้วยนะ เช่นในกรณีบน a_func
returns Result
ตัว check
ก็ต้อง returns Result
ถึงจะใช้ ?
ได้ จะปน Result
กับ Option
ไม่ได้ ไม่งั้นต้องมา match เอง)
ส่วนบทสุดท้ายก็จะเป็นการพูดถึงเรื่องว่าเมื่อไหร่เราควรจะ return Result หรือ panic
เค้าบอกว่า โดยทั่วๆไปเราอยากจะ return Result เพราะ caller สามารถเลือกเองได้ว่าจะทำยังไงต่อกับ error แต่ก็มีบางกรณีที่เราอยากจะ panic เช่น
- เราไม่อยากให้ user retry operation เพราะเหตุผลด้าน security (เค้ายกตัวอย่างการพยายาม access out of bound element ใน array เพราะสามารถไปอ่านข้อมูลของ memory นอก array นั้นได้)
- เราอยากขึ้นพวก example หรือ demo
- เราเขียน test (เพราะส่วนใหญ่เราอยาก fail fast)
- เรารู้ดีกว่า program มันไม่มีวัน error หรอก (ก็เลยไม่ต้อง return Result มา handle error ให้เสียเวลา)
อาจจะมีอย่างอื่นที่ผมอ่านข้ามไปอีก ถ้าอยากอ่านทั้งหมดก็ตามเนื้อหาในหนังสือเลยครับ
จบแล้วครับบทที่ 9