A Rust FFI Example

Using bindgen and Rust to call C/C++ code
Authored: 2021-10-08; Updated:

I've used a number of FFIs (Foreign_function_interface) over the years, but was very impressed with how quickly I got something working with Rust and bindgen.

At work, we have our own SQL-like database, the wire protocol is complicated enough that it's not worth building a native version. Instead there is a C++/C library that handles everything. What makes this a tricky integration is the fact that the database library is:

I have some prior experience with using a binding against this library. I tried (and barely passed) using Go for a project, but ran into all sorts of random segfaulting due to the Go runtime. It may be better as that was around 2016.

First start with a Cargo.toml

name = "query-ffi-example"
version = "0.1.0"
edition = "2018"
build = ""

libc = "0.2"

cc = { version = "1.0", features = ["parallel"] }
pkg-config = "0.3"

In order to link against we need a file. Some of the libs are static archives (.a) and some a dynamic shared objects (.so).
fn main() {
    println!("cargo:rustc-flags=-l dylib=stdc++");

Generate the bindings via bindgen.

$ bindgen include/query/query.h -o -- -I../common/include/

This creates a large file we don't have to edit:

$ wc -l src/
5980 src/

Our can include for convenience of building



Our looks like:

use std::ffi::{CStr, CString};
use std::ptr;
use std::{thread, time};

use query2lib::*;

extern "C" fn callback(qt: *mut query_table, _user_arg: *mut libc::c_void) {
    println!("I'm called from C with value");
    unsafe {
        let c_str = query_dump_table_as_string(qt, query_dump_format_DUMP_DEFAULT);

        let rust_str = CStr::from_ptr(c_str).to_string_lossy().into_owned();
        println!("{}", rust_str);

fn main() {
    let id = CString::new("rust-test").unwrap();
    let hostname = CString::new("db-hostname").unwrap();

    unsafe {
        let conn = query_init(id.as_ptr(), hostname.as_ptr(), query_init_flag_INIT_NONE as i32);

        let sql = CString::new("SELECT current_timestamp;").unwrap();
        let fcb: query_callback = Some(callback);

        let p: *const i32 = ptr::null();
        let query_result = query_run(conn, sql.as_ptr(), fcb, p as *mut libc::c_void);

        println!("query_run result: {}", query_result);
        let ten_millis = time::Duration::from_millis(10000);


And when running we get:

$ export LD_LIBRARY_PATH=`pwd`
$ ./target/debug/query-ffi-example
query_run result: 0
I'm called from C with value
Fri Oct 08 22:34:20 2021

1 rows selected

All that in just a few hours!