C+
FFI

C ABI consumer

This example is the docs/examples/c_consumer reference from the C+ source tree. It backs the systems-language claim that C+ emits normal libraries that C programs can call.

The recipe has two parts:

docs/examples/c_consumer/
├── mathlib/
│   ├── Cplus.toml
│   └── src/lib.cplus
└── c_user/
    ├── c_user.c
    └── Makefile

mathlib is a C+ library. c_user is a C program that includes the generated mathlib.h, links the built library, calls every export, and exits nonzero if any ABI shape fails.

ABI coverage

Export ABI class C-side shape
add(i32, i32) -> i32 scalar arguments and return int32_t add(int32_t, int32_t)
square(Point) -> i32 8-byte #[repr(C)] aggregate argument int32_t square(Point)
make_point(...) -> Point 8-byte aggregate return Point make_point(...)
sum_pair(Pair) -> i64 16-byte aggregate argument int64_t sum_pair(Pair)
make_pair(...) -> Pair 16-byte aggregate return Pair make_pair(...)
sum_triple(Triple) -> i64 larger-than-16-byte aggregate argument int64_t sum_triple(Triple)
make_triple(...) -> Triple larger-than-16-byte aggregate return C sret result shape
color_index(Color) -> i32 plain enum C enum constants
fill_with(*i32, i32) raw pointer out parameter int32_t *
apply(fn(i32)->i32, i32) C function pointer argument int32_t (*)(int32_t)

C+ exports

The aggregate types that cross the C boundary are explicitly #[repr(C)]:

#[repr(C)]
pub struct Point {
    pub x: i32,
    pub y: i32,
}

pub extern fn square(p: Point) -> i32 {
    return p.x * p.x + p.y * p.y;
}

pub extern fn apply(f: fn(i32) -> i32, x: i32) -> i32 {
    return f(x);
}

The C consumer passes a normal C callback through the generated header:

static int32_t double_it(int32_t x) { return x * 2; }

int32_t y = apply(double_it, 21);
printf("apply(double_it, 21) = %d (expected 42)\n", y);
if (y != 42) failures++;

Reproduce

From docs/examples/c_consumer/c_user in the C+ source tree:

make check

Expected result: the Makefile builds mathlib, builds the C program with clang, runs it, prints each expected value, and finishes with 0 failure(s).

That is the useful proof boundary: a separate C compiler consumes the C+ library through generated C-facing declarations, not through a C+ test harness.


‹ Back to all examples