inline-c
is a small crate that allows a user to write C (including
C++) code inside Rust. Both environments are strictly sandboxed: it is
non-obvious for a value to cross the boundary. The C code is
transformed into a string which is written in a temporary file. This
file is then compiled into an object file, that is finally
executed. It is possible to run assertions about the execution of the
C program.
The primary goal of inline-c
is to ease the testing of a C API of a
Rust program (generated with
cbindgen
for example). Note
that it's not tied to a Rust program exclusively, it's just its
initial reason to live.
Install
Add the following lines to your Cargo.toml
file:
[]
= "0.1"
Documentation
The assert_c
and assert_cxx
macros live in the inline-c-macro
crate, but are re-exported in this crate for the sake of simplicity.
Being able to write C code directly in Rust offers nice opportunities,
like having C examples inside the Rust documentation that are
executable and thus tested (with cargo test --doc
). Let's dig into
some examples.
Basic usage
The following example is super basic: C prints Hello, World!
on the
standard output, and Rust asserts that.
use assert_c;
Or with a C++ program:
use assert_cxx;
The assert_c
and assert_cxx
macros return a Result<Assert, Box<dyn Error>>
. See Assert
to learn more about the possible
assertions.
The following example tests the returned value:
use assert_c;
Environment variables
It is possible to define environment variables for the execution of
the given C program. The syntax is using the special #inline_c_rs
C
directive with the following syntax:
#inline_c_rs <variable_name>: "<variable_value>"
Please note the double quotes around the variable value.
use assert_c;
Meta environment variables
Using the #inline_c_rs
C directive can be repetitive if one needs to
define the same environment variable again and again. That's why meta
environment variables exist. They have the following syntax:
INLINE_C_RS_<variable_name>=<variable_value>
It is usually best to define them in a build.rs
script
for example. Let's see it in action with a tiny example:
use assert_c;
use ;
Note: If you have multiple inline C tests that use the same environment
variables, you may see flakiness in test runs because by default cargo test
runs tests parallely. This problem can occur even if your tests are removing
the environment variables during teardown. To fix this, you can do either
of the following:
- Use unique environment variable names for each test
- (Not recommended for Production) Run the tests serially instead of parallely.
You can use the following command:
cargo test -- --test-threads=1
CFLAGS
, CPPFLAGS
, CXXFLAGS
and LDFLAGS
Some classical Makefile
variables like CFLAGS
, CPPFLAGS
,
CXXFLAGS
and LDFLAGS
are understood by inline-c
and consequently
have a special treatment. Their values are added to the appropriate
compilers when the C code is compiled and linked into an object file.
Pro tip: Let's say we have a Rust crate named foo
, and it exports a
C API. It is possible to define CFLAGS
and LDFLAGS
as follow to
correctly compile and link all the C codes to the Rust libfoo
shared
object by writing this in a build.rs
script (it is assumed that
libfoo
lands in the target/<profile>/
directory, and that foo.h
lands in the root directory):
use ;
Et voilà ! Now run cargo build --release
(to generate the
shared objects) and then cargo test --release
to see it in
action.
Using inline-c
inside Rust documentation
Since it is now possible to write C code inside Rust, it is consequently possible to write C examples, that are:
- Part of the Rust documentation with
cargo doc
, and - Tested with all the other Rust examples with
cargo test --doc
.
Yes. Testing C code with cargo test --doc
. How fun is that? No
trick needed. One can write:
/// Blah blah blah.
///
/// # Example
///
/// ```rust
/// # use inline_c::assert_c;
/// #
/// # fn main() {
/// # (assert_c! {
/// #include <stdio.h>
///
/// int main() {
/// printf("Hello, World!");
///
/// return 0;
/// }
/// # })
/// # .success()
/// # .stdout("Hello, World!");
/// # }
/// ```
pub extern "C"
which will compile down into something like this:
int
Notice that this example above is actually Rust code, with C code
inside. Only the C code is printed, due to the #
hack of rustdoc
,
but this example is a valid Rust example, and is fully tested!
There is one minor caveat though: the highlighting. The Rust set of
rules are applied, rather than the C ruleset. See this issue on
rustdoc
to follow the
fix.
C macros
C macros with the #define
directive is supported only with Rust
nightly. One can write:
use assert_c;
Note that multi-lines macros don't work! That's because the \
symbol
is consumed by the Rust lexer. The best workaround is to define the
macro in another .h
file, and to include it with the #include
directive.
License
BSD-3-Clause
, see LICENSE.md
.