0

I have a function like this

fn get_html(address: &str) -> String {
    let mut response = reqwest::blocking::get(
        address,
    );

    response = response.unwrap_or_else(|_e| {String::from("")});
    response = response.text().unwrap_or_else(|_e| {String::from("")});
    return response
    }

Where I'm checking for html content. I would like to return an empty String if any kind of an error occurs somewhere in this function. I'm not sure how to deal with this because unwrap_or_else expecting Result not String.

1
  • unwrap_or_else(|_e| {String::from("")}) -> unwrap_or_default(). Commented Nov 13, 2022 at 2:05

2 Answers 2

2

The reqwest::blocking::get() function is returning a Result<Response>.

To obtain the html, you have to unwrap this result and call .text() method.

That will return a Result<String>, that you have to unwrap again.

In your code you assign a String::from("") when you unwrap the Result<Response>, and that is not right, because you have a Response when it is Ok and a String when it is an Err.

Instead you should match the result and return the String from the function:

fn get_html(address: &str) -> String {
    let mut response = reqwest::blocking::get(
        address,
    );

    match response {
        Ok(response) => response.text().unwrap_or_else(|_e| String::from("")),
        Err(_) => String::from(""),
   }
}

In this code, you use unwrap_or_else() just on the .text() result. While if you have an error on the response itself, you return a String from the function.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! This worked. Also the explanation is very clear!
0

An idiomatic way to solve your issue would be to refactor slightly your code: because code in get_html could fail, it's natural that its signature reflects this, so instead of returning a String, it could return an Option<String>, leaving the caller decide on what to do in this case:

fn get_html(address: &str) -> Option<String> {
    reqwest::blocking::get(address)
        .ok()?
        .text()
        .ok()
}

See the playground. This makes the code much more straightforward. However, you may really want the signature of the function to be -> String, not -> Option<String>. In this case, there are two solutions.

The first solution would be to use the experimental try block. I mention this solution, not because it's currently the most adequate, but because it will be one day (most likely).

fn get_html(address: &str) -> String {
    let result: Option<_> = try {
        reqwest::blocking::get(address)
            .ok()?
            .text()
            .ok()?
     };
     result.unwrap_or_default()
}

See the playground.

Note that, as is, Rust is not able to figure out types on its own for the try block, so we have to help it, which makes this more verbose. This aspect will probably improve over time, when try blocks are stabilized.

Also note that, since the Default::default constructor of String produces an empty string, we can directly use .unwrap_or_default() instead of .unwrap_or(String::new()) or .unwrap_or_else(|| String::new()) (since an empty string is not allocated, the first option is also acceptable).

The second solution would simply be to add an other function with the wanted signature, and make it use the first function

fn get_html_failable(address: &str) -> Option<String> {
    reqwest::blocking::get(address)
        .ok()?
        .text()
        .ok()
}

fn get_html(address: &str) -> String {
    get_html_failable(address).unwrap_or_default()
}

This may seem unconvenient, but lots of libraries in Rust have internal error propagation using types that represent failure (such as Result or Option), because it's really convenient to write such functions and to compose them (both reqwest::blocking::get and .text() return a Result, and see how easy it is to use them in a function that returns an Option), and have wrapper function that expose the wanted signature (most libraries will still expose fail types, but if you are writing an application it's not necessarily the best choice).

A variable of both preceding workarounds is to "simulate" a try block in stable Rust, without relying on an additional toplevel function.

fn get_html(address: &str) -> String {
    (|| reqwest::blocking::get(address).ok()?.text().ok())()
        .unwrap_or_default()
}

See the playground. Note that, in this case, countrary to the try version, Rust is able to figure out the right types on its own.

This is probably the least readable solution, so I wouldn't recommend it (use try blocks instead). But it works.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.