# FFI Safety

### FFI Safety

FFI means foreign function interface. It is the boundary where Zig calls code written in another language, or where another language calls Zig.

Most often, this means C.

Zig can call C functions directly.

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

pub fn main() void {
    _ = c.puts("hello from C");
}
```

`@cImport` asks Zig to import C declarations. `@cInclude` includes a C header.

The call:

```zig
c.puts("hello from C");
```

calls the C function `puts`.

This looks simple, but the boundary is unsafe. C does not carry Zig's safety rules with it. A C function may expect a null-terminated string. It may store a pointer. It may write into a buffer. It may return `null`. It may use global state. It may require the caller to free memory in a particular way.

The Zig side must express those rules clearly.

A common mistake is passing a normal slice to C.

```zig
const msg: []const u8 = "hello";
```

A slice has a pointer and a length. C usually expects a pointer only.

For C strings, use a sentinel-terminated value.

```zig
const msg: [:0]const u8 = "hello";
_ = c.puts(msg);
```

The type:

```zig
[:0]const u8
```

means a slice of bytes ending with a zero byte.

That matches the usual C string convention.

When passing a buffer to C, pass both pointer and length if the C API accepts both.

```zig
extern fn fill(buf: [*]u8, len: usize) c_int;

pub fn main() void {
    var buffer: [128]u8 = undefined;

    const rc = fill(&buffer, buffer.len);

    _ = rc;
}
```

The pointer type:

```zig
[*]u8
```

has no length. The length must be passed separately.

The C function can write past the end if the length is wrong. Zig cannot prevent that once control passes to C.

Null pointers must be handled explicitly.

```zig
extern fn find_name(id: c_int) ?[*:0]const u8;
```

The return type:

```zig
?[*:0]const u8
```

means the function returns either a null pointer or a pointer to a zero-terminated string.

Use an optional branch before reading it.

```zig
const p = find_name(10);

if (p) |name| {
    std.debug.print("{s}\n", .{name});
} else {
    std.debug.print("not found\n", .{});
}
```

A C pointer does not tell you who owns the memory.

This must be learned from the C API.

Some returned pointers are borrowed:

```zig
extern fn getenv(name: [*:0]const u8) ?[*:0]const u8;
```

The caller must not free the result.

Some returned pointers are owned:

```zig
extern fn make_name() ?[*:0]u8;
extern fn free_name(p: [*:0]u8) void;
```

The caller must later call `free_name`.

Zig code should make this rule hard to miss.

```zig
const name = make_name() orelse return error.OutOfMemory;
defer free_name(name);
```

The `defer` places the release next to the acquisition.

C error handling also needs care.

Many C functions return an integer status.

```zig
extern fn open_device() c_int;
```

Wrap it in a Zig function that returns an error union.

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

extern fn open_device() c_int;

fn openDevice() !void {
    const rc = open_device();

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

Now the rest of the Zig program can use `try`.

```zig
try openDevice();
```

Keep C conventions at the boundary. Convert them to Zig conventions as soon as possible.

Callbacks require special attention.

```zig
const Callback = *const fn (value: c_int) callconv(.c) void;

extern fn set_callback(cb: Callback) void;
```

The calling convention:

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

says that the function uses the C ABI.

A Zig callback passed to C must use the ABI the C code expects.

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

Then:

```zig
set_callback(onValue);
```

The callback must remain valid as long as C may call it. Function declarations are fine. Pointers to temporary data are not.

Struct layout is another boundary issue.

A normal Zig struct does not promise C layout.

Use `extern struct` for C-compatible layout.

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

Use C integer types when matching C declarations.

```zig
c_int
c_uint
c_long
usize
```

Do not assume that C's `int` is always `i32` on every target. Use the translated C types or Zig's C ABI types.

For bit-exact binary layouts, `packed struct` may be useful. For C ABI structs, use `extern struct`.

| Need | Type |
|---|---|
| C ABI layout | `extern struct` |
| Exact bit layout | `packed struct` |
| Zig-only data | `struct` |

Variadic C functions are another sharp edge.

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

The argument types must match the C format string. Zig cannot fully check the meaning of a C format string.

Prefer Zig formatting except at the C boundary.

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

The safest FFI design is thin at the edge and typed inside.

Poor style:

```zig
// C pointers and integer status codes spread everywhere.
```

Better style:

```zig
// One small wrapper module owns the C interface.
// The rest of the program sees Zig types and Zig errors.
```

A C call should be treated like unsafe code. Check nulls. Check lengths. Check ownership. Check calling conventions. Check layout.

FFI is useful because Zig is designed to cooperate with C. It is dangerous because C and Zig do not enforce the same rules.

Keep the boundary narrow.

Exercise 19-25. Import `stdio.h` and call `puts`.

Exercise 19-26. Write an `extern struct` that matches a C struct with two `int` fields.

Exercise 19-27. Wrap a C-style integer return code in a Zig function that returns `!void`.

Exercise 19-28. Explain why `extern struct` and `packed struct` solve different problems.

