# cinrs — write C inside Rust
[](https://github.com/tanakh/cinrs/actions/workflows/ci.yml)
[](https://crates.io/crates/cinrs)
[](https://docs.rs/cinrs)
`cinrs` is a procedural macro that takes C source written directly in a Rust
file — C89 to C23, with the GNU extensions — translates it to Rust while the
crate compiles, and hands you what it defines as ordinary Rust items.
```rust
use cinrs::c99;
c99! {
#include <stdio.h>
typedef struct { double x, y; } Vec2;
double dot(Vec2 a, Vec2 b) {
return a.x * b.x + a.y * b.y;
}
void greet(const char *name) {
printf("hello, %s\n", name);
}
__attribute__((cinrs_safe)) int fact(int n) {
return n == 0 ? 1 : n * fact(n - 1);
}
}
fn main() {
// C functions are foreign functions: calling one is `unsafe`…
let d = unsafe { dot(Vec2 { x: 1.0, y: 2.0 }, Vec2 { x: 3.0, y: 4.0 }) };
unsafe { greet(c"cinrs".as_ptr()) };
// …unless the C says `cinrs_safe`, and then `rustc` checks the body.
println!("dot = {d}, fact(10) = {}", fact(10));
}
```
That is `examples/readme.rs`: `cargo run --example readme`.
## Why
* **No C toolchain in the build.** The macro *is* the compiler: no `build.rs`,
no `cc`, no `bindgen`, nothing to install on the build machine. The standard
headers are bundled, and a call such as `printf` links against the platform's
C library like any other `extern "C"` declaration.
* **What the C defines is Rust.** A `struct` is a `#[repr(C)]` type you can
construct, a function is an `extern "C" fn` you can call, a global is a
`static`. Nothing is declared twice, and nothing crosses an FFI boundary that
the optimiser cannot see through.
* **Errors point at the C.** Every generated token carries the span of the C
token it came from, so `cargo` and rust-analyzer put the caret where the
mistake is — for this crate's diagnostics and for `rustc`'s own:
```text
error: use of undeclared identifier 'j'
--> src/main.rs:7:25
|
7 | total += xs[j];
| ^
```
* **Real C, not a subset.** `goto` (computed ones too), `switch` with
fallthrough, bit-fields with GCC's layout, variable length arrays, variadic
functions, `_Complex`, `_Atomic`, `<threads.h>`, K&R definitions, the whole
preprocessor with `#include` and `#embed` — and the GNU extensions real code
uses: statement expressions, `typeof`, `__attribute__((cleanup))`, nested
functions, `__int128`, `__builtin_*`, and inline assembly, for the operand
kinds `asm!` has. What has no honest translation (`setjmp`) is a located
error, never a guess.
* **The SIMD intrinsics, by name.** `#include <immintrin.h>` and write
`_mm_add_epi32(a, b)`: 6,075 of Intel's intrinsics, SSE through AVX2 and
AVX-512 with FMA, AES, GFNI, VAES, SHA and the BMI scalar ones, mapped
straight onto `core::arch::x86_64`, whose
signatures the bundled headers were generated from. `__m128i` punnes through a
`union` like any 16-byte type, GCC's `a * b`, `v[i]` and `{a, b}` on the vector
types are the intrinsics that do the same, an immediate operand becomes `core::arch`'s
`const` generic, and `__attribute__((target("avx2")))` becomes
`#[target_feature]`. BLAKE3's C implementation — four SIMD kernels and a
`cpuid` dispatcher in inline assembly — builds unedited and picks AVX-512.
* **Measured, not claimed.** 98 % of [c-testsuite], 89–93 % of [GCC's torture
tests][gcc-torture] and 82 % of [Clang's C conformance tests][clang-c-tests]
— about 2,270 cases, every failure listed by name with its reason, and **not
one of them a known bug**. See [Conformance and speed](#conformance-and-speed).
* **As fast as a C compiler.** Over 48 whole programs the median run time is
**1.01×** that of `gcc -O2`, with byte-identical output — the SIMD-intrinsics
entries of the Benchmarks Game included.
* **Safety you can opt into.** Mark a function `[[cinrs::safe]]` (or
`__attribute__((cinrs_safe))`) and it is generated *without* `unsafe`, so
`rustc` checks the translation and Rust calls it as `fact(10)`.
* **`core`-only output.** The expansion names nothing but `core` — a variable
length array needs `alloc`, and a `_Thread_local` object needs `std` — so it
works in a `#![no_std]` crate.
## Installation
```text
cargo add cinrs
```
Rust **1.99** or later — the release that made `c_variadic` stable, which is
what a variadic function *definition* is translated to.
**Linux, macOS and Windows.** Linux (x86-64) is where `cinrs` is developed and
where the whole test suite and the conformance corpora run; macOS (arm64) and
Windows (x86-64, MSVC) build, link and run the examples and the portable tests
on every push. Other targets are compile-checked; see
[Cross-compilation][cross].
Two features: `complex` (on by default) is C's complex types, as
[`num_complex::Complex`](https://docs.rs/num-complex) — `default-features =
false` drops it and its one small dependency; `nightly` moves a diagnostic about
a string-literal body onto a caret inside the literal.
## Using it
### Pick the language
| ISO C | `c89!` | `c99!` | `c11!` | `c17!` | `c23!` |
| with GNU extensions | `gnu89!` | `gnu99!` | `gnu11!` | `gnu17!` | `gnu23!` |
| a whole `.c` file | `include_c89!` | `include_c99!` | `include_c11!` | `include_c17!` | `include_c23!` |
`include_gnu89!` … `include_gnu23!` exist too. A feature of a later revision
used in an earlier block is an error that names the macro to write instead.
Everything spelled with a leading double underscore (`__attribute__`,
`__typeof__`, `__builtin_*`) works in the strict macros as well, exactly as in
`gcc -std=c99`.
### Three ways to write the C
```rust,ignore
cinrs::c99! { int twice(int x) { return 2 * x; } } // raw tokens
cinrs::c99! { r#" double eight(void) { return 0x1p3; } "# } // a string literal
cinrs::include_c99!("vendor/parser.c"); // a file
```
Raw tokens give the best diagnostics. The few things Rust's lexer refuses —
hexadecimal floats, `'ab'`, `L"…"`, `\` line continuations, `##` — go in a
string literal, and C that already lives in a file goes in whole. Details:
[input forms][input-forms], [including a C file][include-c].
### Calling it from Rust
* Functions are `pub unsafe extern "C" fn`; [safe functions][safe-functions]
drop the `unsafe`.
* A C name that is a Rust keyword is a raw identifier: `int match(int)` is
called as `r#match(1)`. [More on names][names].
* A bit-field has no address, so it is a pair of methods: `h.length()` and
`h.set_length(200)`.
* Each invocation is one translation unit, expanded into a private module that
is glob re-exported; an ordinary Rust `mod` around the invocation gives its
items a path (`packet::Header`). `#pragma cinrs export` gives its functions
real C symbols, so another block — or a C library — can call them.
### Headers
The ISO C standard headers are bundled and written against a model of the
target, so a block means the same thing on every machine. Your own headers are
found next to the `.rs` file (`#pragma cinrs include_path "…"` adds
directories), and editing one rebuilds the crate. POSIX and the platform's own
headers — `<unistd.h>`, `<pthread.h>`, `struct stat`, `DIR` — are one pragma
away:
```c
#pragma cinrs system_include
#include <unistd.h>
#include <sys/stat.h>
```
See [system headers][system-headers], and [the pragma reference][pragmas] for
all eight `#pragma cinrs` options and the environment variables that go with
them.
### Calling a C library
A header's declarations are the binding: a function a header declares and the
unit does not define is callable from Rust under its own C name, and so are the
header's types.
```rust,ignore
mod z {
cinrs::c99! {
#pragma cinrs system_include
#pragma cinrs link "z"
#include <zlib.h>
/* `deflateInit` is a macro, so it needs a line of C — written here. */
int z_deflate_init(z_stream *s, int level) { return deflateInit(s, level); }
enum { ZDEMO_OK = Z_OK };
}
}
// unsafe { z::crc32(0, buf.as_ptr(), buf.len() as z::uInt) }
```
More in [calling a C library from Rust][calling-c].
### Cross-compilation
`sizeof`, layouts and `#if` are worked out while the macro expands, from a
model of the target. A procedural macro cannot ask `rustc` what the target is,
so a crate that is cross-compiled says so from its build script:
```rust,no_run
// build.rs
fn main() {
println!("cargo:rustc-env=CINRS_TARGET={}", std::env::var("TARGET").unwrap());
}
```
Without it a cross build fails a compile-time assertion instead of computing
the wrong thing. [Cross-compilation][cross] has the supported targets.
## What is supported
[What works][features] is the tour, construct by construct.
[C standard status][c-status] is the table — every feature of C89 to C23 with
its state, in the style of Clang's `c_status` — and [GNU
extensions][gnu-extensions] is the same for GCC's.
## Limitations
The ones most likely to matter; [the full list][limitations] has the rest.
* Not supported, each as a located error: `setjmp`/`longjmp`, the memory
operands and `asm goto` of inline assembly (which is otherwise
`core::arch::asm!`, x86 only), the GNU vector extensions (the Intel
intrinsics are the SIMD that is here),
`_BitInt`, `_Imaginary`, an `_Atomic` aggregate.
* `long double` is `double`.
* The SIMD intrinsics are x86's, and only the baseline instruction set is
predefined: a procedural macro cannot see `-C target-feature`, so `#ifdef
__AVX2__` and `#ifdef __AVX512F__` are false (until a `#pragma GCC
target("avx2")` defines them, as GCC's does) and
`__builtin_cpu_supports("avx2")` is the question to ask. No MMX.
* Variable length arrays and `alloca` live on the heap, in a bump arena per
function call (Rust cannot move the stack pointer); what the C can observe is
unchanged.
* `va_arg` of a `struct` works for records up to sixteen bytes on x86-64
System V only.
* Each invocation is its own translation unit: two blocks that include one
header get two distinct Rust types for each `struct` in it.
* The platform's include directories are not searched unless the unit asks.
## Conformance and speed
| [c-testsuite] — whole programs with expected output | **215 of 218 (98.6 %)** | `c99!` |
| [GCC's C torture tests][gcc-torture] — 1,776 self-checking programs | **1,643 of 1,769 (92.9 %)** | `gnu11!` |
| [Clang's C conformance tests][clang-c-tests] — what must be *refused*, line by line | **167 of 203 (82.3 %)** | per test |
| glibc's own headers through the front end | **66 of 67** | `gnu11!`, `c11!` |
"Correct" means the case passed, or the entry point is required to refuse it
and did. Every remaining case is listed by name as unimplemented or not
planned — **not one is tagged as a bug**.
[The conformance suites][testsuites] says how they are run.
[Benchmarks][benchmarks]: 48 whole C programs — the single-threaded C entries
of the Benchmarks Game, including the eight written with SIMD intrinsics,
Dhrystone, Whetstone and two dozen kernels that isolate one construct each —
built as `gcc -O2`, `clang -O2` and a `cinrs` block under
`rustc -C opt-level=3`. The median `cinrs`/`gcc` ratio is **1.01×**, 42 of the
48 are within 10 % of `gcc` or faster, and every output is identical across the
three builds. A `goto` costs nothing: an outward one is a labelled `break` or
`continue`, and anything else is read back into loops and branches by a
relooper, so an interpreter loop written as a `switch` full of `goto`s — the
SQLite VDBE, say — runs at the speed `gcc` gives it.
## Documentation
| [What works][features] | the language, the preprocessor, the extensions, construct by construct |
| [What the C becomes][translation] | what is generated for each construct — signatures, accessors, the module — for Rust code on the other side |
| [Limitations][limitations] | what is refused, and what differs from a C compiler |
| [C standard status][c-status] · [GNU extensions][gnu-extensions] | feature tables |
| [Pragmas][pragmas] | `#pragma cinrs …`, the other pragmas, the attributes, the environment variables |
| [Including a C file][include-c] | `include_c99!` and its siblings |
| [System headers][system-headers] | `/usr/include` and what glibc's headers do here |
| [Cross-compilation][cross] · [`no_std`][no-std] | targets and data models; what the expansion needs |
| [Conformance][testsuites] · [Benchmarks][benchmarks] · [Real programs][real-programs] | how the numbers above are measured; SQLite, BLAKE3, xxHash and CRoaring against `gcc -O2` and `clang -O2` |
| [API documentation](https://docs.rs/cinrs) | the macro reference: `c99!` and each of its siblings |
## How it works
The macro recovers the C source text of its own invocation (by slicing the
`.rs` file, or by decoding the string literal) together with a map from byte
offsets back to `proc_macro2` spans, then runs a C front end over it — lexer,
preprocessor, parser, semantic analysis with C's conversion rules made
explicit — and emits Rust, `c2rust`-style: `#[repr(C)]` records, raw pointers,
wrapping arithmetic where C defines wrap-around, `pub unsafe extern "C" fn` for
each function. Every token it emits is stamped with the span of the C it came
from, which is what makes the errors land where they should.
`0.1` means the macro surface may still change. The companion crates
`cinrs-core`, `cinrs-macros` and `cinrs-rt` are implementation details of this
one and carry no stability promise.
## License
Licensed under either of
* [Apache License, Version 2.0](https://github.com/tanakh/cinrs/blob/master/LICENSE-APACHE)
* [MIT license](https://github.com/tanakh/cinrs/blob/master/LICENSE-MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
[c-testsuite]: https://github.com/tanakh/cinrs/blob/master/doc/c-testsuite.md
[gcc-torture]: https://github.com/tanakh/cinrs/blob/master/doc/gcc-torture.md
[clang-c-tests]: https://github.com/tanakh/cinrs/blob/master/doc/clang-c-tests.md
[testsuites]: https://github.com/tanakh/cinrs/blob/master/doc/testsuites.md
[benchmarks]: https://github.com/tanakh/cinrs/blob/master/doc/benchmarks.md
[real-programs]: https://github.com/tanakh/cinrs/blob/master/doc/real-programs.md
[features]: https://github.com/tanakh/cinrs/blob/master/doc/features.md
[translation]: https://github.com/tanakh/cinrs/blob/master/doc/translation.md
[input-forms]: https://github.com/tanakh/cinrs/blob/master/doc/features.md#input-forms
[safe-functions]: https://github.com/tanakh/cinrs/blob/master/doc/features.md#safe-functions
[names]: https://github.com/tanakh/cinrs/blob/master/doc/features.md#names-rust-would-not-take
[calling-c]: https://github.com/tanakh/cinrs/blob/master/doc/features.md#calling-a-c-library-from-rust
[limitations]: https://github.com/tanakh/cinrs/blob/master/doc/limitations.md
[c-status]: https://github.com/tanakh/cinrs/blob/master/doc/c-status.md
[gnu-extensions]: https://github.com/tanakh/cinrs/blob/master/doc/gnu-extensions.md
[pragmas]: https://github.com/tanakh/cinrs/blob/master/doc/pragmas.md
[include-c]: https://github.com/tanakh/cinrs/blob/master/doc/include-c.md
[system-headers]: https://github.com/tanakh/cinrs/blob/master/doc/system-headers.md
[cross]: https://github.com/tanakh/cinrs/blob/master/doc/cross-compilation.md
[no-std]: https://github.com/tanakh/cinrs/blob/master/doc/no-std.md