# Interfaces by Convention

### Interfaces by Convention

Zig has no built-in interface keyword.

Instead, interfaces are formed by agreement between code that calls operations and code that provides them.

A type satisfies an interface if it contains the expected declarations with compatible behavior.

This is sometimes called structural typing, but Zig implements it through ordinary compile-time checking.

Consider a simple example.

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

const ConsoleWriter = struct {
    pub fn write(self: *ConsoleWriter, bytes: []const u8) void {
        _ = self;
        std.debug.print("{s}", .{bytes});
    }
};

fn printMessage(writer: anytype) void {
    writer.write("hello\n");
}

pub fn main() void {
    var writer = ConsoleWriter{};
    printMessage(&writer);
}
```

The output is:

```text
hello
```

`printMessage` accepts:

```zig
writer: anytype
```

The function does not care about the concrete type.

It only assumes the value provides:

```zig
write([]const u8)
```

If the declaration exists, the function compiles.

If not, compilation fails.

Another type may satisfy the same interface:

```zig
const BufferWriter = struct {
    buffer: []u8,
    len: usize,

    pub fn write(self: *BufferWriter, bytes: []const u8) void {
        for (bytes) |b| {
            self.buffer[self.len] = b;
            self.len += 1;
        }
    }
};
```

`printMessage` works with this type too, even though the types are unrelated.

The interface is defined entirely by convention.

A more explicit version may validate requirements at compile time.

```zig
fn printMessage(writer: anytype) void {
    const T = @TypeOf(writer);

    if (!@hasDecl(T, "write")) {
        @compileError("type must provide write()");
    }

    writer.write("hello\n");
}
```

This improves diagnostics.

Without the check, the compiler eventually reports that `write` does not exist. With the check, the error message describes the intended interface directly.

Interfaces in Zig are therefore usually documented rather than declared.

For example, an allocator interface might require:

- `alloc`
- `free`
- `resize`

An iterator interface might require:

- `next`

A formatter interface might require:

- `format`

The compiler verifies usage automatically when concrete types are substituted.

This design has several consequences.

First, interface dispatch is usually static.

The compiler knows the concrete type during specialization:

```zig
fn process(writer: anytype) void
```

Each instantiated version operates directly on the concrete type.

There is no implicit virtual dispatch table.

Second, interfaces remain lightweight.

No inheritance graph exists. No separate interface declarations are needed. Types participate simply by providing matching operations.

Third, interfaces can be partial.

A function may require only one operation:

```zig
next()
```

Another function may require several:

```zig
read()
write()
flush()
```

The interface emerges from actual use.

Sometimes runtime polymorphism is required.

In that case, Zig programmers usually build explicit interface tables manually.

A simplified example:

```zig
const Writer = struct {
    ptr: *anyopaque,
    writeFn: *const fn (*anyopaque, []const u8) void,

    pub fn write(self: Writer, bytes: []const u8) void {
        self.writeFn(self.ptr, bytes);
    }
};
```

This resembles a virtual table in other languages.

The data pointer:

```zig
*anyopaque
```

stores an erased object pointer.

The function pointer:

```zig
writeFn
```

knows how to operate on it.

The standard library uses this pattern in several places when runtime dispatch is necessary.

Most generic Zig code, however, uses compile-time specialization instead of runtime polymorphism.

The language encourages simple conventions:

- operations define the interface
- compile-time checks enforce requirements
- explicit runtime dispatch is built only when needed

Exercise 11-13. Write a generic function that calls `next()` on a type.

Exercise 11-14. Add compile-time checks requiring both `read` and `write`.

Exercise 11-15. Implement a small runtime-dispatched logger using function pointers.

Exercise 11-16. Write two unrelated structs that satisfy the same interface convention.

