Rusqlite Insert & Rust Match - problem assigning Err to Result enum

Have written a function to insert a vector of records into an SQLite database. I employ the match statement to test if each insert worked. If there is an error I have an Err option. In it I thought I could print a message, assign Err(e) to the data_inserted variable, then break to exit the a loop and return to main with the error contained in the returned Result via the "return data_inserted" line. But I get an error on Err(e) that says type annotation needed, cannot infer type of type parameter T declared on the enum Result. Consider specifying the generic arguments ::,T, rusqlite::Error>.
I do not understand how to apply the error message so I commented out the two lines and inserted return Err(e) and that compiles. So two questions. 1) Why don't the commented out two lines work? 2) With "return Err(e)" in place, if there is an error, I think the for loop and function end but in the process is the error assigned to the data_inserted variable and is that Result enum what's returned to main or is something else happening?
Below is the function.

use crate::structures::structures::Record;
use rusqlite::{Connection, Result, named_params};

pub fn insert_records(conn: &Connection, records: &mut Vec<Record>) -> Result<(), rusqlite::Error> {

    let mut data_inserted: Result<(), rusqlite::Error> = Ok(());

    for record in records {

        data_inserted = match conn.execute(
            "INSERT INTO data (date, open, high, low, close, volume) 
            VALUES (:date, :open, :high, :low, :close, :volume)",
            named_params! {
                ":date": &record.date,
                ":open": &record.open,
                ":high": &record.high,
                ":low": &record.low,
                ":close": &record.close,
                ":volume": &record.volume,
            },
        ){
            rusqlite::Result::Ok(_) => {
                eprintln!("Record added successfully");
                Ok(())
            }
            rusqlite::Result::Err(e) => {
                eprintln!("Error adding record: {}", e);
                return Err(e);
                // Err(e);
                // break
            }
        };
    } // end for loop

    return data_inserted

}
    let mut data_inserted: Result<(), rusqlite::Error> = Ok(());
    for record in records {
        data_inserted = match conn.execute(...) {
            // ...
            rusqlite::Result::Err(e) => {
                Err(e);
                break
            }
        };
    } // end for loop

    return data_inserted

This just creates a Result::<?, rusqlite::Error> and immediately drops it, then breaks the loop. data_inserted is not modified and the Err(e) you create and drop doesn't have to have any relationship to data_inserted or the return type, so the compiler doesn't have anyway to tell what the ? should be.

This probably fixes it:

             // ...
             rusqlite::Result::Err(e) => {
-                Err(e);
+                data_inserted = Err(e);
                 break
             }
         };
     } // end for loop

-    return data_inserted
+    // style nit
+    data_inserted

(But see below.)

data_inserted is not modified, and you return out of the function on the return Err(e) line directly. With the code you've shared, there is no practical difference. I would use return Err(e) as the break version implies there's something more to do within this function if we get to that point, where as return Err(e) lets me know there is not.

1 Like

Thank you for the response. I am starting to think that my confusion is with the match statement and how to implement it. The function below compiles and I thought that in this case if the result is successful, Ok is assigned to table_creation but if there is an error, Err(e) is assigned to table_creation and table_creation is returned by the function. This function is almost identical to the inserting records function.

use rusqlite::{Connection, Result};

pub fn create_table(conn: &Connection) -> Result<(), rusqlite::Error> {

   let table_creation: Result<(), rusqlite::Error> = match conn.execute (
        "CREATE TABLE IF NOT EXISTS data (
            date TEXT NOT NULL PRIMARY KEY,
            open REAL NOT NULL,
            high REAL NOT NULL,
            low REAL NOT NULL,
            close REAL NOT NULL,
            volume INTEGER NOT NULL
        )",
        [],
    ){
        Ok(_) => { 
            eprintln!("Table created successfully");
            Ok(())
        },
        Err(e) => {
            eprintln!("Error creating table: {}", e);
            Err(e)
        }
    };

    return table_creation

}

I'm going to start with some basics -- probably the beginning things you understand already, so just bear with me if so.

Many control flow blocks in Rust are also expressions -- they evaluate to some value you can assign or otherwise use -- and match is one of those. Some examples:

let mut number: i32;

// All the arms (after `=> ...`) are `i32`s and so the `match` as a whole
// is an `i32`.  (The arms always have to have the same type.)
number = match boolean {
    true => 10,
    false => 30,
};

// Same with `if`/`else` (you need the `else`)
number = if boolean { 10 } else { 30 };

// Same with bare blocks within a function.
number = { println!("hi!"); 42 };

// Same with the function block itself!
fn foo() -> i32 { 67 }
number = foo();

And that's what's going on with your latest example:

let table_creation: Result<(), rusqlite::Error> = match conn.execute(...) {
    Ok(_) => { 
        eprintln!("Table created successfully");
        Ok(())
    },
    Err(e) => {
        eprintln!("Error creating table: {}", e);
        Err(e)
    }
};

Both the arms are blocks that end with Result<(), rusqlite::Error> and so the match as a whole is also Result<(), rusqlite::Error>, and you assign it to table_creation.


With the basics covered, let's look at what exactly was different in the previous version.

data_inserted = match conn.execute(...) {
    rusqlite::Result::Ok(_) => {
        eprintln!("Record added successfully");
        Ok(()) // Result::<(), rusqlite::Error>
    }
    rusqlite::Result::Err(e) => {
         eprintln!("Error creating table: {}", e);
         Err(e); // This is not the last expression of the block, it's
                 // a statement ending with `;` in the block, and it just
                 // drops the `Result` (if you annotate it so it compiles).

         break   // This is the last expression and it has type `!`
    }
};

The Err(e) doesn't end up assigned to data_inserted because it never makes it out of the block. Or perhaps think of it as, the order of operations is wrong: you would need the assignment to happen and then the break to happen. But the break happens before the block ends, which is when the assignment would happen.

If you annotated things so they compiled, you would break the loop early, but lose the error.

Now, what's this ! type I mentioned? It's the "never" type, and as far as control flow goes, it indicates some part of the control flow that cannot be reached. Execution never actually makes it to the end of the second arm. Once the break is reached, control flow jumps elsewhere (out of the for loop).

! has special coercion powers where it can coerce to any other type, so you can do things like this:

fn example(operation: &str) -> Result<i32, String> {
    let outcome = match operation {
        "do this" => { ... }
        "do that" => { ... }
        // `return` also has type `!`
        unknown => return Err(format!("unknown operation {unknown:?}")),
    };
    
    Ok(outcome)
}

The end of the last arm is never reached -- that's what ! means here -- so it's okay that there is no actual i32 value there. From a type perspective, ! coerces to i32.


This would do more what you intended, because we break after the assignment:

data_inserted = match conn.execute(...) {
    rusqlite::Result::Ok(_) => {
        eprintln!("Record added successfully");
        Ok(())
    }
    rusqlite::Result::Err(e) => {
         eprintln!("Error creating table: {}", e);
         Err(e)
    }
};

if data_inserted.is_err() {
    break; 
}

But I still think either of these are cleaner:

data_inserted = match conn.execute(...) {
    rusqlite::Result::Ok(_) => {
        eprintln!("Record added successfully");
        Ok(())
    }
    rusqlite::Result::Err(e) => {
        eprintln!("Error creating table: {}", e);
        return Err(e);
    }
};
match conn.execute(...) {
    rusqlite::Result::Ok(_) => {
        eprintln!("Record added successfully");
        data_inserted = Ok(());
    }
    rusqlite::Result::Err(e) => {
        eprintln!("Error creating table: {}", e);
        return Err(e);
    }
}

And note now that we only ever assign data_inserted = Ok(()), so you don't really need it in a variable. (Something I didn't notice in my previous reply.)

for record in records {
    match conn.execute(...) {
        rusqlite::Result::Ok(_) => {
            eprintln!("Record added successfully");
        }
        rusqlite::Result::Err(e) => {
            eprintln!("Error creating table: {}", e);
            return Err(e);
        }
    }
}

Ok(())

(If the successful results had different values,the variable may still make sense to return the last success. But you always return Ok(()) for success.)

1 Like

I clearly did not understand how assignment works with the ok and err variants of the result enum particularly that order matters. Great observation about the data_inserted variable not being needed. Below is the updated code. Thank you for sharing your expertise.

use crate::structures::structures::Record;
use rusqlite::{Connection, Result, named_params};

pub fn insert_records(conn: & Connection, records: &mut Vec<Record>) -> Result<(), rusqlite::Error> {

    for record in records {

        match conn.execute(
            "INSERT INTO data (date, open, high, low, close, volume) 
            VALUES (:date, :open, :high, :low, :close, :volume)",
            named_params! {
                ":date": &record.date,
                ":open": &record.open,
                ":high": &record.high,
                ":low": &record.low,
                ":close": &record.close,
                ":volume": &record.volume,
            },
        ){
            Ok(_) => {
                eprintln!("Record added successfully");
            }
            Err(e) => {
                eprintln!("Error adding record: {}", e);
                return Err(e);
            }
        };//
    } // end for loop

   Ok(())

}