Drop without overhead?

I use sqlite via rusqlite

And have code like this to hold handle to open database:

struct Db {
    conn: rusqlite::Connection,
    sqlite_ext: *const c_void,
}

to free resources after destroying struct Db I need write code like this:

unsafe {
  sqlite3_close(handle);
  sqlite_ext_shutdown(sqlite_ext);
}

The key moment that I have to call sqlite_ext_shutdown after call of sqlite3_close.

rusqlite::Connection calls sqlite3_close in the rusqlite::Connection::close and
rusqlite::Connection::drop,
both take self, but in Drop implementation I receive &mut self, so code like this:

impl Drop for Db {
  fn drop(&mut self) {
    drop(self.conn);// or self.conn.close()
    unsafe { sqlite_ext_shutdown(self.sqlite_ext) };
  }
}

cause compile time error.

Obviously I can workaround this by using Option<rusqlite::Connection> or
may be Rc<RefCell<rusqlite::Connection>> and use swap,
but may be there is solution without overhead?

You do not need to drop self.conn - the destructor for it will automatically run :slight_smile:

I'm afraid that I don't understand you.

impl Drop for Db {
  fn drop(&mut self) {
    drop(self.conn);// or self.conn.close()
    unsafe { sqlite_ext_shutdown(self.sqlite_ext) };//!!!!
  }
}

at point !!!! I have to be sure that destructor of self.conn has been called,
how automatically call of drop help me in this case?

As I understand rust, it call conn.drop after Db::drop and this doesn't help at all.

Put the sqlite_ext into a local first before closing the conn, then make the unsafe call via the local.

Ok, I can implement algorithm like you suggest in

impl Db {
  fn close(self) {
   ...
  }

and find all places where struct Db go out of scope and call this method,
but how I prevent in future go of struct Db out of scope without calling of Db::close?

Ok I think I confused myself with something.

You can try putting the conn into a https://doc.rust-lang.org/std/mem/union.ManuallyDrop.html and using that to control the order.

3 Likes

Hmm, I see what you're saying. Well, Rust drops structs in lexical order, i.e.

struct Foo {
  x: T,
  y: U,
}

values of type Foo, when dropped, will drop x first, then y.

Therefore, you can write a RAII wrapper around sqlit_ext.

struct SqliteExt(*const c_void);

impl Drop for SqliteExt {
  fn drop(&mut self) {
    unsafe { sqlite_ext_shutdown(self.0) }
  }
}

struct Db {
  conn: rusqlite::Connection,
  sqlite_ext: SqliteExt,
}

and then you just allow the rust compiler to take care of dropping Db :grinning:

(you should definitely add a comment to this effect though)

2 Likes

Is that guaranteed forever these days? I know that’s the current impl and there was talk about making that a formal guarantee but not sure if that happened.

it is now guaranteed

https://github.com/rust-lang/rust/issues/43034

https://github.com/rust-lang/rfcs/pull/1857

3 Likes

Great - thanks.

I think the ManuallyDrop approach still seems preferable to me as it’s more obvious that something funky is going on. It also doesn’t depend on field declaration order and focuses the required ordering where it matters.

1 Like

By the way, this thread reminds me that I always thought it was weird that Drop::drop() takes a &mut self rather than self.