# Packed Structs

### Packed Structs

A normal struct is laid out for ordinary program use. The compiler may add padding between fields so that each field has a suitable address.

A packed struct is different. Its fields are placed at exact bit positions.

```zig
const Flags = packed struct {
    read: bool,
    write: bool,
    execute: bool,
};
```

This declares a packed type with three one-bit fields. Each `bool` field takes one bit.

A value is made in the usual way.

```zig
const flags = Flags{
    .read = true,
    .write = false,
    .execute = true,
};
```

A complete program:

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

const Flags = packed struct {
    read: bool,
    write: bool,
    execute: bool,
};

pub fn main() void {
    const flags = Flags{
        .read = true,
        .write = false,
        .execute = true,
    };

    std.debug.print("read = {}, write = {}, execute = {}\n", .{
        flags.read,
        flags.write,
        flags.execute,
    });
}
```

The output is:

```text
read = true, write = false, execute = true
```

The syntax for field access does not change. A packed struct is still a struct. The difference is representation.

Packed structs are useful when the representation matters.

Common examples are bit flags, hardware registers, file formats, and network packet fields.

```zig
const Permissions = packed struct {
    owner_read: bool,
    owner_write: bool,
    owner_execute: bool,
    group_read: bool,
    group_write: bool,
    group_execute: bool,
    other_read: bool,
    other_write: bool,
    other_execute: bool,
};
```

This describes nine permission bits. A normal struct would usually use more space. A packed struct uses one bit for each `bool`.

A packed struct may use integer fields with explicit bit widths.

```zig
const Header = packed struct {
    version: u4,
    kind: u4,
    length: u16,
};
```

Here `version` uses 4 bits, `kind` uses 4 bits, and `length` uses 16 bits.

The total size is 24 bits.

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

const Header = packed struct {
    version: u4,
    kind: u4,
    length: u16,
};

pub fn main() void {
    const h = Header{
        .version = 1,
        .kind = 3,
        .length = 1024,
    };

    std.debug.print("version = {d}\n", .{h.version});
    std.debug.print("kind = {d}\n", .{h.kind});
    std.debug.print("length = {d}\n", .{h.length});
}
```

A field of type `u4` cannot hold a value larger than 15. The bit width is part of the type.

Packed structs can be converted to and from integers of the same bit size.

```zig
const Header = packed struct {
    version: u4,
    kind: u4,
    length: u16,
};

const raw: u24 = @bitCast(Header{
    .version = 1,
    .kind = 3,
    .length = 1024,
});
```

The reverse conversion is also a bit cast.

```zig
const h: Header = @bitCast(raw);
```

`@bitCast` keeps the bits and changes the type used to interpret them. It does not parse, validate, or reorder the data.

This is useful, but it should be used carefully. The meaning of the bits must already be known.

Packed structs may have methods.

```zig
const Header = packed struct {
    version: u4,
    kind: u4,
    length: u16,

    pub fn isLarge(self: Header) bool {
        return self.length > 1024;
    }
};
```

They may also have declarations.

```zig
const Header = packed struct {
    version: u4,
    kind: u4,
    length: u16,

    const default_version = 1;
};
```

A packed struct is not a general replacement for a normal struct. Use normal structs for ordinary data.

```zig
const User = struct {
    id: u64,
    name: []const u8,
    active: bool,
};
```

This is ordinary program data. The exact bit layout is not the point.

Use a packed struct when the exact layout is the point.

```zig
const StatusRegister = packed struct {
    carry: bool,
    zero: bool,
    interrupt_disable: bool,
    decimal: bool,
    break_flag: bool,
    unused: bool,
    overflow: bool,
    negative: bool,
};
```

This type describes an 8-bit register.

```zig
const raw: u8 = 0b1000_0011;
const status: StatusRegister = @bitCast(raw);
```

Now the fields may be read by name.

```zig
if (status.negative) {
    std.debug.print("negative\n", .{});
}
```

This is clearer than masking the integer by hand at every use site.

The hand-written version would look like this:

```zig
const negative = (raw & 0b1000_0000) != 0;
```

The packed struct puts those names in one place.

There are still limits. A packed struct describes a bit layout inside a value. It does not solve byte order by itself. When bytes come from a file or network, the program must still handle endianness correctly.

For example, reading two bytes into a `u16` depends on the chosen byte order. Only after the integer has the intended value should it be bit-cast into a packed struct.

A packed struct may expose unaligned fields. Taking pointers to such fields is restricted because the field may not begin at an address boundary suitable for its type.

For most code, this rule has a simple consequence: read and write packed fields by value. Do not design APIs that require pointers to individual packed fields.

Packed structs are a low-level feature. They are precise, but they are also easy to misuse. They should appear near the boundary where the program touches hardware, binary formats, or external data.

Exercises.

7-21. Define a packed struct with three boolean fields: `read`, `write`, and `execute`.

7-22. Define a packed struct for one byte with fields `low: u4` and `high: u4`.

7-23. Convert a `u8` value to the packed byte struct with `@bitCast`, then print both fields.

7-24. Define a packed struct for an 8-bit status register. Give each bit a name.

