# Using C Structs

### Using C Structs

A C struct groups several fields into one value.

For example:

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

This defines a value with two fields:

```text
x
y
```

In Zig, you can use C structs in two main ways.

You can import a struct from a C header.

You can define a C-compatible struct yourself with `extern struct`.

Both are useful.

#### Importing a C Struct

Suppose you have this C header:

```c
// point.h

#ifndef POINT_H
#define POINT_H

struct Point {
    int x;
    int y;
};

#endif
```

You can import it into Zig:

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

Then you can create a value:

```zig
pub fn main() void {
    var p = c.struct_Point{
        .x = 10,
        .y = 20,
    };

    _ = p;
}
```

C structs imported by `@cImport` may appear with generated names such as `c.struct_Point`, depending on how the C header declares them.

If the C header uses `typedef`, the imported name is usually cleaner.

```c
// point.h

#ifndef POINT_H
#define POINT_H

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

#endif
```

Now Zig can usually use:

```zig
var p = c.Point{
    .x = 10,
    .y = 20,
};
```

The `typedef` gives the struct a convenient C type name. Zig imports that name.

#### Reading and Writing Fields

A C struct field is accessed with dot syntax, just like a Zig struct field.

```zig
var p = c.Point{
    .x = 10,
    .y = 20,
};

p.x = 30;

std.debug.print("({}, {})\n", .{ p.x, p.y });
```

This prints:

```text
(30, 20)
```

The syntax is simple. The important part is not the field access. The important part is memory layout.

#### C Struct Layout

A struct is not only a group of names. It is also a memory layout.

For this C struct:

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

C decides where `x` and `y` live in memory. Usually, it looks like this:

```text
Point
+---------+---------+
| x       | y       |
+---------+---------+
```

If `int` is 4 bytes, the whole struct is usually 8 bytes.

But real structs can include padding.

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

This may look like 5 bytes: 1 byte for `char`, 4 bytes for `int`.

But the compiler will usually add padding so `b` is properly aligned:

```text
Example
+---+---+---+---+---------+
| a | padding   | b       |
+---+---+---+---+---------+
```

The struct may be 8 bytes, not 5.

This matters when Zig and C share structs. Both sides must agree on the same layout.

#### `extern struct`

When you define a struct in Zig and want C-compatible layout, use `extern struct`.

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

The word `extern` tells Zig:

Use a layout compatible with C.

This is the correct choice when you pass the struct to C, receive it from C, or expose it to C.

Do not use a normal Zig struct for C ABI data unless you have a specific reason and know the layout rules.

This is a normal Zig struct:

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

This is a C-compatible Zig struct:

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

The second one is the right form for C interop.

#### Passing Structs to C

Suppose C declares this:

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

void print_point(Point p);
```

In Zig:

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

pub fn main() void {
    const p = c.Point{
        .x = 10,
        .y = 20,
    };

    c.print_point(p);
}
```

Here, the struct is passed by value. Zig copies the whole struct into the C function call according to the C ABI.

For small structs, this is common.

For larger structs, C APIs often use pointers.

#### Passing Pointers to Structs

C often uses this style:

```c
void move_point(Point *p, int dx, int dy);
```

The function receives a pointer, then modifies the struct through that pointer.

In Zig:

```zig
var p = c.Point{
    .x = 10,
    .y = 20,
};

c.move_point(&p, 5, 7);
```

After the call, C may have changed the fields:

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

If the C function promises not to modify the struct, it may use `const`:

```c
void print_point(const Point *p);
```

Then Zig can pass a pointer to a const value:

```zig
const p = c.Point{
    .x = 10,
    .y = 20,
};

c.print_point(&p);
```

The difference is important:

```c
Point *p
```

means C may modify the struct.

```c
const Point *p
```

means C should only read it.

#### Structs with Pointers

Many C structs contain pointers.

```c
typedef struct Buffer {
    unsigned char *data;
    size_t len;
} Buffer;
```

In Zig, this may be imported as a struct with a pointer field and a length field.

You can create one from a Zig slice:

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

var buffer = c.Buffer{
    .data = slice.ptr,
    .len = slice.len,
};
```

This is a common bridge between Zig slices and C structs.

But remember: the C struct does not own the memory automatically. It only stores a pointer and a length.

The actual bytes still live in `bytes`.

That means lifetime matters.

This is safe:

```zig
var bytes: [1024]u8 = undefined;

var buffer = c.Buffer{
    .data = bytes[0..].ptr,
    .len = bytes.len,
};

c.use_buffer(&buffer);
```

This is dangerous if the C code stores the pointer for later use after `bytes` is gone.

A C struct can contain a pointer. That does not tell you who owns the memory. You must know the API’s ownership rules.

#### Structs Returned from C

C functions can return structs.

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

Zig can call this:

```zig
const p = c.make_point(10, 20);

std.debug.print("({}, {})\n", .{ p.x, p.y });
```

The returned value is a normal Zig value with C-compatible layout.

C functions can also return pointers to structs:

```c
Point *create_point(int x, int y);
void destroy_point(Point *p);
```

In Zig:

```zig
const p = c.create_point(10, 20) orelse return error.CreatePointFailed;
defer c.destroy_point(p);

std.debug.print("({}, {})\n", .{ p.x, p.y });
```

The `orelse` handles a null pointer. The `defer` ensures cleanup.

The pattern is common when using C libraries:

```zig
const handle = c.create_something(...) orelse return error.CreateFailed;
defer c.destroy_something(handle);
```

#### Opaque Structs

Some C libraries hide struct fields from you.

The header may say:

```c
typedef struct Database Database;

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

This declares a type named `Database`, but it does not show its fields.

This is an opaque struct. You can hold a pointer to it, but you cannot access its fields.

In Zig, you treat it as a handle:

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

You should not try to inspect the internal memory. The C library owns the implementation details.

Opaque structs are common in libraries. They help keep the API stable.

Examples of this style include database handles, window handles, parser objects, compression streams, and graphics contexts.

#### Nested Structs

C structs can contain other structs.

```c
typedef struct Size {
    int width;
    int height;
} Size;

typedef struct Rect {
    int x;
    int y;
    Size size;
} Rect;
```

In Zig:

```zig
var r = c.Rect{
    .x = 0,
    .y = 0,
    .size = c.Size{
        .width = 640,
        .height = 480,
    },
};

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

The nested struct is just another field.

Again, the key rule is layout compatibility. Imported C structs already use the C layout. Manually defined Zig structs should use `extern struct` when shared with C.

#### Packed Structs Are Different

C code sometimes uses packed structs:

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

Packed structs remove normal padding.

This is common in binary file formats, network protocols, and hardware registers.

Be careful. Packed layout has stricter rules and may cause unaligned access. You should not assume a C packed struct maps to an ordinary Zig struct.

Zig has `packed struct`, but `packed struct` and `extern struct` serve different purposes.

Use `extern struct` for normal C ABI layout.

Use `packed struct` for bit-level or byte-exact layouts when appropriate.

Do not guess. Check the C header and the ABI expectations.

#### Bitfields

C structs can contain bitfields:

```c
typedef struct Flags {
    unsigned int readable : 1;
    unsigned int writable : 1;
    unsigned int executable : 1;
} Flags;
```

Bitfields are difficult because their layout can depend on compiler and target rules.

Zig may import some C bitfields, but they are more delicate than ordinary fields.

For stable interop, many libraries avoid exposing bitfields directly. If you control the C API, prefer ordinary integer flags:

```c
#define FLAG_READABLE   1
#define FLAG_WRITABLE   2
#define FLAG_EXECUTABLE 4

typedef struct FileMode {
    unsigned int flags;
} FileMode;
```

Then Zig can use bit operations clearly.

```zig
const readable = (mode.flags & c.FLAG_READABLE) != 0;
```

This is easier to port and easier to reason about.

#### Initializing C Structs Safely

C APIs sometimes expect you to zero-initialize a struct.

Example:

```c
typedef struct Options {
    int enable_cache;
    int max_connections;
    void *user_data;
} Options;
```

A C example may do this:

```c
Options options = {0};
```

In Zig, you can initialize fields explicitly:

```zig
var options = c.Options{
    .enable_cache = 0,
    .max_connections = 100,
    .user_data = null,
};
```

This is often best because every field is visible.

Some imported C structs may have many fields. If zero initialization is correct according to the C library, Zig may allow:

```zig
var options: c.Options = std.mem.zeroes(c.Options);
```

But do this only when the C library documents that zero is a valid initial state.

Zero bytes are not always a valid value for every type or every library.

#### Struct Version Fields

Some C APIs require a struct size or version field.

Example:

```c
typedef struct Options {
    size_t struct_size;
    int enable_cache;
} Options;
```

The C library may expect:

```c
options.struct_size = sizeof(Options);
```

In Zig:

```zig
var options = c.Options{
    .struct_size = @sizeOf(c.Options),
    .enable_cache = 1,
};
```

This pattern lets the C library know which version of the struct you are using.

If a C API requires this field, do not forget it. The call may fail or behave incorrectly.

#### Wrapping C Structs in Zig Types

Imported C structs are useful, but you often do not want the whole program using them directly.

You can wrap them in a Zig type.

Suppose C exposes:

```c
typedef struct Image Image;

Image *image_load(const char *path);
void image_free(Image *image);
int image_width(const Image *image);
int image_height(const Image *image);
```

A Zig wrapper might look like this:

```zig
const ImageError = error{
    LoadFailed,
};

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

    pub fn load(path: [*:0]const u8) ImageError!Image {
        const ptr = c.image_load(path) orelse return error.LoadFailed;

        return Image{
            .ptr = ptr,
        };
    }

    pub fn deinit(self: Image) void {
        c.image_free(self.ptr);
    }

    pub fn width(self: Image) c_int {
        return c.image_width(self.ptr);
    }

    pub fn height(self: Image) c_int {
        return c.image_height(self.ptr);
    }
};
```

Then callers use:

```zig
const image = try Image.load("photo.png");
defer image.deinit();

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

This is much better than exposing raw C calls everywhere.

The wrapper gives the C object a Zig shape:

Load returns an error union.

Cleanup uses `deinit`.

Methods hide the raw pointer.

The rest of the program uses a safer interface.

#### Common Mistakes

The first common mistake is defining a normal Zig struct and passing it to C.

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

This is not the right default for C interop. Use:

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

The second common mistake is forgetting that structs with pointer fields do not automatically own memory.

```zig
var buffer = c.Buffer{
    .data = slice.ptr,
    .len = slice.len,
};
```

This does not copy the bytes. It only points to them.

The third common mistake is freeing memory with the wrong allocator.

If C creates a struct, usually C must destroy it.

If Zig creates memory, usually Zig must free it.

The fourth common mistake is assuming all C structs can be safely zeroed.

Only zero a C struct when the C library says zero initialization is valid.

#### What to Remember

C structs are about memory layout.

Imported C structs already use C-compatible layout.

When defining a Zig struct for C interop, use `extern struct`.

Use C-compatible field types such as `c_int`, `c_uint`, and `c_long`.

Pass small structs by value when the C API expects that.

Pass pointers when the C API expects pointers.

Treat opaque structs as handles.

Be careful with pointer fields, lifetime, ownership, packed structs, and bitfields.

The best pattern is to keep raw C structs at the boundary and wrap them in clean Zig types when possible.

