Lifetime error with self-referential type (redb::WriteTransaction)

Use #![deny(elided_lifetimes_in_paths)] and consider every lifetime you have to add.

// `self` is the only thing to borrow from, so not much to think about.
impl Database {
    pub fn begin_write(&self) -> WriteTransaction<'_> {
// Only the first parameter is actually a borrow of `self`,
// `'txn` can be unconstrained since you just assign `None`
impl DbWrapper {
    fn begin_write<'txn>(&self) -> MetaUseTransaction<'_, 'txn> {

And before fixing...

impl CacheTable<'_,'_> {
    fn open(&mut self, use_transaction: &UseTransaction) {

That's a lot of lifetimes, let's just give them all names first so we get better errors.

impl<'db, 'txn> CacheTable<'db, 'txn> {
    fn open<'s, 'b, 'u>(
        &'s mut self, 
        use_transaction: &'b UseTransaction<'u>
    ) {

Playground.

Ah yes, this makes sense, we need a &'txn UseTransaction<'db> since we're trying to assign the argument to a field with those lifetimes.

// Fixed
impl<'db, 'txn> CacheTable<'db, 'txn> {
    fn open(&mut self, use_transaction: &'txn UseTransaction<'db>) {

And now a new error pops up.

We're right back where this thread started: There's no safe way to create a self-referential struct such as this without exclusively borrowing yourself for the rest of your validity:

impl<'db, 'txn> MetaUseTransaction<'db, 'txn> {
    // That's a `&'txn mut MetaUseTransaction<'db, 'txn>`
    fn compute_some_thing(&'txn mut self) -> Result<(), ()> {

...and that won't work because you have a non-trivial destructor. (Even when you don't it's pretty much always too inflexible to be what you really want.)


The answer to "can I safely construct self-referential structs when there are non-trivial destructors involved" is no.

3 Likes