# Exporting Zig to C

### Exporting Zig to C

Zig can call C, but C can also call Zig.

This is useful when you want to add Zig code to an existing C project, write a library in Zig with a C API, or replace part of a C codebase gradually.

The key tool is `export`.

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

This defines a Zig function that is visible to the linker with a C-compatible symbol name.

A C file can declare it like this:

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

Then C can call it:

```c
int result = add(10, 20);
```

#### What `export` Means

A normal Zig function is only meant for Zig code unless you expose it.

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

This function is not automatically part of a C API.

An exported function is different:

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

The word `export` tells Zig:

Make this function available as a symbol in the compiled output.

Use a calling convention suitable for external code.

Keep the name visible so other object files or programs can link to it.

For C interop, exported functions should use C-compatible types.

#### Use C-Compatible Types

C does not understand Zig-specific types such as slices, error unions, optionals, or normal Zig structs.

Do not export this directly:

```zig
export fn badRead(buffer: []u8) void {
    _ = buffer;
}
```

A C compiler does not know what `[]u8` means. A Zig slice is a pair of values: pointer and length. C needs that written explicitly.

Export this instead:

```zig
export fn read_buffer(buffer: [*]u8, len: usize) void {
    const slice = buffer[0..len];

    for (slice) |*byte| {
        byte.* = 0;
    }
}
```

C can declare it like this:

```c
void read_buffer(unsigned char *buffer, size_t len);
```

At the boundary, use C-shaped APIs.

Inside the Zig function, you can convert the C-shaped data into safer Zig values.

#### Pointer Plus Length Instead of Slices

Zig programmers often use slices:

```zig
[]u8
[]const u8
```

C usually uses pointer plus length:

```c
unsigned char *buffer, size_t len
```

So an exported Zig API should look like C:

```zig
export fn fill(buffer: [*]u8, len: usize) c_int {
    if (len == 0) {
        return 0;
    }

    const slice = buffer[0..len];

    for (slice, 0..) |*byte, i| {
        byte.* = @intCast(i % 256);
    }

    return 0;
}
```

C declaration:

```c
int fill(unsigned char *buffer, size_t len);
```

C call:

```c
unsigned char buffer[16];

int rc = fill(buffer, sizeof(buffer));
```

The C side sees a normal C function. The Zig side still gets to work with a slice after the boundary conversion.

#### Return Error Codes, Not Zig Errors

Zig error unions are excellent inside Zig:

```zig
fn openFile() !void {
    return error.OpenFailed;
}
```

But C does not understand Zig error unions.

Do not export this as a C API:

```zig
export fn bad_open() !void {
    return error.OpenFailed;
}
```

Instead, return a C-style status code:

```zig
export fn open_resource() c_int {
    openResource() catch {
        return -1;
    };

    return 0;
}

fn openResource() !void {
    return error.OpenFailed;
}
```

C declaration:

```c
int open_resource(void);
```

C usage:

```c
if (open_resource() != 0) {
    /* handle error */
}
```

This pattern is common.

Inside Zig, use Zig errors.

At the C boundary, translate them into integer status codes.

#### Returning Values Through Output Parameters

C APIs often return detailed data through output parameters.

For example, instead of returning a Zig struct directly, export a function like this:

```zig
export fn get_size(width: *c_int, height: *c_int) c_int {
    width.* = 800;
    height.* = 600;
    return 0;
}
```

C declaration:

```c
int get_size(int *width, int *height);
```

C call:

```c
int width = 0;
int height = 0;

if (get_size(&width, &height) == 0) {
    /* use width and height */
}
```

This style is easy for C callers to understand.

But you should validate pointers if null is possible.

```zig
export fn get_size(width: ?*c_int, height: ?*c_int) c_int {
    const w = width orelse return -1;
    const h = height orelse return -1;

    w.* = 800;
    h.* = 600;

    return 0;
}
```

C can pass null. Zig should protect itself if the API allows that.

#### Exporting Structs

If C needs to use a struct defined by Zig, the struct must have C-compatible layout.

Use `extern struct`.

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

export fn make_point(x: c_int, y: c_int) Point {
    return Point{
        .x = x,
        .y = y,
    };
}
```

C declaration:

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

Point make_point(int x, int y);
```

Both sides must agree on field order, field types, alignment, and padding.

For public C APIs, you should provide a C header file that matches the exported Zig functions and structs.

#### Exporting Opaque Handles

For more complex Zig objects, do not expose the full struct layout to C.

Use an opaque handle style.

C header:

```c
typedef struct Database Database;

Database *database_open(const char *path);
void database_close(Database *db);
int database_exec(Database *db, const char *sql);
```

C does not know the fields of `Database`. It only holds a pointer.

In Zig, you can define the real object privately:

```zig
const Database = struct {
    // internal fields
};
```

Then export functions that return and accept pointers.

A simplified example:

```zig
const std = @import("std");

const Database = struct {
    id: u32,
};

var global_db = Database{ .id = 1 };

export fn database_open(path: [*:0]const u8) ?*Database {
    _ = path;
    return &global_db;
}

export fn database_close(db: ?*Database) void {
    _ = db;
}

export fn database_exec(db: ?*Database, sql: [*:0]const u8) c_int {
    const real_db = db orelse return -1;
    _ = real_db;
    _ = sql;

    return 0;
}
```

This example uses a global object only to keep the code small. Real libraries usually allocate and free objects properly.

The design idea is important: expose a pointer handle to C, keep the real Zig implementation private.

#### Allocating Objects for C

If Zig creates an object and C later destroys it, you need a clear allocation policy.

Example:

```zig
const std = @import("std");

const Thing = struct {
    value: c_int,
};

const allocator = std.heap.c_allocator;

export fn thing_create(value: c_int) ?*Thing {
    const thing = allocator.create(Thing) catch return null;
    thing.* = Thing{ .value = value };
    return thing;
}

export fn thing_destroy(thing: ?*Thing) void {
    const ptr = thing orelse return;
    allocator.destroy(ptr);
}

export fn thing_get_value(thing: ?*Thing) c_int {
    const ptr = thing orelse return 0;
    return ptr.value;
}
```

C header:

```c
typedef struct Thing Thing;

Thing *thing_create(int value);
void thing_destroy(Thing *thing);
int thing_get_value(Thing *thing);
```

C usage:

```c
Thing *thing = thing_create(42);

if (thing != NULL) {
    int value = thing_get_value(thing);
    thing_destroy(thing);
}
```

The pair is explicit:

```text
thing_create
thing_destroy
```

This is essential. If Zig allocates something for C, your API must also provide the matching destroy function.

#### Exporting Strings

Returning strings across the C boundary needs care.

Do not return a Zig slice:

```zig
export fn bad_name() []const u8 {
    return "zig";
}
```

C cannot use that type.

For a static string, return a null-terminated pointer:

```zig
export fn library_name() [*:0]const u8 {
    return "myziglib";
}
```

C declaration:

```c
const char *library_name(void);
```

C must not free this string because it points to static memory.

For dynamically allocated strings, provide a free function:

```zig
const std = @import("std");

const allocator = std.heap.c_allocator;

export fn make_message() ?[*:0]u8 {
    const msg = allocator.dupeZ(u8, "hello from zig") catch return null;
    return msg.ptr;
}

export fn free_message(msg: ?[*:0]u8) void {
    const ptr = msg orelse return;

    const slice = std.mem.span(ptr);
    allocator.free(slice);
}
```

The public rule must be clear:

C receives the string from `make_message`.

C must release it with `free_message`.

Do not ask C callers to guess.

#### Symbol Names

By default, `export fn add` exposes a symbol named `add`.

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

A C file can declare:

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

For public libraries, use a prefix to avoid name collisions.

Better:

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

C declaration:

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

C has a flat global symbol namespace. Prefixes are normal and useful.

#### Writing a Header for C Users

C users need a header file.

For the exported function:

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

write:

```c
#ifndef MYLIB_H
#define MYLIB_H

int mylib_add(int a, int b);

#endif
```

For opaque types:

```c
#ifndef MYLIB_H
#define MYLIB_H

typedef struct Thing Thing;

Thing *thing_create(int value);
void thing_destroy(Thing *thing);
int thing_get_value(Thing *thing);

#endif
```

Keep this header simple. Use C types, not Zig types. The header is the contract for C callers.

#### Building a Zig Library for C

Instead of building an executable, you may build a library.

A simplified `build.zig` shape:

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const lib = b.addStaticLibrary(.{
        .name = "mylib",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/mylib.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    lib.linkLibC();

    b.installArtifact(lib);
}
```

This builds a static library.

A C program can then link against that library, along with the header you provide.

#### Calling Convention

For C interop, functions crossing the boundary must use the C calling convention.

`export fn` is commonly used for exported C ABI functions.

For callback functions passed to C, you often write:

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

The calling convention matters because caller and callee must agree on how arguments and return values move through registers and stack.

If the calling convention is wrong, the program may crash.

#### Global State

Exported functions are often called from code you do not control.

That means you should be careful with global state.

This is simple:

```zig
var counter: c_int = 0;

export fn increment_counter() c_int {
    counter += 1;
    return counter;
}
```

But it may fail in threaded programs. Two threads could call the function at the same time.

For public libraries, document thread-safety rules clearly.

Possible rules:

```text
This function is thread-safe.
This object must only be used from one thread.
The caller must provide synchronization.
The library uses internal locking.
```

C APIs often live for many years. Ambiguous thread-safety rules become bugs.

#### Panics Must Not Cross the C Boundary

A Zig panic should not escape through a C API.

An exported function should return an error code instead of panicking when possible.

Risky:

```zig
export fn divide(a: c_int, b: c_int) c_int {
    return @divTrunc(a, b);
}
```

If `b` is zero, this can panic or trap depending on build mode and target behavior.

Safer:

```zig
export fn divide(a: c_int, b: c_int, out: ?*c_int) c_int {
    const result_ptr = out orelse return -1;

    if (b == 0) {
        return -2;
    }

    result_ptr.* = @divTrunc(a, b);
    return 0;
}
```

C usage:

```c
int result = 0;
int rc = divide(10, 2, &result);

if (rc == 0) {
    /* use result */
}
```

This is more verbose, but it gives C callers a predictable contract.

#### Keep the C API Small

When exporting Zig to C, resist the urge to expose everything.

A good C API is small, stable, and boring.

Prefer:

```text
create
destroy
get
set
run
read
write
```

Avoid exposing Zig internals. Avoid leaking allocator details unless the caller must control allocation. Avoid complex generic behavior at the C boundary.

The Zig side can be rich and expressive.

The C side should be plain and stable.

#### What to Remember

Use `export fn` to make Zig functions callable from C.

Use C-compatible types at the boundary.

Do not export Zig slices, error unions, optionals, or ordinary Zig structs directly.

Represent slices as pointer plus length.

Represent errors as status codes.

Represent complex objects as opaque handles.

Provide matching destroy functions for objects allocated by Zig.

Return C strings only when ownership is clear.

Write a C header that matches your exported Zig API.

Keep exported APIs small, explicit, and stable.

