# ABI Compatibility

### ABI Compatibility

ABI means Application Binary Interface.

An API describes how source code calls something. An ABI describes how compiled code calls something.

When Zig calls C, source code is only half of the story. The final machine code must agree on details such as:

| ABI detail | What it controls |
|---|---|
| Calling convention | How function arguments are passed |
| Return values | How results come back |
| Struct layout | How fields are placed in memory |
| Alignment | Where values may legally live in memory |
| Symbol names | What function names look like to the linker |
| Integer sizes | How large C types are on the target |
| Stack rules | Who cleans up and how stack memory is used |

If Zig and C disagree on these details, the program may compile but still behave incorrectly.

#### API vs ABI

A C header gives you an API.

```c
int add(int a, int b);
```

This says:

```text
There is a function named add.
It takes two int values.
It returns an int.
```

The ABI answers lower-level questions:

```text
Which registers hold the arguments?
How large is int on this target?
Where does the return value go?
What symbol name does the linker search for?
```

You usually do not write ABI rules by hand. The compiler and target platform handle them. But when Zig and C interact, you must use types and declarations that let both compilers agree.

#### Calling Convention

A calling convention is a rule for calling functions at the machine-code level.

For C interop, use the C calling convention.

When you import a C function with `@cImport`, Zig knows it is a C function.

When you export a Zig function to C, use C-compatible exports:

```zig
export fn add(a: c_int, b: c_int) c_int {
    return a + b;
}
```

For callbacks passed from Zig to C, write the calling convention explicitly:

```zig
fn callback(value: c_int) callconv(.c) void {
    _ = value;
}
```

Then pass it to C:

```zig
c.run_callback(callback);
```

The `callconv(.c)` part matters. It tells Zig to generate a function that C can call correctly.

#### C Type Sizes Depend on the Target

C type sizes are not identical on every platform.

For example, `int` is commonly 32 bits, but `long` differs across common systems.

| C type | Common Linux x86_64 | Common Windows x64 |
|---|---:|---:|
| `int` | 32 bits | 32 bits |
| `long` | 64 bits | 32 bits |
| `long long` | 64 bits | 64 bits |
| pointer | 64 bits | 64 bits |

This is why Zig provides C-compatible type names:

```zig
c_int
c_long
c_longlong
c_uint
c_ulong
```

Do not assume that C `long` is always the same as Zig `i64`.

At the C boundary, prefer C-compatible types:

```zig
export fn take_long(x: c_long) void {
    _ = x;
}
```

Inside Zig code, you can convert to a fixed-width type when that is what your logic needs:

```zig
const value: i64 = @intCast(x);
```

#### Struct Layout

A normal Zig struct uses Zig layout rules.

```zig
const Point = struct {
    x: i32,
    y: i32,
};
```

A C-compatible struct must use C layout rules:

```zig
const Point = extern struct {
    x: c_int,
    y: c_int,
};
```

Use `extern struct` when the struct crosses the C boundary.

For example, if C expects this:

```c
typedef struct Point {
    int x;
    int y;
} Point;
```

then Zig should match it with:

```zig
const Point = extern struct {
    x: c_int,
    y: c_int,
};
```

The field order and field types must match.

#### Padding and Alignment

C structs often contain padding.

Consider this C struct:

```c
typedef struct Example {
    char a;
    int b;
} Example;
```

You might think it uses 5 bytes: 1 byte for `char`, 4 bytes for `int`.

On many targets, it uses 8 bytes because `int` needs stronger alignment.

A possible layout:

```text
byte 0      a
byte 1-3    padding
byte 4-7    b
```

Zig must match this layout if the struct is passed to C.

That is what `extern struct` is for.

You can inspect size and alignment in Zig:

```zig
std.debug.print("size = {}\n", .{@sizeOf(Example)});
std.debug.print("align = {}\n", .{@alignOf(Example)});
```

For imported C structs, Zig uses the C layout from the target.

#### Packed Structs Are Special

C code sometimes uses packed structs to remove padding.

```c
#pragma pack(push, 1)
typedef struct Header {
    unsigned char tag;
    unsigned int len;
} Header;
#pragma pack(pop)
```

Packed structs are common in binary formats, wire protocols, and hardware-facing code.

Do not treat them as ordinary structs.

In Zig, `packed struct` has bit-level layout rules, while `extern struct` follows C ABI layout rules. They solve different problems.

Use `extern struct` for normal C ABI structs.

Use `packed struct` only when you need exact packed layout and know the target representation.

#### Enums

C enums are usually represented as integer types, but the exact underlying type can depend on the C compiler, target, and flags.

If you import a C enum through `@cImport`, Zig handles the imported representation.

If you manually define a Zig type for a C enum boundary, be careful. For stable public C APIs, integer constants are often simpler.

C:

```c
#define MODE_READ  1
#define MODE_WRITE 2
```

Zig boundary:

```zig
export fn open_mode(mode: c_int) c_int {
    _ = mode;
    return 0;
}
```

Inside Zig, you can translate that integer into a safer Zig enum if you want.

#### Symbol Names

The linker does not call functions by source-code syntax. It uses symbols.

This Zig function:

```zig
export fn mylib_add(a: c_int, b: c_int) c_int {
    return a + b;
}
```

exports a symbol named:

```text
mylib_add
```

A C header can declare:

```c
int mylib_add(int a, int b);
```

Then the C object file and Zig object file can link.

C has a mostly flat symbol namespace, so use prefixes for library APIs:

```text
mylib_create
mylib_destroy
mylib_read
mylib_write
```

This avoids collisions with other libraries.

#### Name Mangling and C++

C++ changes symbol names through name mangling.

C++ function:

```cpp
int add(int a, int b);
```

may not export a symbol literally named `add`.

To make a C-compatible function from C++, use `extern "C"`:

```cpp
extern "C" int add(int a, int b);
```

Then Zig can treat it like a C function.

This chapter is about C ABI compatibility. C++ ABI compatibility is much more complicated because of constructors, destructors, templates, exceptions, overloading, RTTI, and standard library differences.

When mixing Zig with C++, prefer a small `extern "C"` wrapper.

#### Nullability Is Partly a Convention

C pointers can be null unless the documentation says otherwise.

Zig can represent nullable pointers with optionals:

```zig
?*T
```

When exporting to C, use optional pointers if C may pass null:

```zig
export fn destroy_handle(handle: ?*Handle) void {
    const h = handle orelse return;
    allocator.destroy(h);
}
```

When a pointer must not be null, a non-optional pointer documents that expectation on the Zig side:

```zig
fn useHandle(handle: *Handle) void {
    _ = handle;
}
```

But C can still pass invalid data. A C caller can violate your contract.

For public C APIs, defensive null checks are often worth the small cost.

#### Ownership Is Not in the ABI

The ABI tells compiled code how to pass values. It does not tell you who owns memory.

These two C functions may have the same ABI shape:

```c
char *get_name(void);
char *make_name(void);
```

But their ownership rules may differ.

`get_name` might return a borrowed pointer.

`make_name` might allocate memory that the caller must free.

The ABI cannot tell you that. The header may not tell you either. You need documentation and naming conventions.

Good C APIs make ownership visible:

```c
char *mylib_message_create(void);
void mylib_message_destroy(char *message);
```

The matching create and destroy functions give callers a clear contract.

#### ABI Stability

ABI stability means compiled code can keep working across versions.

If you publish a C library, changing these can break ABI compatibility:

| Change | ABI risk |
|---|---|
| Reordering struct fields | High |
| Changing field types | High |
| Removing exported functions | High |
| Changing function parameters | High |
| Changing return type | High |
| Changing enum size or representation | Medium to high |
| Adding fields to public structs | Often high |
| Adding new functions | Usually safe |

This is why many C libraries use opaque structs.

Instead of exposing fields:

```c
typedef struct Database {
    int fd;
    int flags;
} Database;
```

they expose only the name:

```c
typedef struct Database Database;
```

Then callers use pointers:

```c
Database *database_open(const char *path);
void database_close(Database *db);
```

The library can change the internal struct later without breaking callers.

#### Stable Public ABI Pattern

For a stable C ABI from Zig, prefer this pattern:

```c
typedef struct MyLibHandle MyLibHandle;

MyLibHandle *mylib_create(void);
void mylib_destroy(MyLibHandle *handle);
int mylib_do_work(MyLibHandle *handle);
```

The Zig implementation can keep the actual struct private:

```zig
const MyLibHandle = struct {
    value: u32,
};

export fn mylib_create() ?*MyLibHandle {
    const handle = allocator.create(MyLibHandle) catch return null;
    handle.* = .{ .value = 0 };
    return handle;
}

export fn mylib_destroy(handle: ?*MyLibHandle) void {
    const h = handle orelse return;
    allocator.destroy(h);
}

export fn mylib_do_work(handle: ?*MyLibHandle) c_int {
    const h = handle orelse return -1;
    h.value += 1;
    return 0;
}
```

C sees only a pointer. Zig keeps the layout private.

This is the safest long-term design.

#### Cross-Compilation and ABI

The ABI depends on the target.

This command builds for one target:

```bash
zig build -Dtarget=x86_64-linux-gnu
```

This builds for another:

```bash
zig build -Dtarget=x86_64-windows-gnu
```

The same C-looking type may have different layout or size across those targets. That is normal.

Use Zig’s target-aware C types and imported headers. Avoid hardcoding assumptions such as:

```text
long is always 64 bits
pointers are always 8 bytes
struct padding is always the same
```

Those assumptions break portability.

#### What to Remember

ABI compatibility is about compiled-code agreement.

Use C-compatible types at the boundary.

Use `extern struct` for structs shared with C.

Use `callconv(.c)` for callbacks called by C.

Use `export fn` for functions C should call.

Do not export Zig-only types directly.

Do not assume C type sizes are the same on every platform.

Keep public C ABIs small and stable.

Use opaque handles when you want long-term compatibility.

The compiler can help with ABI details, but it cannot infer ownership, lifetime, thread safety, or API promises. Those must be designed and documented.

