I'm writing a struct that represents a DB and that should provide "high level" functions to access the database, hiding the details of the SQL stuff. I'm using the rusqlite crate, internally.
Now, it is straight forward to wrap the underlying rusqlite DB connection in my struct. The connection will be created in the constructor (i.e., new()) and it will be closed in the Drop handler. The DB tables will also be created in new(), the via self.conn.execute() function.
The problem is that, for the actual load() and store() functions, which will do the heavy lifting, I want to use prepared statements. These should be created once and re-used whenever load() or store() is called. So, I need to create them in the new() function, by calling self.conn.prepare(), and store them in my struct alongside the connection.
Unfortunately, this doesn't work, because Statement<'a> holds a reference to the Connection that has lifetime 'a, and Rust doesn't allow me to store the ownedConnection instance in the same struct as the Statement<'a> instances that hold references to that connection
From a "lifetime" perspective, this is perfectly fine, because the Statement<'a> is referencing a Connection that lives in the same struct, which means that the Connection will obviously be alive for as long as the Statement<'a> is alive. But how do I convince the Rust compiler?
Is there a "good" way to maintain the Statement<'a>s together with the Connection ???
Despite the unfortunate overlap in terminology, Rust lifetimes like 'a do not denote the liveness of values. They denote the duration of borrows. The main relation between the two is that it is an error for a value to go out of scope while borrowed. But it is also an error for a value to be moved, or to have a fresh &mut _ taken to it, while it is borrowed.
That is why self-referential structs -- structs that contain both the referent and a Rust reference to the referent --are not practical. It's technically possible to create one, but then you can't for example move the struct, or obtain a &mut _ to the Connection, etc.
There are some crates which aim to support the general pattern which you could check out (ouroborus, yoke). It requires unsafe to implement and is notoriously hard to get right -- they've had soundness issues in the past, and I haven't actively tracked if they have outstanding issues, so do your own investigation WRT how "good" they are, if you decide to go that route.
I don't recommend attempting with unsafe yourself, at least, not unless you dedicate the time to learn about the soundness problems those crates have encountered so you don't repeat them.
(I don't know enough about rusqlite to know if there's a non-borrowing alternative that would allow you to sidestep the issue either.)
means "everything marked by 'a is definitely not stored anywhere in MyDatabase.
That scope is external to the struct and outlives it, so unfortunately it does the exact opposite of what you want and forbids borrowing from the fields of the struct. There is no way for a struct to borrow from itself. Cycles like that are not supported by the borrow checker.
It would be nice if something like the 'this lifetime, which is provided by ouroborus, would be built into the language. If it means that we cannot move the referenced "inner" struct (e.g., Connection) or obtain a &mut _ to it, then this would be okay for my use-case â because once the Statement<'a>s have been created, I don't need to use the Connectiondirectly anymore. I just need to ensure the Connection will remain alive and outlive the Statement<'a>s.
I have now "solved" this by making two separate structs: One that essentially just initializes and wraps the Connection, and one that creates and wraps the Statement<'a>s. The latter struct needs to hold a reference to the former one. This works, but is not "ideal" from a user perspective, because one now needs to deal with the two separate structs everywhere...
If the referenced "inner" struct and the reference to that "inner" struct are wrapped in the same "outer" struct, then it is obvious that both (the referenced struct and the reference) will go out of scope at the very same the â when the "outer" struct goes out of scope. So the reference definitely does not outlive the referenced struct and all should be fine, right?
As quinedot said, there still could be a problem, if the "outer" struct allowed the "inner" struct to be moved out, or if the "outer" struct exposed any &mut _ references to the "inner" struct. But these cases could probably be detect and prevented by the borrow checker. Either that, or we maybe could add the notion of a "restricted" struct member that must not ever be moved or ever have any &mut _ references and therefore allows for a proper 'this liftime...
Probably. I know there exists Pin<Ptr> in Rust. But does this help in my use-case?
If so, could you give a simple example?
To my understanding, Pin<Ptr> ensures that the pointed-to object won't move when working with "raw" pointers, so that the "raw" pointer remains valid. But working with "raw" pointers and pins still requires an unsafe context, and that we generally want to avoid...
I don't know if it will help, but this is what I did in my case:
I create an internal structure to deal with the operations and then create another structure to create this internal structure under a Arc<RwLock<>>, so on that I expose the operations protected.
Look here ff you want to see my example.
If one were to construct a self-referential struct based on Pin, then it would be that struct, not Connection, that must be !Unpin, so that is not an obstacle. (Other things are. Ideally, there would be a library presenting a safe API to pin-based self reference, but right now, the only safe way to make use of it is to define an async block, which can contain references to its own variables.)
May need a &'a mut depending on the what you need to do with the Connection.
A dirty but "safe" hack would be to Box conn, then Box::leak it to get a static reference to it. That will leak the connection and which means it's Drop function is never called. Never calling drop is allowed, though frowned upon, in safe rust. This might be okay if you don't make a lot of connections.
use rusqlite::{Connection, Statement};
pub struct DbAccessWrapper {
conn: &'static Connection,
prepared_statement_1: Statement<'static>,
}
impl DbAccessWrapper {
pub fn new() -> Self {
// Be warned the Connection is leaked for the lifetime of the program, so it will not be dropped.
let conn = Box::leak(Box::new(Connection::open_in_memory().unwrap()));
let prepared_statement_1 = conn
.prepare("SELECT * FROM some_table WHERE some_column = ?")
.unwrap();
DbAccessWrapper {
conn,
prepared_statement_1,
}
}
}
You could unleak the box with unsafe and the extra care that involves, but at that point your might as well use a crate like ouroborus.
Maybe some day, but it'd be something different than what Rust lifetimes are today, and won't be coming any time soon.
Without some new type of lifetime, that would be some sort of global analysis with horrible failure cases. Like, here's something the borrow checker has no problem with today...
But if a MyDatabase could be self-referential, those ones can't be passed to use_it. So either it is something completely new to the language and use_it can't compile at all, or global analysis rejects some calls to use_it and not others... based on the body of use_it and how the MyDatabase was constructed.
Note that even this concept doesn't solve your OP, though! Unless you boxed the Connection or otherwise moved it behind some indirection, anyway. Statement<'a> contains a &'a Connection that would dangle if you moved the Connection (like when you move the MyDatabase). Rust doesn't have move constructors or the like to fix up the references after moving.
Probably not without unsafe and more or less recreating one of the self-referential pattern crates.
Even with Pin, Rust references are usually the wrong tool for the job, as their (Rust) lifetimes correspond to borrow durations, not value liveness.
I struggled with this part of my reply as I'm not sure what your actual code looks like or if there are any misconceptions. E.g. there is no safe way to construct the MyDatabase<'a> from the OP without moving the Connection -- in safe code, you'd have to create the Statement<'a>s and then move them and the Connection into a MyDatabase<'a>. But maybe you really attempted a Vec<Statement<'_>> or something. More on that in a bit.
Anyway, it sounds like something like @Cocalus is working for or can work for you...
// Or just pass `&Connection` and something containing `Statement<'_>`s
// separately.
pub struct DbAccessWrapper<'a> {
conn: &'a Connection,
prepared_statement_1: Statement<'a>,
}
And your main complaint is that you need this new struct in addition to some Connection owning type that you want to move around before you create the Statement<'a>s.
Creating DbAccessWrapper<'_> will effectively pin the Connection to the stack in a compiler-checked way until the wrapper goes away, but it sounds like this is fine for your purposes? In that case I don't think two structs is really that bad, as they are two pretty different situations: Times when you can move the Connection around and times when you're pinned to the stack -- when you have to wait for all the Statement<'_>s to go away before you can go up the call stack or otherwise move or destruct Connection. And it's preferable not to have to deal with lifetimes when in the latter case anyway.
(It's rusqlite's design which resulted in this dichotomy, not your own choices, but it is what it is.)
Quick sidebar:
You would want to be sure Connection doesn't do anything important on drop. Besides closing the connection and freeing memory, it may flush data or something.
Now I would like to get back to why this doesn't work:
You can create a Statement<'a> from a shared &'a Connection, and you've stated it's acceptable to not be able to obtain a &mut Connection or move the connection, so it may seem like you could construct a self-referential MyConn somehow and then pass a reference to MyConn around. After all, while Connection is shared-borrowed, it can't be moved or have a &mut _ taken to it.
And this almost works, but does not -- because of destructors. Both Connection and Statement<'a> have destructors, so MyConn<'a> has one as well. And being destructed is -- generally speaking -- an exclusive operation, like taking a &mut _. This conflicts with being borrowed forever, which is what you have to do to create the self-referential MyConn in safe Rust.[1]
The compiler recognizes that &'t T has no destructor; going out of scope does not "observe" the T or keep the lifetime 't active (does not keep the borrow of the referent active)
Vec<V> uses an unsafe, unstable feature to inform the compiler that it drops the Vs but doesn't do anything else with them.
Together that's enough for the compiler to prove that the borrow of the Connection isn't used when MyConn<'a> drops, and to therefore let the code compile.
Now, when Statement<'a> drops, it does utilize it's &'a Connection field. But there is still an out: you can disable its destructor using ManuallyDrop.
However, there's a downside here: similar to leaking Connection, this creates the possibility of logical bugs if dropping Statement<'_>s does anything important. In this case there is a way to call the destructors without unsafe, but it still may be a foot-gun.
Do I recommend using this pattern? Not really. It feels pretty fragile to me, manually dropping also feels pretty foot-gunny, and you have still borrowed the Connection forever and cannot move it, e.g. to pass it up the call stack. (We've only worked around the interaction with destructors specifically.)
This is similar to how two separate structs pin the Connection to the stack, but with a lot more restrictions. âŠī¸
The problem is that the borrowed connection field must not be replaced/overwritten with anything else while references to it exist (otherwise it could be swapped with another connection, and your original connection could be dropped).
The borrow checker ensures that by making the source of a loan unmovable for as long as it's borrowed, but in self-referential structs that's not useful, because it prevents such struct from being returned or assigned to anything.
Tracking relationships between fields while they can be moved is a more complex problem, which isn't supported by the borrow checker (except secret internals of async futures, but trying to replicate that without causing UB is very tricky, especially when any exclusive references are involved).
I see. However, I think, in my use-case, it would be fine to have some sort of "restricted" struct that can not be moved and does not allow its fields to be re-assigned after construction, but that is allowed to be self-referential in turn. It will usually be used like this:
fn some_function() {
let db = MyDbWrapper::new(...);
db.load_value(...);
db.store_value(...);
[...]
db.load_value(...);
db.store_value(...);
[...]
/* MyDbWrapper dropped here */
}
So, there's no need to ever move or return the MyDbWrapper.
Anyways, I'm now using the solution where the actual DB connection and the DB access functions (prepared statements) live in two separate structs and this is working alright.
Actually, closing/flushing the underlying DB connection "cleanly" is something we definitely want to ensure. So never calling the Drop function is, unfortuantely, not an option here.
Yeah, when all the objects stay in the same function it's fine. If you used separate variables instead of fields, then they could be tracked separately and borrowed without problems (this is why Futures of async fn may be internally self-referential structs).
Unfortunately, involving additional function call happens to require moving the result, and borrow checking is enforced based on the function's interface, not the context where it's used.
If you don't need the wrapper movable outside the scope, you can do something like this:
let conn = Connection::new();
let db = DbWrapper::new(&conn);
and use references for all the fields in the wrapper.