# Calling C Functions

### Calling C Functions

Calling a C function from Zig has three parts.

First, Zig must know the C function exists.

Second, the final executable must link to the compiled C code.

Third, your Zig code must call the function using the right types.

A header gives Zig the declaration:

```c
// mathlib.h

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

A C file gives the linker the implementation:

```c
// mathlib.c

#include "mathlib.h"

int add(int a, int b) {
    return a + b;
}
```

Your Zig file imports the header and calls the function:

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

const c = @cImport({
    @cInclude("mathlib.h");
});

pub fn main() void {
    const result = c.add(10, 20);
    std.debug.print("result = {}\n", .{result});
}
```

The call itself looks simple:

```zig
const result = c.add(10, 20);
```

But behind that line, Zig is following the C ABI. It passes the arguments the way C expects, receives the return value the way C returns it, and uses the imported declaration to check the call.

#### A C Function Declaration

A C function declaration says the function name, parameters, and return type.

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

This means:

The function is named `add`.

It takes two `int` values.

It returns an `int`.

After `@cImport`, Zig sees this as a C function under the imported namespace:

```zig
c.add
```

So Zig code can call:

```zig
const x = c.add(2, 3);
```

If the C function returns `5`, then `x` receives that value.

#### C Types at the Boundary

C types and Zig types are not always the same.

At the C boundary, use C-compatible types.

| C type | Zig type at C boundary |
|---|---|
| `char` | `c_char` |
| `signed char` | `c_schar` |
| `unsigned char` | `c_uchar` |
| `short` | `c_short` |
| `unsigned short` | `c_ushort` |
| `int` | `c_int` |
| `unsigned int` | `c_uint` |
| `long` | `c_long` |
| `unsigned long` | `c_ulong` |
| `long long` | `c_longlong` |
| `unsigned long long` | `c_ulonglong` |
| `float` | `f32` |
| `double` | `f64` |
| `void *` | `?*anyopaque` or pointer types from import |

For example, if C declares:

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

then the Zig result is C-compatible:

```zig
const result: c_int = c.add(10, 20);
```

You can convert it into a normal Zig type if needed:

```zig
const value: i32 = @intCast(c.add(10, 20));
```

The cast is explicit. Zig does not want silent narrowing or surprising integer conversion.

#### Calling a Function That Returns Nothing

In C, a function that returns nothing uses `void`.

```c
void say_hello(void);
```

In Zig, this maps naturally to a function that returns `void`.

You call it like this:

```zig
c.say_hello();
```

There is no result to store.

If you write:

```zig
const x = c.say_hello();
```

then `x` would have type `void`, which is rarely useful.

#### Calling a Function That Returns a Status Code

Many C APIs report errors with integer return codes.

For example:

```c
int save_file(const char *path);
```

The library might document:

```text
0 means success
nonzero means failure
```

In Zig, you should usually wrap this in an error union:

```zig
const SaveError = error{
    Failed,
};

fn saveFile(path: [*:0]const u8) SaveError!void {
    const rc = c.save_file(path);

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

Then the rest of your Zig code can use:

```zig
try saveFile("data.txt");
```

This is better than spreading raw C status checks across your program.

#### Passing Numbers

Passing numbers is direct when the types match.

C:

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

Zig:

```zig
const result = c.max_int(10, 20);
```

For constants like `10` and `20`, Zig can usually coerce them into the expected C integer type.

For variables, be more explicit:

```zig
const a: c_int = 10;
const b: c_int = 20;

const result = c.max_int(a, b);
```

If you have a Zig `usize` and the C function expects `int`, do not pass it blindly:

```zig
const n: usize = 100;
const result = c.take_int(@intCast(n));
```

This says clearly: convert `usize` to the C integer type expected by the function.

#### Passing Pointers

C uses pointers heavily.

A C function might expect a pointer to an integer:

```c
void increment(int *value);
```

In Zig, you can pass the address of a variable:

```zig
var x: c_int = 41;
c.increment(&x);
```

After the call, C may have modified `x`.

```zig
std.debug.print("x = {}\n", .{x});
```

If the C function expects `const int *`, it promises not to modify the value:

```c
void print_int(const int *value);
```

Zig can pass a pointer to a const value:

```zig
const x: c_int = 42;
c.print_int(&x);
```

The distinction between mutable and const pointers still matters.

#### Passing Buffers

C often represents a buffer as a pointer plus a length:

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

In Zig, a slice already contains a pointer and a length:

```zig
var buffer: [1024]u8 = undefined;
const slice = buffer[0..];
```

To call the C function, pass the slice pointer and length:

```zig
const rc = c.fill_buffer(slice.ptr, slice.len);
```

Then check the return code:

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

A wrapper is cleaner:

```zig
const FillError = error{
    FillFailed,
};

fn fillBuffer(buffer: []u8) FillError!void {
    const rc = c.fill_buffer(buffer.ptr, buffer.len);

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

Now callers do not need to remember the C pointer-plus-length pattern:

```zig
try fillBuffer(buffer[0..]);
```

#### Passing C Strings

C strings usually end with a zero byte.

A C function might look like this:

```c
void print_name(const char *name);
```

You can pass a Zig string literal:

```zig
c.print_name("Zig");
```

This works because Zig string literals are compatible with null-terminated C string parameters.

But a runtime slice is different:

```zig
const name: []const u8 = "Zig";
```

Do not assume this is safe:

```zig
c.print_name(name.ptr);
```

The pointer does not carry the length. C will keep reading until it finds a zero byte.

Use a null-terminated string type when the C function requires it:

```zig
const name: [*:0]const u8 = "Zig";
c.print_name(name);
```

The `:0` means there is a zero sentinel at the end.

#### Receiving C Strings

A C function may return a string:

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

In Zig, that may appear as a pointer to a null-terminated sequence.

```zig
const ptr = c.get_name();
```

Before using it, ask two questions.

Can it be null?

Who owns the memory?

If the C documentation says the return value can be null, check it:

```zig
const ptr = c.get_name();

if (ptr == null) {
    return error.NoName;
}
```

If the documentation says the returned pointer must not be freed, do not free it.

If the documentation says the caller must free it, call the matching C cleanup function.

C APIs depend heavily on documentation. Zig can check types, but it cannot infer every ownership rule from a header.

#### Calling Functions That Allocate

Suppose C has this API:

```c
char *make_message(void);
void free_message(char *message);
```

The Zig code should pair allocation and cleanup:

```zig
const ptr = c.make_message();

if (ptr == null) {
    return error.OutOfMemory;
}

defer c.free_message(ptr);

// use ptr here
```

The `defer` makes cleanup reliable. When the current scope exits, Zig calls `free_message`.

Do not use a Zig allocator to free memory allocated by C unless the C library explicitly says that is valid.

The allocator that allocates memory should normally free it.

#### Calling Functions with Output Parameters

C often returns data through pointer parameters.

Example:

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

The function returns a status code. The actual values are written into `width` and `height`.

Zig code:

```zig
var width: c_int = undefined;
var height: c_int = undefined;

const rc = c.get_size(&width, &height);

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

std.debug.print("{} x {}\n", .{ width, height });
```

A Zig wrapper can return a struct instead:

```zig
const Size = struct {
    width: c_int,
    height: c_int,
};

const SizeError = error{
    GetSizeFailed,
};

fn getSize() SizeError!Size {
    var width: c_int = undefined;
    var height: c_int = undefined;

    const rc = c.get_size(&width, &height);

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

    return Size{
        .width = width,
        .height = height,
    };
}
```

Now the caller gets a normal Zig value:

```zig
const size = try getSize();
```

This is easier to use correctly.

#### Calling Variadic C Functions

Some C functions accept a variable number of arguments.

The classic example is `printf`:

```c
int printf(const char *format, ...);
```

Zig can call C variadic functions, but you should be careful.

Example:

```zig
_ = c.printf("value = %d\n", @as(c_int, 42));
```

Notice the explicit type:

```zig
@as(c_int, 42)
```

C variadic functions do not carry strong type information for the extra arguments. If you pass the wrong type, the compiler may not protect you fully.

In Zig code, prefer `std.debug.print` or `std.io` formatting when you can:

```zig
std.debug.print("value = {}\n", .{42});
```

Use C variadic functions mainly when calling existing C APIs that require them.

#### Function Pointers from C

C APIs sometimes accept callback functions.

Example:

```c
typedef void (*callback_t)(int value);

void run_callback(callback_t cb);
```

In Zig, you can pass a compatible function:

```zig
fn callback(value: c_int) callconv(.c) void {
    std.debug.print("value = {}\n", .{value});
}

pub fn main() void {
    c.run_callback(callback);
}
```

The important part is:

```zig
callconv(.c)
```

This tells Zig the function uses the C calling convention.

When C calls back into Zig, the ABI must match.

#### Null Pointers

C uses null pointers often.

A C function might return `NULL` when it fails:

```c
void *create_handle(void);
```

In Zig, imported nullable C pointers are usually represented as optional pointers.

That means you should check them:

```zig
const handle = c.create_handle();

if (handle == null) {
    return error.CreateFailed;
}
```

After checking, unwrap the optional:

```zig
const non_null_handle = handle.?;
```

Then use it:

```zig
defer c.destroy_handle(non_null_handle);
```

The pattern is common:

```zig
const handle = c.create_handle() orelse return error.CreateFailed;
defer c.destroy_handle(handle);
```

This is concise and clear.

#### Do Not Guess Ownership

When calling C functions, the type signature does not tell the whole story.

These two functions may look similar:

```c
const char *get_static_name(void);
char *make_owned_name(void);
```

But they may have very different ownership rules.

The first might return a pointer to static memory. You must not free it.

The second might allocate memory. You must free it.

The header alone may not tell you enough. Read the C library documentation.

For every pointer returned from C, ask:

Can it be null?

How long is it valid?

Who frees it?

Which function frees it?

Can it be used from multiple threads?

These questions matter more than the syntax.

#### A Practical Wrapper Example

Suppose a C library exposes this:

```c
int read_config(const char *path, char *buffer, size_t buffer_len);
```

It returns `0` on success and nonzero on failure.

A direct Zig call might look like this:

```zig
var buffer: [4096]u8 = undefined;

const rc = c.read_config("config.txt", &buffer, buffer.len);

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

But a wrapper is better:

```zig
const ConfigError = error{
    ReadConfigFailed,
};

fn readConfig(path: [*:0]const u8, buffer: []u8) ConfigError!void {
    const rc = c.read_config(path, buffer.ptr, buffer.len);

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

Now callers write:

```zig
var buffer: [4096]u8 = undefined;
try readConfig("config.txt", buffer[0..]);
```

This wrapper turns a C-style function into a Zig-style function.

The C function uses raw pointers and status codes.

The Zig function uses slices and error unions.

#### What to Remember

Calling C functions from Zig is direct, but the boundary still matters.

Use `@cImport` to import declarations.

Make sure the C implementation is linked.

Use C-compatible types at the boundary.

Pass slices as pointer plus length when C expects that pattern.

Use null-terminated strings for C string parameters.

Check nullable pointers.

Pair C allocation with C cleanup.

Wrap C APIs in Zig functions so the rest of your program can use Zig errors, slices, structs, and clear ownership rules.

