11

I want to write a function that reads the contents of a file, and raises an error if it fails. I want to call this function from a python script, so I'm including some mentions of Python below in case it might be relevant.

As I have tried showing in the comments, more work might happen that raises other types of errors, so if it is possible I would like to use a generic error if this is possible in Rust(?). How can I return the error so it can be handled and wrapped in a python error as shown in do_work? Not sure if my approach that is resulting in the error below is in the right direction.

fn work_with_text() -> Result<(), dyn std::error::Error> {
    let content = match std::fs::read_to_string("text.txt") {
        Ok(t) => t,
        Err(e) => return Err(e),
    };
    // do something with content that may cause another type of error (rusqlite error)
    Ok(())
}
    

#[pyfunction]
fn do_work(_py: Python) -> PyResult<u32> {
    match work_with_text() {
        Ok(_) => (0),
        Err(e) => {
            let gil = Python::acquire_gil();
            let py = gil.python();
            let error_message = format!("Error happened {}", e.to_string());
            PyIOError::new_err(error_message).restore(py);
            return Err(PyErr::fetch(py));
        }
    };

    // ...
}

error:

1   | ...                   fn work_with_text() -> Result<(), dyn std::error::Error> {
    |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `(dyn std::error::Error + 'static)`
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
Simen Russnes
  • 2,002
  • 2
  • 26
  • 56
  • 3
  • Is there a particular reason, you want to keep the return type as `Result<(), dyn std::error::Error>` instead of `Result<(), std::io::Error>` or just `std::io::Result<()>`? – vallentin Jan 22 '21 at 12:55
  • @vallentin because I might have other types of errors as well, that I don't think would be covered by std::io. For example rusqlite errors – Simen Russnes Jan 22 '21 at 12:56
  • 3
    @SimenRussnes In that case, it would be more idiomatic to make your own `enum Error` with variants for each kind of error. Instead of `dyn std::error::Error`. – vallentin Jan 22 '21 at 12:57
  • @vallentin really depends whether it's a library or an application, and what's later done with the error, though. `Box` is usually fine in an application, or here if they only raise a very generic exception at the python level. – Masklinn Jan 22 '21 at 13:04
  • 1
    Also FWIW your `match` is a complicated way of writing `let content = read_to_string(...)?;`, and the later will take care of conversions if available & necessary. – Masklinn Jan 22 '21 at 13:05
  • @Masklinn To each their own of course. Personally, I never use `Box`, regardless of library vs application. Every time I've used `Box`, I've in the future had to rework code, when I needed to handle the individual error kinds. Dealing with `Box` can be quite annoying. – vallentin Jan 22 '21 at 13:11

1 Answers1

12

Your current version does not work because trait objects don't have a statically known size, which means the compiler doesn't know how much space to allocate for them on the stack, so you can't use unsized types as function arguments or return values unless you make them sized by putting them behind a pointer.

Fixed example:

fn work_with_text() -> Result<(), Box<dyn std::error::Error>> {
    let content = std::fs::read_to_string("text.txt")?;
    // do something with content that may cause another type of error (rusqlite error)
    Ok(())
}

Having a Box<dyn std::error::Error> also allows you to return a wide variety of errors from your function since most error types can be automatically converted into a Box<dyn std::error::Error> via the ? operator.

If you'd like to get a deeper understanding of both sizedness and error handling in Rust I highly recommend reading Sizedness in Rust and Error Handling in Rust.

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98