Result types and error handling


#1

Hi everyone,

I just started learning/using Rust and I was wondering on what I could improve upon in this piece of code, in terms of result types and error handling (mostly, other feedback is welcome too):

fn parse_markdown(path_as_string: &str) -> io::Result<String> {
    let markdown_file_contents = try!(get_file_contents(path_as_string));
    let m = Markdown::new(&markdown_file_contents);
    let mut h = Html::new(html::Flags::empty(), 0);

    match h.render(&m).to_str() {
        Ok(html) => { Ok(html.to_string()) }
        Err(_) => { Err(Error::new(ErrorKind::Other, "HTML render error")) }
    }
}

I have a few questions on this:

  1. Do I need to create a new Result type for this, or just keep it as is?
  2. Does io::Result<String> become Result<String, Error> automatically?
  3. Is there a better or shorter way to create custom errors?

Thanks.


#2
  1. You can use try!(...) instead of the last match if the error type can be converted to an io::Error. try!(...) will automatically try to convert an error type to the return error type.
  2. Yes, io::Result<T> is an alias for Result<T, io::Error>.
  3. It depends on what the error type is. You can sometimes do Err(my_error.into()), but that will only work if my_error can be converted to the type you want.

Take a look at the Into and From traits. That’s what is used to convert errors and implementing From will also enable Into on the converted type.


#3

Thanks! I will look into those traits.

Yeah, I can’t use try! because to_str() returns a type of error that cannot be converted. It has to be try!(...); and not try!(...) right (i.e. ending with semicolon)? The semicolon usage is still somewhat confusing to me.


#4

You are right, it would not work in that case, but you can probably feed the error into your io::Error:

match h.render(&m).to_str() {
    Ok(html) => Ok(html.to_string()),
    Err(e) => Err(Error::new(ErrorKind::Other, e))
}

It will just become a Box<std::error::Error> in any case.

The semicolon rules are not too complicated.

  1. A line has to end with a semicolon if it’s not the returning last line of a function or a block and if it’s not a control structure (like if, for, match etc.).

  2. The last line should not end with a semicolon if the return value of the line should be returned from the enclosing function or block. A last line with a semicolon will always return (). if and match works like expressions here, so they can return, like in your code.

  3. You can actually call macros with any of the delimiters (...), {...} and [...], but one is usually chosen based on what the macro does. A macro that is called with {...} is treated like if and match and follows the same rules. (...) and [...] will cause it to be treated as a function.

So, you would not write try!(...); as your last line, because that would always return () and your function expects io::Result<String>. I hope this didn’t confuse you even more…


#5

Hehe no, I think I got it. Thanks again! :+1: