I am trying to provide a thin abstraction over a database in Rust. Here's a simplified version of the underlying database API as I understand it:
// Connection to a database
struct Connection {}
impl Connection {
fn new() -> Connection {
Connection {}
}
}
// Prepared SQL statement
struct Statement<'a> {
conn: &'a Connection,
}
impl Connection {
// Create a prepared statement
fn prepare(&self) -> Statement {
return Statement { conn: self };
}
}
I want to provide a wrapper which upon construction connects to a database and stores some prepared statements. After constructing the wrapper I don't actually need the raw connection any more.
Here's a simplified version of my code:
struct Wrapper<'a> {
stmt: Statement<'a>,
}
impl<'a> Wrapper<'a> {
fn new() -> Wrapper<'a> {
let conn = Connection::new();
Wrapper {
stmt: conn.prepare(),
}
}
}
fn main() {
let _w = Wrapper::new();
}
Wrapper::new
fails to compile because the returned value references conn
whose lifetime ends at the end of the function ("error[E0515]: cannot return value referencing local variable conn
").
The simplest solution is just to require the caller to construct a Connection
; but then this is a rather leaky "wrapper" for the database.
Ideally I'd like to move conn
out of the function. Since I'm moving the Wrapper
out of the function, I thought I could just move conn
into the Wrapper
:
struct Wrapper<'a> {
conn: Connection,
stmt: Statement<'a>,
}
impl<'a> Wrapper<'a> {
fn new() -> Wrapper<'a> {
let conn = Connection {};
Wrapper {
stmt: conn.prepare(),
conn,
}
}
}
But this just adds another error: "cannot move out of conn
because it is borrowed", even if I try to dodge the chicken-and-egg initialisation of the .stmt
and .conn
fields with Option
& mutation hackery. I've read that you can't really have one struct member reference another in rust, which would explain this.
For a while I thought I could use an Rc
, but for this to work I think it would need to be on the Statement
's reference to the Connection
; which is in library code.
Can anyone see which Rust design pattern I'm missing?