As the Rust compiler does not directly know how to compile C or C++ code (or code from any other language, which presents a C interface), it is necessary to compile your non-Rust code ahead of time.
For embedded projects, this most commonly means compiling the C/C++ code to a static archive (such as cool-library.a), which can then be combined with your Rust code at the final linking step.
If the library you would like to use is already distributed as a static archive, it is not necessary to rebuild your code. Just convert the provided interface header file as described above, and include the static archive at compile/link time.
If your code exists as a source project, it will be necessary to compile your C/C++ code to a static library, either by triggering your existing build system (such as make, CMake, etc.), or by porting the necessary compilation steps to use a tool called the cc crate. For both of these steps, it is necessary to use a build.rs script.
A build.rs script is a file written in Rust syntax, that is executed on your compilation machine, AFTER dependencies of your project have been built, but BEFORE your project is built.
The full reference may be found here. build.rs scripts are useful for generating code (such as via bindgen), calling out to external build systems such as Make, or directly compiling C/C++ through use of the cc crate.
For projects with complex external projects or build systems, it may be easiest to use std::process::Command to "shell out" to your other build systems by traversing relative paths, calling a fixed command (such as make library), and then copying the resulting static library to the proper location in the target build directory.
While your crate may be targeting a no_std embedded platform, your build.rs executes only on machines compiling your crate. This means you may use any Rust crates which will run on your compilation host.
For projects with limited dependencies or complexity, or for projects where it is difficult to modify the build system to produce a static library (rather than a final binary or executable), it may be easier to instead utilize the cc crate, which provides an idiomatic Rust interface to the compiler provided by the host.
In the simplest case of compiling a single C file as a dependency to a static library, an example build.rs script using the cc crate would look like this:
extern crate cc;
fn main() {
cc::Build::new()
.file("foo.c")
.compile("libfoo.a");
}
Using Rust code inside a C or C++ project mostly consists of two parts.
• Creating a C-friendly API in Rust
• Embedding your Rust project into an external build system
Apart from cargo and meson, most build systems don't have native Rust support. So you're most likely best off just using cargo for compiling your crate and any dependencies.
Create a new cargo project as usual.
There are flags to tell cargo to emit a systems library, instead of its regular rust target. This also allows you to set a different output name for your library, if you want it to differ from the rest of your crate.
[lib]
name = "your_crate"
crate-type = ["cdylib"]
# Creates dynamic lib
# crate-type = ["staticlib"] # Creates static lib
Because C++ has no stable ABI for the Rust compiler to target, we use C for any interoperability between different languages. This is no exception when using Rust inside of C and C++ code.
The Rust compiler mangles symbol names differently than native code linkers expect. As such, any function that Rust exports to be used outside of Rust needs to be told not to be mangled by the compiler.
By default, any function you write in Rust will use the Rust ABI (which is also not stabilized). Instead, when building outwards facing FFI APIs we need to tell the compiler to use the system ABI.
Depending on your platform, you might want to target a specific ABI version, which are documented here.
Putting these parts together, you get a function that looks roughly like this.
#[no_mangle]
pub extern "C" fn rust_function() {
}
Just as when using C code in your Rust project you now need to transform data from and to a form that the rest of the application will understand.
So then, that's one half of the problem solved. How do you use this now?
This very much depends on your project and/or build system
cargo will create a my_lib.so/my_lib.dll or my_lib.a file, depending on your platform and settings. This library can simply be linked by your build system.
However, calling a Rust function from C requires a header file to declare the function signatures.
Every function in your Rust-ffi API needs to have a corresponding header function.
#[no_mangle]
pub extern "C" fn rust_function() {}
would then become
void rust_function();
etc.
There is a tool to automate this process, called cbindgen which analyses your Rust code and then generates headers for your C and C++ projects from it.
At this point, using the Rust functions from C is as simple as including the header and calling them!