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}