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?
#[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