问题
Right now I have code that uses the rusqlite sqlite bindings to open a db connection and do a bunch of db operations in my application like this:
extern crate rusqlite;
use rusqlite::SqliteConnection;
struct MyAppState {
db: SqliteConnection,
// ... pretend there's other fields here ...
}
impl MyAppState {
fn new() -> MyAppState {
let db = SqliteConnection::open(":memory:").unwrap();
MyAppState {
db: db
}
}
fn query_some_info(&mut self, arg: i64) -> i64 {
let mut stmt = self.db.prepare("SELECT ? + 1").unwrap();
let mut result_iter = stmt.query(&[&arg]).unwrap();
let result = result_iter.next().unwrap().unwrap().get(0);
result
}
}
fn main() {
let mut app = MyAppState::new();
for i in range(0, 100) {
let result = app.query_some_info(i);
println!("{}", result);
}
}
Since the prepared statement lives in a local variable, this seems to miss the point of prepared statements to some extent since I have to re-prepare it every time the function is called and the local variable comes into being. Ideally, I would prepare all my statements at most once and stash them in the MyAppState
struct for the duration of the db connection.
However, since the SqliteStatement type is parameterized over the lifetime of the db connection, it borrows the connection and by extension the struct it lives in and I can't do anything with the struct anymore like return the struct by value or call &mut self
methods on it (query_some_info
doesn't really need to take &mut self
here, but some code in my actual program does unless everything goes on to live in RefCell
s, which isn't the worst, I guess, but still).
Usually when the borrow checker betrays me like that, my recourse is to give up on stack discipline and put some Rc<RefCell< >>
here and there until it all works out, but in this case there's some lifetimes in the types either way and I don't know how to word it in a way that appeases the borrow checker.
Ideally I'd like to write code that only prepares the statements right when the db gets opened, or maybe prepares them only once when they are first used, and then never calls prepare
again during the duration of the db connection, while mostly keeping the safety of the rusqlite bindings rather than writing code against the sqlite3 C API or breaking abstraction or whatever. How do I?
回答1:
You are right, indeed, that sibling references are awkward in Rust. There is a good reason though, they are not easily modeled by the ownership system.
In this particular case, I would advise you to split the structure: you can keep the prepared statements in a dedicated cache also parametrized on the lifetime of the db
for example; the db
instead should be instantiated at the top of your program and passed down (think dependency injection) so that the cache that depends on it can outlive the program main function.
This does mean that the db
will remain borrowed, obviously.
回答2:
The Statement
struct has a lifetime parameter, Statement<'conn>
. When you prepare the statement, you must have a reference to the Connection
that outlives the statement.
extern crate rusqlite;
use rusqlite::{Connection, Statement};
struct MyAppState {
db: Connection,
}
impl MyAppState {
fn new() -> MyAppState {
let db = Connection::open(":memory:").unwrap();
MyAppState { db: db }
}
}
struct PreparedStatement<'conn> {
statement: Statement<'conn>,
}
impl<'conn> PreparedStatement<'conn> {
pub fn new<'a>(conn: &'a Connection, sql: &str) -> PreparedStatement<'a> {
PreparedStatement {
statement: conn.prepare(sql).unwrap(),
}
}
fn query_some_info(&mut self, arg: i64) -> i64 {
let mut result_iter = self.statement.query(&[&arg]).unwrap();
let result = result_iter.next().unwrap().unwrap().get(0);
result
}
}
fn main() {
let app = MyAppState::new();
let mut prepared_stmt = PreparedStatement::new(&app.db, "SELECT ? + 1");
for i in 0..100 {
let result = prepared_stmt.query_some_info(i);
println!("{}", result);
}
}
In Rust, unlike some other languages, I have found that factoring something out into a function changes its meaning. It introduces new lifetimes, which usually works against you. But in this case, that's exactly what was needed.
来源:https://stackoverflow.com/questions/27552670/how-to-store-sqlite-prepared-statements-for-later