CXX: C++ main() calling Rust

I'm experimenting with this structure for a GUI application:

FLTK <--- my C++ frontend ---> my Rust library

Here FLTK is the C++ GUI framework and X ---> Y means "X calls Y". So int main() would be written in C++ and would call into FLTK for GUI stuff and into Rust for the "business logic". (Yes, I know about FLTK — Rust GUI library // Lib.rs, and I still want to experiment with this alternative approach.)

Now, I'd like to manage the C++ frontend ---> Rust library part with CXX. CXX certainly supports C++ calling Rust, but I'm not sure how to set it up to coordinate a Rust library with a C++ binary that calls into it. Is that a supported use-case? Can I achieve this with Cargo and the cc build dependency, or should I look into using something like Bazel instead?

I am quite sure Cargo will suffice.
There is a guide here: Cargo-based setup — Rust ♡ C++

I've read that page, but it doesn't speak to how you can write your main() in C++ and have it call Rust via a CXX bridge.

For now I'm using a "C++ sandwich", like this:

main.rs ---------+
                 | (CXX bridge)
                 v
FLTK    <--- my C++ layer ---> (CXX bridge) ---> lib.rs

This actually has some advantages, since I can write command-line parsing and other non-GUI setup in Rust instead of C++. But I'm still curious whether there's a way to do without main.rs and the Rust -> C++ -> Rust double indirection here.

Doesn't the cxx crate also generate C++ glue that lets you call Rust functions?

extern "Rust"

#[cxx::bridge]
mod ffi {
    extern "Rust" {

    }
}

The extern "Rust" section of a CXX bridge declares Rust types and signatures to be made available to C++.

The CXX code generator uses your extern "Rust" section(s) to produce a C++ header file containing the corresponding C++ declarations. The generated header has the same path as the Rust source file containing the bridge, except with a .rs.h file extension.

A bridge module may contain zero or more extern "Rust" blocks.

So you could write your main() in C++ like normal, then import the foo.rs.h header to call into your Rust code.

The idea is you would compile your Rust code to a cdylib or staticlib using cargo build, then use the C++ project's build system to link it into your final executable.

3 Likes

That makes sense -- so an additional (non-Cargo) build system does need to be involved. With the Rust -> C++ -> Rust setup I can get by using only Cargo, so I'm inclined to stick with that.

Yeah, that's a good reason to stick with a Rust main(). People always underestimate how valuable it is to have a build system that Just Works.

It really depends on how complex your C++ is, though. I've found using the cc crate works okay for compiling simple self-contained libraries, but if your C++ pulls in dependencies or has a more "interesting" build story your build.rs script tends to get quite brittle.

You also don't get the incremental compilation something like CMake or Bazel would give you for your C++ code.

1 Like