Error handling
- Rust course
- Intermediate
- Lesson 109/115
- 6 min
Handle API errors with anyhow.
Error handling
For the user to fix a broken record, they must know where and why the error happened. The context that anyhow::Context would add in a real project, we build here with an error enum:
#[derive(Debug)]
enum KayitHatasi {
AlanSayisi { no: usize, adet: usize },
Id { no: usize, deger: String },
}
impl fmt::Display for KayitHatasi {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
KayitHatasi::AlanSayisi { no, adet } => write!(f, "kayıt {no}: 5 alan beklendi, {adet} geldi"),
KayitHatasi::Id { no, deger } => write!(f, "kayıt {no}: geçersiz id: {deger}"),
}
}
}A reader that returns errors
This time, instead of skipping a broken record, we stop at the first error; collecting into Result<Vec<_>, _> with collect does this in one line:
fn oku(veri: &str) -> Result<Vec<Yazi>, KayitHatasi> {
veri.lines()
.enumerate()
.map(|(i, s)| kayit_coz(i + 1, s))
.collect()
}Practice Report a broken record
Write KayitHatasi (with Display), kayit_coz(no, s) and oku above. Print N yazı for two valid records and the error for x|a|b|c|true.
Starter code
#[derive(Debug, Clone)]
struct Yazi {
id: u32,
konu: String,
yazar: String,
etiketler: Vec<String>,
yayinda: bool,
}
fn main() {
// buraya yaz
}Sample solution
use std::fmt;
#[derive(Debug, Clone)]
struct Yazi {
id: u32,
konu: String,
yazar: String,
etiketler: Vec<String>,
yayinda: bool,
}
#[derive(Debug)]
enum KayitHatasi {
AlanSayisi { no: usize, adet: usize },
Id { no: usize, deger: String },
}
impl fmt::Display for KayitHatasi {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
KayitHatasi::AlanSayisi { no, adet } => write!(f, "kayıt {no}: 5 alan beklendi, {adet} geldi"),
KayitHatasi::Id { no, deger } => write!(f, "kayıt {no}: geçersiz id: {deger}"),
}
}
}
fn kayit_coz(no: usize, s: &str) -> Result<Yazi, KayitHatasi> {
let alanlar: Vec<&str> = s.split('|').collect();
let [id, konu, yazar, etiket, yayinda] = alanlar[..] else {
return Err(KayitHatasi::AlanSayisi { no, adet: alanlar.len() });
};
Ok(Yazi {
id: id.parse().map_err(|_| KayitHatasi::Id { no, deger: id.to_string() })?,
konu: konu.to_string(),
yazar: yazar.to_string(),
etiketler: etiket.split(',').map(String::from).collect(),
yayinda: yayinda == "true",
})
}
fn oku(veri: &str) -> Result<Vec<Yazi>, KayitHatasi> {
veri.lines().enumerate().map(|(i, s)| kayit_coz(i + 1, s)).collect()
}
fn main() {
match oku("1|Rust ile başlangıç|ece|rust|true\n2|Sahiplik|can|rust|false") {
Ok(v) => println!("{} yazı", v.len()),
Err(e) => println!("{}", e),
}
match oku("x|a|b|c|true") {
Ok(v) => println!("{} yazı", v.len()),
Err(e) => println!("{}", e),
}
}Expected output
2 yazı kayıt 1: geçersiz id: x
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 Where is the best place to add the record number to the error?
- In the Display implementation
- Nowhere
- In the read loop iterating lines with enumerate Correct answer
- Inside main
Explanation: Only the loop knows the number, so the number is passed as a parameter to the function creating the error.
2 Numbered iteration
veri.lines()
.____()
.map(|(i, s)| kayit_coz(i + 1, s))
.collect()
veri.lines()
.enumerate()
.map(|(i, s)| kayit_coz(i + 1, s))
.collect()
Explanation: enumerate adds each line's position; used in the error.
3 Record and error
| Term | Meaning |
|---|---|
x|a|b|c|true |
invalid id: x |
1|a|b |
5 fields expected, 3 given |
(valid) |
Ok |
First error |
Reading stops |