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.
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 | |
|---|---|
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 Spice standard library ships ready-made bindings for several popular C libraries under std/bindings/,
including libcurl, GTK4, and LLVM. Check those files for complete, production-ready examples of the
patterns shown in this tutorial.