4

I have an existing C++ program that uses Berkeley DB as a storage backend. I would like to rewrite it in Rust. Is there a way to write a Foreign Function Interface in Rust to use Berkeley DB? I have found the tutorial Rust Foreign Function Interface, but it seems too simple an example for the complicated C structs used in BDB; for example, to open a database I need to declare a DB struct and call DB->open(). But I don't know how to do this using the example shown in the tutorial.

Can anyone help with this?

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
monkey1s2
  • 41
  • 2

2 Answers2

3

Well, looking into the C API of BDB I found out that it consists of C structures with elements-pointers to functions. It is not explained in the tutorial (which is very strange), but Rust currently supports pointers to foreign functions. It is also mentioned in Rust reference manual.

You can create all required structures roughly based on the ones defined in db.h, and since Rust and C structures memory layout is the same you can pass these structures to/from the library and expect correct pointers to be present in them.

For example, your DB->open() call could look like this:

struct DB {
    open: extern "C" fn()
}

let db = ...  // Get DB from somewhere
(db.open)()   // Parentheses around db.open are needed to disambiguate field access

This, however, really should be wrapped in some kind of impl-based interface because calling extern functions is unsafe operation, and you do not want your users to put unsafe around all database interactions.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
0

Given the sheer size and complexity of the DB struct, there doesn't appear to be a "clean" way to expose the whole thing to Rust. A tool similar to C2HS to generate the FFI from C headers would be nice, but alas we don't have one.

Note also that the Rust FFI can't currently call into C++ libraries, so you'll have to use the C API instead.

I'm not familiar with the DB APIs at all, but it appears plausible to create a small support library in C to actually create an instance of the DB struct, then expose the public members of the struct __db via getter and setter functions.

Your implementation might look something like this:

[#link_args = "-lrust_dbhelper"]
extern {
    fn create_DB() -> *c_void;
    fn free_DB(db: *c_void);
}

struct DB {
     priv db: *c_void
}

impl Drop for DB {
    fn drop(&self) {
       free_DB(self.db);
    }
}

priv struct DBAppMembers {
    pgsize: u32,
    priority: DBCachePriority
    // Additional members omitted for brevity
}

impl DB {
    pub fn new() -> DB {
        DB {
          db: create_DB()
        }
    }

    pub fn set_pgsize(&mut self, u32 pgsize) {
        unsafe {
            let x: *mut DBAppMembers = ::std::ptr::transmute(self.db);
            x.pgsize = pgsize;
        }
    }
    // Additional methods omitted for brevity
}

You can save yourself from some additional work by specifically calling C functions with the DB.db member as a parameter, but that requires working in an unsafe context, which should probably be avoided where possible. Otherwise, each function exported by libdb will need to have its own wrapper in your native struct DB.

tari
  • 658
  • 4
  • 14
  • 2
    Note that there is [rust-bindgen](https://github.com/crabtw/rust-bindgen) which removes much (but not all) of the legwork in binding to C libs. – huon Aug 08 '13 at 07:52