inline_c/lib.rs
1//! `inline-c` is a small crate that allows a user to write C
2//! (including C++) code inside Rust. Both environments are strictly
3//! sandboxed: it is non-obvious for a value to cross the
4//! boundary. The C code is transformed into a string which is written
5//! in a temporary file. This file is then compiled into an object
6//! file, that is finally executed. It is possible to run assertions
7//! about the execution of the C program.
8//!
9//! The primary goal of `inline-c` is to ease the testing of a C API
10//! of a Rust program. Note that it's not tied to a Rust program
11//! exclusively, it's just its initial reason to live.
12//!
13//! The [`assert_c`] and [`assert_cxx`] macros live in the
14//! `inline-c-macro` crate, but are re-exported in this crate for the
15//! sake of simplicity.
16//!
17//! Being able to write C code directly in Rust offers nice
18//! opportunities, like having C examples inside the Rust
19//! documentation that are executable and thus tested (with `cargo
20//! test --doc`). Let's dig into some examples.
21//!
22//! ## Basic usage
23//!
24//! The following example is super basic: C prints `Hello, World!` on
25//! the standard output, and Rust asserts that.
26//!
27//! ```rust
28//! use inline_c::assert_c;
29//!
30//! fn test_stdout() {
31//! (assert_c! {
32//! #include <stdio.h>
33//!
34//! int main() {
35//! printf("Hello, World!");
36//!
37//! return 0;
38//! }
39//! })
40//! .success()
41//! .stdout("Hello, World!");
42//! }
43//!
44//! # fn main() { test_stdout(); }
45//! ```
46//!
47//! Or with a C++ program:
48//!
49//! ```rust
50//! use inline_c::assert_cxx;
51//!
52//! fn test_cxx() {
53//! (assert_cxx! {
54//! #include <iostream>
55//!
56//! int main() {
57//! std::cout << "Hello, World!";
58//!
59//! return 0;
60//! }
61//! })
62//! .success()
63//! .stdout("Hello, World!");
64//! }
65//!
66//! # fn main() {
67//! # #[cfg(not(target_os = "windows"))]
68//! # test_cxx();
69//! # }
70//! ```
71//!
72//! The [`assert_c`] and [`assert_cxx`] macros return a
73//! `Result<Assert, Box<dyn Error>>`. See [`Assert`] to learn more
74//! about the possible assertions.
75//!
76//! The following example tests the returned value:
77//!
78//! ```rust
79//! use inline_c::assert_c;
80//!
81//! fn test_result() {
82//! (assert_c! {
83//! int main() {
84//! int x = 1;
85//! int y = 2;
86//!
87//! return x + y;
88//! }
89//! })
90//! .failure()
91//! .code(3);
92//! }
93//!
94//! # fn main() { test_result() }
95//! ```
96//!
97//! ## Environment variables
98//!
99//! It is possible to define environment variables for the execution
100//! of the given C program. The syntax is using the special
101//! `#inline_c_rs` C directive with the following syntax:
102//!
103//! ```c
104//! #inline_c_rs <variable_name>: "<variable_value>"
105//! ```
106//!
107//! Please note the double quotes around the variable value.
108//!
109//! ```rust
110//! use inline_c::assert_c;
111//!
112//! fn test_environment_variable() {
113//! (assert_c! {
114//! #inline_c_rs FOO: "bar baz qux"
115//!
116//! #include <stdio.h>
117//! #include <stdlib.h>
118//!
119//! int main() {
120//! const char* foo = getenv("FOO");
121//!
122//! if (NULL == foo) {
123//! return 1;
124//! }
125//!
126//! printf("FOO is set to `%s`", foo);
127//!
128//! return 0;
129//! }
130//! })
131//! .success()
132//! .stdout("FOO is set to `bar baz qux`");
133//! }
134//!
135//! # fn main() {
136//! # std::env::set_var("INLINE_C_RS_CFLAGS", "-D_CRT_SECURE_NO_WARNINGS");
137//! # test_environment_variable()
138//! # }
139//! ```
140//!
141//! ### Meta environment variables
142//!
143//! Using the `#inline_c_rs` C directive can be repetitive if one
144//! needs to define the same environment variable again and
145//! again. That's why meta environment variables exist. They have the
146//! following syntax:
147//!
148//! ```sh
149//! INLINE_C_RS_<variable_name>=<variable_value>
150//! ```
151//!
152//! It is usually best to define them in [a `build.rs`
153//! script](https://doc.rust-lang.org/cargo/reference/build-scripts.html)
154//! for example. Let's see it in action with a tiny example:
155//!
156//! ```rust
157//! use inline_c::assert_c;
158//! use std::env::{set_var, remove_var};
159//!
160//! fn test_meta_environment_variable() {
161//! set_var("INLINE_C_RS_FOO", "bar baz qux");
162//!
163//! (assert_c! {
164//! #include <stdio.h>
165//! #include <stdlib.h>
166//!
167//! int main() {
168//! const char* foo = getenv("FOO");
169//!
170//! if (NULL == foo) {
171//! return 1;
172//! }
173//!
174//! printf("FOO is set to `%s`", foo);
175//!
176//! return 0;
177//! }
178//! })
179//! .success()
180//! .stdout("FOO is set to `bar baz qux`");
181//!
182//! remove_var("INLINE_C_RS_FOO");
183//! }
184//!
185//! # fn main() {
186//! # std::env::set_var("INLINE_C_RS_CFLAGS", "-D_CRT_SECURE_NO_WARNINGS");
187//! # test_meta_environment_variable()
188//! # }
189//! ```
190//!
191//! ### `CFLAGS`, `CPPFLAGS`, `CXXFLAGS` and `LDFLAGS`
192//!
193//! Some classical `Makefile` variables like `CFLAGS`, `CPPFLAGS`,
194//! `CXXFLAGS` and `LDFLAGS` are understood by `inline-c` and
195//! consequently have a special treatment. Their values are added to
196//! the appropriate compilers when the C code is compiled and linked
197//! into an object file.
198//!
199//! Pro tip: Let's say we have a Rust crate named `foo`, and it
200//! exports a C API. It is possible to define `CFLAGS` and `LDFLAGS`
201//! as follow to correctly compile and link all the C codes to the
202//! Rust `libfoo` shared object by writing this in a `build.rs` script
203//! (it is assumed that `libfoo` lands in the `target/<profile>/`
204//! directory, and that `foo.h` lands in the root directory):
205//!
206//! ```rust,ignore
207//! use std::{env, ffi::OsStr};
208//!
209//! fn main() {
210//! let include_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
211//!
212//! let mut shared_object_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
213//! shared_object_dir.push("target");
214//! shared_object_dir.push(env::var("PROFILE").unwrap());
215//! let shared_object_dir = shared_object_dir.as_path().to_string_lossy();
216//!
217//! // The following options mean:
218//! //
219//! // * `-I`, add `include_dir` to include search path,
220//! // * `-L`, add `shared_object_dir` to library search path,
221//! // * `-D_DEBUG`, enable debug mode to enable `assert.h`.
222//! println!(
223//! "cargo:rustc-env=INLINE_C_RS_CFLAGS=-I{I} -L{L} -D_DEBUG",
224//! I = include_dir,
225//! L = shared_object_dir.clone(),
226//! );
227//!
228//! // Here, we pass the fullpath to the shared object with
229//! // `LDFLAGS`.
230//! println!(
231//! "cargo:rustc-env=INLINE_C_RS_LDFLAGS={shared_object_dir}/{lib}",
232//! shared_object_dir = shared_object_dir,
233//! lib = if cfg!(target_os = "windows") {
234//! "foo.dll".to_string()
235//! } else if cfg!(target_os = "macos") {
236//! "libfoo.dylib".to_string()
237//! } else {
238//! "libfoo.so".to_string()
239//! }
240//! );
241//! }
242//! ```
243//!
244//! _Et voilà !_ Now run `cargo build --release` (to generate the
245//! shared objects) and then `cargo test --release` to see it in
246//! action.
247//!
248//! ## Using `inline-c` inside Rust documentation
249//!
250//! Since it is now possible to write C code inside Rust, it is
251//! consequently possible to write C examples, that are:
252//!
253//! 1. Part of the Rust documentation with `cargo doc`, and
254//! 2. Tested with all the other Rust examples with `cargo test --doc`.
255//!
256//! Yes. Testing C code with `cargo test --doc`. How _fun_ is that? No
257//! trick needed. One can write:
258//!
259//! ```rust,ignore
260//! /// Blah blah blah.
261//! ///
262//! /// # Example
263//! ///
264//! /// ```rust
265//! /// # use inline_c::assert_c;
266//! /// #
267//! /// # fn main() {
268//! /// # (assert_c! {
269//! /// #include <stdio.h>
270//! ///
271//! /// int main() {
272//! /// printf("Hello, World!");
273//! ///
274//! /// return 0;
275//! /// }
276//! /// # })
277//! /// # .success()
278//! /// # .stdout("Hello, World!");
279//! /// # }
280//! /// ```
281//! pub extern "C" fn some_function() {}
282//! ```
283//!
284//! which will compile down into something like this:
285//!
286//! ```rust
287//! # use inline_c::assert_c;
288//! #
289//! # fn main() {
290//! # (assert_c! {
291//! #include <stdio.h>
292//!
293//! int main() {
294//! printf("Hello, World!");
295//!
296//! return 0;
297//! }
298//! # })
299//! # .success()
300//! # .stdout("Hello, World!");
301//! # }
302//! ```
303//!
304//! Notice that this example above is actually Rust code, with C code
305//! inside. Only the C code is printed, due to the `#` hack of
306//! `rustdoc`, but this example is a valid Rust example, and is fully
307//! tested!
308//!
309//! There is one minor caveat though: the highlighting. The Rust set
310//! of rules are applied, rather than the C ruleset. [See this issue
311//! on `rustdoc` to follow the
312//! fix](https://github.com/rust-lang/rust/issues/78917).
313//!
314//! ## C macros
315//!
316//! C macros with the `#define` directive is supported only with Rust
317//! nightly. One can write:
318//!
319//! ```rust,ignore
320//! use inline_c::assert_c;
321//!
322//! fn test_c_macro() {
323//! (assert_c! {
324//! #define sum(a, b) ((a) + (b))
325//!
326//! int main() {
327//! return !(sum(1, 2) == 3);
328//! }
329//! })
330//! .success();
331//! }
332//! ```
333//!
334//! Note that multi-lines macros don't work! That's because the `\` symbol
335//! is consumed by the Rust lexer. The best workaround is to define the
336//! macro in another `.h` file, and to include it with the `#include`
337//! directive.
338
339mod assert;
340mod run;
341
342pub use crate::run::{run, Language};
343pub use assert::Assert;
344pub use inline_c_macro::{assert_c, assert_cxx};
345pub mod predicates {
346 //! Re-export the prelude of the `predicates` crate, which is useful for assertions.
347 //!
348 //! # Example
349 //!
350 //! An end of line on all systems are represented by the `\n`
351 //! character, except on Windows where it is `\r\n`. Even if C
352 //! writes `\n`, it will be translated into `\r\n`, so we need to
353 //! normalize this. This is where the `predicates` crate can be
354 //! helpful.
355 //!
356 //! ```rust
357 //! use inline_c::{assert_c, predicates::*};
358 //!
359 //! fn test_predicates() {
360 //! (assert_c! {
361 //! #include <stdio.h>
362 //!
363 //! int main() {
364 //! printf("Hello, World!\n");
365 //!
366 //! return 0;
367 //! }
368 //! })
369 //! .success()
370 //! .stdout(predicate::eq("Hello, World!\n").normalize());
371 //! }
372 //!
373 //! # fn main() { test_predicates() }
374 //! ```
375
376 pub use predicates::prelude::*;
377}
378
379#[cfg(test)]
380mod tests {
381 use super::predicates::*;
382 use super::*;
383 use crate as inline_c;
384 use std::env::{remove_var, set_var};
385
386 #[test]
387 fn test_c_macro() {
388 (assert_c! {
389 int main() {
390 int x = 1;
391 int y = 2;
392
393 return x + y;
394 }
395 })
396 .failure()
397 .code(3);
398 }
399
400 #[test]
401 fn test_c_macro_with_include() {
402 (assert_c! {
403 #include <stdio.h>
404
405 int main() {
406 printf("Hello, World!\n");
407
408 return 0;
409 }
410 })
411 .success()
412 .stdout(predicate::eq("Hello, World!\n").normalize());
413 }
414
415 #[test]
416 fn test_c_macro_with_env_vars_inlined() {
417 set_var("INLINE_C_RS_CFLAGS", "-D_CRT_SECURE_NO_WARNINGS");
418
419 (assert_c! {
420 // Those are env variables.
421 #inline_c_rs FOO: "bar baz qux"
422 #inline_c_rs HELLO: "World!"
423
424 #include <stdio.h>
425 #include <stdlib.h>
426
427 int main() {
428 const char* foo = getenv("FOO");
429 const char* hello = getenv("HELLO");
430
431 if (NULL == foo || NULL == hello) {
432 return 1;
433 }
434
435 printf("FOO is set to `%s`\n", foo);
436 printf("HELLO is set to `%s`\n", hello);
437
438 return 0;
439 }
440 })
441 .success()
442 .stdout(
443 predicate::eq(
444 "FOO is set to `bar baz qux`\n\
445 HELLO is set to `World!`\n",
446 )
447 .normalize(),
448 );
449
450 remove_var("INLINE_C_RS_CFLAGS");
451 }
452
453 #[test]
454 fn test_c_macro_with_env_vars_from_env_vars() {
455 // Define env vars through env vars.
456 set_var("INLINE_C_RS_FOO", "bar baz qux");
457 set_var("INLINE_C_RS_HELLO", "World!");
458 set_var("INLINE_C_RS_CFLAGS", "-D_CRT_SECURE_NO_WARNINGS");
459
460 (assert_c! {
461 #include <stdio.h>
462 #include <stdlib.h>
463
464 int main() {
465 const char* foo = getenv("FOO");
466 const char* hello = getenv("HELLO");
467
468 if (NULL == foo || NULL == hello) {
469 return 1;
470 }
471
472 printf("FOO is set to `%s`\n", foo);
473 printf("HELLO is set to `%s`\n", hello);
474
475 return 0;
476 }
477 })
478 .success()
479 .stdout(
480 predicate::eq(
481 "FOO is set to `bar baz qux`\n\
482 HELLO is set to `World!`\n",
483 )
484 .normalize(),
485 );
486
487 remove_var("INLINE_C_RS_FOO");
488 remove_var("INLINE_C_RS_HELLO");
489 remove_var("INLINE_C_RS_CFLAGS");
490 }
491
492 #[cfg(nightly)]
493 #[test]
494 fn test_c_macro_with_define() {
495 (assert_c! {
496 #define sum(a, b) ((a) + (b))
497
498 int main() {
499 return !(sum(1, 2) == 3);
500 }
501 })
502 .success();
503 }
504}