Hi! I’m trying to learn Rust, as a little project, I’m trying to build a web scraper that will scrape some content and rebuild it with a static site generator, or using it for making POST requests.
I’m still at a very early stage and I still don’t know much, the simplest error handling strategy I know is using match
with Result
.
To my eyes, this syntax looks correct, but also looks kind of a lot of lines for a simple http request.
I know the reqwest docs suggest to handle errors with the ?
operator, which I don’t know yet, therefore I’m just using what I know now.
fn get_document(permalink: String) -> Html {
let html_content_result = reqwest::blocking::get(&permalink);
let html_content = match html_content_result {
Ok(response) => response,
Err(error) => panic!("There was an error making the request: {:?}", error),
};
let html_content_text_result = html_content.text();
let html_content_text = match html_content_text_result {
Ok(text) => text,
Err(error) =>
panic!(
"There was an error getting the html text from the content of response: :{:?}",
error
),
};
let document = Html::parse_document(&html_content_text);
document
}
As for my understanding, this is what I’m doing here:
I’m making an http request, if i get a Response
, I try to get the text out of the response body, otherwise I handle the error by panicking with a custom message.
Getting the text out of the request body is another passage that requires error handling, therefore I use the match expression again to get the text out and handle the possible error (In what circumstances can extracting the text of a response body fail?).
Then I can finally parse the document and return it!
I wonder if it is a correct and understandable way of doing what I’ve in mind.
Do you think this would be a suitable project for someone who is at chapter 7 of the Rust book? I feel like i actually need to build somethiong before keep going with the theory!
This will work in general. One point of improvement: right now, if the request fails, the panic will cause your whole program to crash. You could change your function to return a
Result<Html, SomeErrorType>
instead, and handle errors more gracefully in the place where your function is called (e.g. ignoring pages that returned an error and continuing with the rest).Look into anyhow for an easy to use error handling crate, allowing you to return an
anyhow::Result<Html>
They did say they haven’t learned the ? operator (that’s chapter 9 of the rust book), so this approach might be better for once they get there.
For someone only on chapter 7, this is ok. I would not call it idiomatic but you have not gotten to the Error Handling in chapter 9 yet. I would probably hold on getting feedback on error handling until you have gotten to that point.
But the TLDR of it is rust has two forms of errors, unrecoverable errors in the form of panic and recoverable ones in the form of returning a
Result
. In this case you have opted for panicking which IMO is the wrong choice for something that is expected to fail - and http requests and parsing external data is expected to fail (even if only some of the time). Networks fail all the time, servers go down, send back wrong responses and many other things.Do you really want to crash your program every time that happens? Probably not - at least not at this level. Instead you likely want to return an error from this function and let the caller deal with it instead as they will likely have more context as to what to do with it rather than in the leaf functions of where the error originates.
But all that is probably for once you have read through chapter 9. For now it is good to know that when you have the pattern
match foo { Ok(value) => value, Err(err) => panic!("it broke! {}", err), }
You can generally replace that with a call to expect instead:
foo.expect("it broke")
Or just unwrap it if you dont need to add more context for what ever reason.
unrecoverable errors in the form of panic
ahem
You can use expect on Options and Results to extract the value and panic on Error in the same line (https://doc.rust-lang.org/std/result/enum.Result.html#method.expect).
let html_content = reqwest::blocking::get(&permalink).expect("the request should succeed");
You can also use unwrap if you don’t need a custom message. The ? operator is definitely the most compact way of handling errors, and for good reason because the rust developers want people to use it. Once you learn that the code will become somewhat smaller.