Have you ever written a C API for your Rust program? I'm sure you did, otherwise what are you waiting for? It's fun! Have you ever dreamed of running your C API directly in your Rust implementation, for example to unit test it? Nah? Because I did. Bah, I'm probably not the only one. Right? Please tell me I'm not.
The inline-c
crate allows you to write C code within Rust directly,
to compile it and to run some assertions. Note that C and Rust are
fully sandboxed, values cannot be exchanged between the two. The
purpose of inline-c
is to ease the testing of a C API, that is for
example automatically generated with [cbindgen
].
Add the following lines to your Cargo.toml
file:
toml
[dev-dependencies]
inline-c = "0.1"
assert_c!
and assert_cxx!
macrosBasic usage of the assert_c!
(or assert_cxx!
) macro. In the
following example a simple Hello, World! C program is compiled and
executed. It is then asserted than the exit code and the outputs are
correct. The next example asserts than the C program correctly returns
an error.
```rust
fn testsuccessful() {
(assertc! {
#include
int main() {
printf("Hello, World!\n");
return 0;
}
})
.success()
.stdout("Hello, World!\n");
}
fn testbadly() { (assertc! { int main() { int x = 1; int y = 2;
return x + y;
}
})
.failure()
.code(3);
} ```
Now, let's enter the real reason to live of this project. Let's way we want a C program to link against a specific shared library.
Note: The
CFLAGS
,CXXFLAGS
,CPPFLAGS
andLDFLAGS
are supported environment variables.
Great! We may want to define a value for the CFLAGS
and the
LDFLAGS
environment variables. First way is to use the
#inline_c_rs
C directive with the following syntax:
```
```
Please note the double quotes around the variable value.
Let's see a concrete example. We declare 3 environment variables,
resp. FOO
, CFLAGS
and LDFLAGS
. The C program prints their
corresponding values, and exit accordingly.
```rust
fn testcmacrowithenvvarsinlined() { (assertc! { #inlinecrs FOO: "bar baz qux" #inlinecrs CFLAGs: "-Ixyz/include -Lzyx/lib" #inlinec_rs LDFLAGS: "-lfoo"
#include <stdio.h>
#include <stdlib.h>
int main() {
const char* foo = getenv("FOO");
if (NULL == foo) {
return 1;
}
printf("FOO is set to `%s`\n", foo);
return 0;
}
})
.success()
.stdout("FOO is set to `bar baz qux`\n");
} ```
This is cool isn't it? But it can be repetitive. What if we can define
environment variables globally, for all the C program written in
assert_c!
or assert_cxx!
?
It is possible with meta environment variables, with the following syntax:
INLINE_C_RS_<variable_name>=<variable_value>
Let's see it in action. We set 2 environments variables,
resp. INLINE_C_RS_FOO
, INLINE_C_RS_CFLAGS
and
INLINE_C_RS_LDFLAGS
, that will create FOO
, CFLAGS
and LDFLAGS
for this C program specifically:
```rust
fn testcmacrowithenvvarsfromenvvars() { setvar("INLINECRSFOO", "bar baz qux"); setvar("INLINECRSCFLAGS", "-Ixyz/include -Lxyz/lib"); setvar("INLINECRSLDFLAGS", "-lfoo");
(assert_c! {
#include <stdio.h>
#include <stdlib.h>
int main() {
const char* foo = getenv("FOO");
if (NULL == foo) {
return 1;
}
printf("FOO is set to `%s`\n", foo);
return 0;
}
})
.success()
.stdout("FOO is set to `bar baz qux`\n");
remove_var("INLINE_C_RS_FOO");
remove_var("INLINE_C_RS_CFLAGS");
remove_var("INLINE_C_RS_LDFLAGS");
} ```
Note that we have use
set_var
and
remove_var
to set or remove the environment variables. That's for the sake of
simplicity: It is possible to set those variables before running your
tests or anything.
BSD-3-Clause
, see LICENSE.md
.