# Wrapping Existing Libraries

### Wrapping Existing Libraries

Wrapping a C library means building a Zig layer around it.

The C library remains underneath. Zig calls it. But most of your Zig program does not call the C API directly. Instead, your program calls a cleaner Zig API.

This is the usual shape:

```text
Application Zig code
        |
        v
Zig wrapper
        |
        v
C library
```

The wrapper is the boundary. It translates C habits into Zig habits.

C often uses raw pointers, null pointers, integer return codes, manual cleanup, and null-terminated strings. Zig usually prefers slices, optionals, error unions, `deinit`, and explicit ownership.

#### Why Wrap a C Library

You can call C functions directly:

```zig
const rc = c.database_exec(db, "select 1");

if (rc != 0) {
    return error.ExecFailed;
}
```

But if you write this everywhere, your Zig code becomes C-shaped.

A wrapper lets the rest of your code use Zig style:

```zig
try db.exec("select 1");
```

The wrapper has one job: keep C details contained.

#### A Raw C API

Suppose a C library exposes this 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);
const char *database_last_error(Database *db);
```

This is a normal C API.

It uses an opaque pointer:

```c
Database *
```

It uses a null pointer for open failure.

It uses an integer status code for execution failure.

It uses C strings:

```c
const char *
```

Now we import it:

```zig
const c = @cImport({
    @cInclude("database.h");
});
```

Direct use would look like this:

```zig
const db = c.database_open("app.db") orelse return error.OpenFailed;
defer c.database_close(db);

const rc = c.database_exec(db, "create table users(id integer)");

if (rc != 0) {
    return error.ExecFailed;
}
```

This works. But it exposes C details everywhere.

#### A Zig Wrapper Type

A better interface starts with a Zig struct:

```zig
const Database = struct {
    ptr: *c.Database,
};
```

The struct stores the raw C pointer. The field is private by convention because users of the wrapper should not need to touch it.

Now add an `open` function:

```zig
const DatabaseError = error{
    OpenFailed,
    ExecFailed,
};

const Database = struct {
    ptr: *c.Database,

    pub fn open(path: [*:0]const u8) DatabaseError!Database {
        const ptr = c.database_open(path) orelse return error.OpenFailed;

        return Database{
            .ptr = ptr,
        };
    }
};
```

Now callers can write:

```zig
const db = try Database.open("app.db");
```

That already feels more like Zig.

#### Add Cleanup with `deinit`

C APIs often have matching pairs:

```text
open  / close
create / destroy
init / deinit
alloc / free
```

In Zig wrappers, the usual cleanup method is named `deinit`.

```zig
pub fn deinit(self: Database) void {
    c.database_close(self.ptr);
}
```

Now usage becomes:

```zig
const db = try Database.open("app.db");
defer db.deinit();
```

This is a strong pattern.

Acquire the resource.

Immediately schedule cleanup with `defer`.

Use the resource.

#### Add a Method for Work

Now wrap `database_exec`:

```zig
pub fn exec(self: Database, sql: [*:0]const u8) DatabaseError!void {
    const rc = c.database_exec(self.ptr, sql);

    if (rc != 0) {
        return error.ExecFailed;
    }
}
```

Callers now write:

```zig
try db.exec("create table users(id integer)");
```

The raw C status code stays inside the wrapper.

#### Complete First Wrapper

```zig
const c = @cImport({
    @cInclude("database.h");
});

const DatabaseError = error{
    OpenFailed,
    ExecFailed,
};

const Database = struct {
    ptr: *c.Database,

    pub fn open(path: [*:0]const u8) DatabaseError!Database {
        const ptr = c.database_open(path) orelse return error.OpenFailed;

        return Database{
            .ptr = ptr,
        };
    }

    pub fn deinit(self: Database) void {
        c.database_close(self.ptr);
    }

    pub fn exec(self: Database, sql: [*:0]const u8) DatabaseError!void {
        const rc = c.database_exec(self.ptr, sql);

        if (rc != 0) {
            return error.ExecFailed;
        }
    }
};
```

Usage:

```zig
const db = try Database.open("app.db");
defer db.deinit();

try db.exec("create table users(id integer)");
try db.exec("insert into users values (1)");
```

The application code does not see `database_open`, `database_close`, or `database_exec`. It sees `Database.open`, `db.deinit`, and `db.exec`.

#### Handling Error Messages

Many C libraries provide a way to read the last error message.

```c
const char *database_last_error(Database *db);
```

A simple Zig wrapper can expose it:

```zig
pub fn lastError(self: Database) [*:0]const u8 {
    return c.database_last_error(self.ptr);
}
```

Usage:

```zig
db.exec("bad sql") catch |err| {
    std.debug.print("error: {s}\n", .{db.lastError()});
    return err;
};
```

This returns a C string pointer. That may be enough for simple cases.

A more Zig-shaped wrapper may convert it to a slice:

```zig
pub fn lastError(self: Database) []const u8 {
    const ptr = c.database_last_error(self.ptr);
    return std.mem.span(ptr);
}
```

`std.mem.span` reads a sentinel-terminated string and returns a slice.

Use this only when the C function never returns null. If it can return null, handle that:

```zig
pub fn lastError(self: Database) []const u8 {
    const ptr = c.database_last_error(self.ptr) orelse return "";
    return std.mem.span(ptr);
}
```

#### Accepting Zig Slices

The wrapper above still requires null-terminated strings:

```zig
pub fn exec(self: Database, sql: [*:0]const u8) DatabaseError!void
```

That works for string literals:

```zig
try db.exec("select 1");
```

But many Zig strings are slices:

```zig
const sql: []const u8 = getSqlFromSomewhere();
```

A C function that expects `const char *` needs a null-terminated string. The wrapper can allocate a temporary copy:

```zig
pub fn exec(
    self: Database,
    allocator: std.mem.Allocator,
    sql: []const u8,
) DatabaseError!void {
    const sql_z = allocator.dupeZ(u8, sql) catch return error.OutOfMemory;
    defer allocator.free(sql_z);

    const rc = c.database_exec(self.ptr, sql_z.ptr);

    if (rc != 0) {
        return error.ExecFailed;
    }
}
```

Now update the error set:

```zig
const DatabaseError = error{
    OpenFailed,
    ExecFailed,
    OutOfMemory,
};
```

Callers can pass ordinary slices:

```zig
try db.exec(allocator, sql);
```

This is more flexible, but it requires an allocator. That is a reasonable tradeoff when conversion needs memory.

#### Keep Ownership Explicit

A wrapper must make ownership clear.

If the C library returns borrowed memory, do not free it.

If the C library returns owned memory, provide a Zig cleanup path.

Example C API:

```c
char *database_get_allocated_message(Database *db);
void database_free_message(char *message);
```

A Zig wrapper might expose a small owned type:

```zig
const Message = struct {
    ptr: [*:0]u8,

    pub fn deinit(self: Message) void {
        c.database_free_message(self.ptr);
    }

    pub fn bytes(self: Message) []const u8 {
        return std.mem.span(self.ptr);
    }
};
```

Then:

```zig
pub fn getMessage(self: Database) DatabaseError!Message {
    const ptr = c.database_get_allocated_message(self.ptr) orelse {
        return error.OutOfMemory;
    };

    return Message{
        .ptr = ptr,
    };
}
```

Usage:

```zig
const msg = try db.getMessage();
defer msg.deinit();

std.debug.print("{s}\n", .{msg.bytes()});
```

This wrapper makes the cleanup rule visible.

#### Avoid Leaking Raw Pointers

This is weak:

```zig
pub fn raw(self: Database) *c.Database {
    return self.ptr;
}
```

It may be necessary sometimes, but it weakens the wrapper. Once callers can freely access the raw pointer, they can bypass your safety rules.

Prefer not to expose raw handles unless there is a strong reason.

If you do expose one, name it clearly:

```zig
pub fn rawHandle(self: Database) *c.Database {
    return self.ptr;
}
```

That tells readers they are leaving the safer wrapper layer.

#### Convert C Status Codes Once

Do not repeat this everywhere:

```zig
if (rc != 0) return error.ExecFailed;
```

Create a helper:

```zig
fn checkExec(rc: c_int) DatabaseError!void {
    if (rc != 0) {
        return error.ExecFailed;
    }
}
```

Then:

```zig
pub fn exec(self: Database, sql: [*:0]const u8) DatabaseError!void {
    try checkExec(c.database_exec(self.ptr, sql));
}
```

This keeps the mapping from C status code to Zig error in one place.

For richer libraries, use a switch:

```zig
fn check(rc: c_int) DatabaseError!void {
    return switch (rc) {
        0 => {},
        1 => error.ExecFailed,
        2 => error.OutOfMemory,
        else => error.Unknown,
    };
}
```

#### Wrap Constants and Flags

C APIs often use integer flags:

```c
#define DATABASE_READONLY 1
#define DATABASE_CREATE   2
```

Instead of exposing raw constants everywhere, wrap them:

```zig
const OpenFlags = packed struct {
    readonly: bool = false,
    create: bool = false,

    fn toC(self: OpenFlags) c_int {
        var flags: c_int = 0;

        if (self.readonly) flags |= c.DATABASE_READONLY;
        if (self.create) flags |= c.DATABASE_CREATE;

        return flags;
    }
};
```

Usage:

```zig
const flags = OpenFlags{
    .readonly = true,
    .create = false,
};

const c_flags = flags.toC();
```

This gives Zig callers named fields instead of raw bit operations.

#### Choose the Right Wrapper Thickness

A thin wrapper stays close to C.

```zig
pub fn exec(self: Database, sql: [*:0]const u8) DatabaseError!void {
    const rc = c.database_exec(self.ptr, sql);
    if (rc != 0) return error.ExecFailed;
}
```

A thick wrapper changes the interface more.

```zig
pub fn exec(self: Database, allocator: std.mem.Allocator, sql: []const u8) DatabaseError!void {
    const sql_z = allocator.dupeZ(u8, sql) catch return error.OutOfMemory;
    defer allocator.free(sql_z);

    try check(c.database_exec(self.ptr, sql_z.ptr));
}
```

Both are valid.

Use a thin wrapper when the C API is already clean or performance-critical.

Use a thicker wrapper when you want a safer, more natural Zig interface.

#### Keep the Wrapper Honest

A wrapper should not hide expensive work.

If a function allocates, make that visible by taking an allocator.

If a function may fail, return an error union.

If a function borrows memory, document the lifetime.

If a function transfers ownership, provide a `deinit`.

Good Zig APIs make cost and ownership visible.

#### What to Remember

Wrapping a C library means containing C details at the edge.

Use a Zig struct to hold the C handle.

Use `open`, `create`, or `init` to acquire resources.

Use `deinit`, `close`, or `destroy` to release resources.

Translate null pointers into errors or optionals.

Translate status codes into error unions.

Convert C strings into slices when useful.

Accept Zig slices when possible, but allocate null-terminated copies only when needed.

Expose raw C handles only when necessary.

A good wrapper lets most of your program feel like Zig, even when the implementation uses C underneath.

