# Type Constraints by Use

### Type Constraints by Use

Zig does not have a formal trait or interface system.

Instead, generic code is constrained by the operations it performs. A type is valid if the required operations exist for that type.

Consider this function:

```zig
fn add(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
    return a + b;
}
```

This function does not explicitly require numeric types.

Instead, it assumes the `+` operator is valid.

These calls work:

```zig
const x = add(3, 4);
const y = add(1.5, 2.25);
```

because integers and floating-point values support addition.

This call fails:

```zig
const z = add(true, false);
```

The compiler reports an error because booleans do not support `+`.

The constraint comes from use.

The compiler checks operations only after the function is instantiated with concrete types.

This style appears throughout Zig code.

Here is a generic equality function:

```zig
fn equal(a: anytype, b: @TypeOf(a)) bool {
    return a == b;
}
```

The only requirement is that the type supports `==`.

The compiler enforces this automatically.

A more realistic example is sorting.

```zig
fn lessThan(a: anytype, b: @TypeOf(a)) bool {
    return a < b;
}
```

This works for ordered numeric types.

It fails for structs unless the struct defines meaningful comparison logic elsewhere.

Sometimes the requirements should be checked explicitly.

Zig provides compile-time reflection for this purpose.

This function accepts only integer types:

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

fn double(value: anytype) @TypeOf(value) {
    const T = @TypeOf(value);

    const info = @typeInfo(T);

    switch (info) {
        .int => {},
        else => @compileError("expected integer type"),
    }

    return value * 2;
}

pub fn main() void {
    const x = double(21);

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

The output is:

```text
42
```

The line:

```zig
@typeInfo(T)
```

returns information about the type.

The `switch` checks whether the type category is `.int`.

If not, the compiler stops with:

```text
expected integer type
```

This produces clearer diagnostics than waiting for an invalid operator later in the function.

Compile-time checks can enforce more detailed rules.

This function accepts only pointer types:

```zig
fn isNull(ptr: anytype) bool {
    const T = @TypeOf(ptr);

    switch (@typeInfo(T)) {
        .pointer => {},
        else => @compileError("expected pointer"),
    }

    return ptr == null;
}
```

Generic constraints may also depend on declarations.

Suppose a function requires a type to contain a method named `write`:

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

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

    writer.write();
}
```

`@hasDecl` checks whether a declaration exists in the type.

This resembles interface checking, but the mechanism is entirely compile-time reflection.

The standard library frequently uses this approach.

Containers, allocators, formatters, and I/O abstractions often require types to provide specific declarations or operations.

The constraints are structural:

- does the type support this operation?
- does this declaration exist?
- does this function return the expected type?

No explicit inheritance hierarchy is required.

This keeps the language small while still allowing highly generic code.

Exercise 11-9. Write a generic function that accepts only floating-point types.

Exercise 11-10. Write a function that accepts only pointer types.

Exercise 11-11. Write a generic `sum` function for integer arrays.

Exercise 11-12. Write a compile-time check that requires a type to contain a declaration named `init`.

