rusqlite and pyo3 PyResult handling errors

454 Views Asked by At

I'm attempting to open and write to a database in a Rust library I will call from python, with the help of pyo3. If an error occurs, I would like to raise an exception that can be caught in the calling Python process, but I'm having difficulties terminating execution and raising an error.

use rusqlite::{Connection};
use rusqlite::NO_PARAMS;
use pyo3::{Python, wrap_pyfunction};
use pyo3::exceptions::PyIOError;

#[pyfunction]
fn do_something(_py: Python) -> PyResult<u32> {
    match Connection::open("database.sql") {
        Ok(t) => conn = t,
        Err(e) => {
            let gil = Python::acquire_gil();
            let py = gil.python();
            let error_message = format!("Unable to open database! {}", e.to_string());
            PyIOError::new_err(error_message).restore(py)
        }
    };
    
    match conn.execute(
        "create table if not exists cats (
            id              INTEGER PRIMARY KEY,
            name            TEXT NOT NULL,
        )",
        NO_PARAMS,
    ) {
        Ok(_t) => (),
        Err(e) => {
            let gil = Python::acquire_gil();
            let py = gil.python();
            let error_message = format!("Unable to open database! {}", e.to_string());
            PyIOError::new_err(error_message).restore(py)
        }
    }
    Ok(0)

It's my understanding that by calling the restore function on the PyIOError object, an error would be raised, however, I must be misunderstanding because the compiler seems to consider it a possibility that conn is not initialised:

error[E0381]: borrow of possibly-uninitialized variable: `conn`

18  |             match conn.execute(
    |                   ^^^^ use of possibly-uninitialized `conn`

What would be an appropriate approach here?

1

There are 1 best solutions below

0
On BEST ANSWER

First of all, your Ok(t) = conn = t fails, as you haven't defined conn. So prior to the match add let conn;. Alternatively, you can also just assign the result of the match to conn.

Second, you still need to return an Err.

#[pyfunction]
fn do_something(_py: Python) -> PyResult<u32> {
    let conn = match Connection::open("database.sql") {
        Ok(t) => t,
        Err(e) => {
            let gil = Python::acquire_gil();
            let py = gil.python();
            let error_message = format!("Unable to open database! {}", e.to_string());
            PyIOError::new_err(error_message).restore(py);
            return Err(PyErr::fetch(py));
        }
    };

    match conn.execute(
        "create table if not exists cats (
            id              INTEGER PRIMARY KEY,
            name            TEXT NOT NULL,
        )",
        NO_PARAMS,
    ) {
        Ok(_t) => (),
        Err(e) => {
            let gil = Python::acquire_gil();
            let py = gil.python();
            let error_message = format!("Unable to open database! {}", e.to_string());
            PyIOError::new_err(error_message).restore(py);
            return Err(PyErr::fetch(py));
        }
    }
    Ok(0)
}

It's been some time since I used PyO3. But unless I remember incorrectly, then you can just remove restore() also just return the Err and let PyO3 handle the rest.

#[pyfunction]
fn do_something(_py: Python) -> PyResult<u32> {
    let conn = match Connection::open("database.sql") {
        Ok(t) => t,
        Err(e) => {
            let error_message = format!("Unable to open database! {}", e.to_string());
            return Err(PyIOError::new_err(error_message));
        }
    };

    match conn.execute(
        "create table if not exists cats (
            id              INTEGER PRIMARY KEY,
            name            TEXT NOT NULL,
        )",
        NO_PARAMS,
    ) {
        Ok(_t) => (),
        Err(e) => {
            let error_message = format!("Unable to open database! {}", e.to_string());
            return Err(PyIOError::new_err(error_message));
        }
    }
    Ok(0)
}