Search code examples
rustpyo3rusqlite

rusqlite and pyo3 PyResult handling errors


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?


Solution

  • 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)
    }