C/C++ Interoperability
Spice is designed to interoperate seamlessly with C and C++ code. Because Spice compiles to native machine code via LLVM, there is no runtime bridge or conversion layer—calling a C function from Spice (or vice versa) is zero-cost and ABI-compatible.
This tutorial covers the four building blocks of Spice–C interop:
- Declaring external C functions with
ext - Controlling name mangling with attributes
- Linking C libraries with linker-flag attributes
- Exposing Spice functions back to C
Declaring external C functions¶
Use the ext keyword to declare a C function without defining it. Spice resolves the symbol at link time.
| Spice | |
|---|---|
The declaration syntax mirrors normal Spice functions:
f<ReturnType>for functions that return a valuepfor procedures (voidfunctions in C)
To bind malloc and free from the C standard library:
| Spice | |
|---|---|
The heap qualifier
heap on a pointer return type tells the Spice compiler that the memory was heap-allocated by the callee.
It enables the compiler to track the pointer's ownership and lifetime correctly.
Variadic C functions (those declared with ... in C) are supported too:
| Spice | |
|---|---|
Renaming an external symbol¶
ext declarations are never name-mangled—the compiler always uses the plain function name as the linker symbol,
so they are directly compatible with C and extern "C" C++ symbols out of the box. No attribute is needed for
a straightforward binding.
The core.compiler.mangledName attribute lets you give the declaration a different name on the Spice side while
still linking against the original C symbol:
This declares pthreadSelf() in Spice but tells the linker to look for the symbol pthread_self, so the Spice
code can use the more idiomatic camel-case name without any runtime cost.
Type aliases for opaque C types¶
Many C APIs use opaque handles—pointers to internal structs that the caller never dereferences. Model these as
alias byte* in Spice:
The alias keeps the code readable and makes function signatures self-documenting while remaining fully compatible with the C ABI.
Returning structs by value¶
On x86-64 (Linux, macOS and Windows), AArch64 and WebAssembly, structs, that are returned by value, follow the C calling convention of the target, exactly like Clang lowers them: small structs are returned in registers, larger ones via a hidden pointer to memory of the caller. On all other targets, structs are always returned via memory, which can differ from the C calling convention. On the supported targets, external C functions can return structs by value and Spice functions, that return structs, can be called from C:
Passing structs by value
Struct parameters are not lowered to the C calling convention yet. This applies to Spice code calling C functions and to C code calling Spice functions. Pass pointers to structs instead.
Linking an external C library¶
Use the module-level core.linker.flag attribute to pass flags to the linker. Place it at the top of any
.spice file in your project:
| Spice | |
|---|---|
For libraries discovered via pkg-config, you can embed the shell command directly in the flag value. Spice
evaluates backtick expressions at build time:
| Spice | |
|---|---|
When linking statically (--static), Spice calls pkg-config --static, so the flags also include the private
dependencies that the static archive of the library needs.
Platform-specific flags
Use core.linux.linker.flag, core.darwin.linker.flag, and core.windows.linker.flag instead of the
generic core.linker.flag when a library is linked differently across platforms (different flag syntax,
different package names, DLL vs. static lib, etc.).
Multiple flags
You can specify multiple linker flags in a single attribute block by separating them with commas:
Exposing Spice functions to C¶
To call a Spice function from C, mark it public and disable name mangling so the C linker can find the plain
symbol name:
| Spice | |
|---|---|
Compile this to an object file and link it into your C project. On the C side, declare the function with a matching signature:
| C | |
|---|---|
Calling from C++
When the consumer is a C++ translation unit, wrap the declaration in extern "C" to prevent the C++
compiler from mangling the name on its side:
Putting it all together¶
The following example ties together all four concepts to build a small libcurl wrapper that fetches a URL:
Run it with:
| Bash | |
|---|---|
Spice invokes pkg-config at build time to obtain the compiler and linker flags for libcurl, compiles the Spice
source, and links everything in a single step.
Existing bindings
The official Spice packages include ready-made bindings for several
popular C libraries, like libcurl, gtk4 and sqlite, and the standard library ships the LLVM and OpenSSL
bindings under std/bindings/. Check those files for complete, production-ready examples of the patterns shown
in this tutorial.