# Chapter 8: Interfaces and Generics

Freestanding code keeps Loup's interfaces and generics. A UART, a framebuffer console and a debug ring buffer can all implement one `SerialOut` interface; a table or a ring buffer can be written once and used for several element types. Both features are resolved at compile time, so neither needs a runtime (Language Reference §3-3.4.3):

- a call through an interface-typed value is a **static type-id dispatch**: the compiler knows every type that implements the interface and branches to the right method — no heap-allocated vtable, no runtime registration;
- generic code is **monomorphized**: each instantiation becomes its own concrete code.

This chapter covers interface declarations and dispatch, generic functions and types, bounds, explicit type arguments, lifetime parameters, and the parts of the hosted feature set that freestanding leaves out.

## Declaring and implementing an interface

An interface lists method signatures, each ending in `;`. A struct or class names the interfaces it implements with `implements` in its header and defines the methods in its body:

```loup
@mode(freestanding)

interface SerialOut {
    func writeByte(b: uint8): void;
    func name(): string;
}

# The PC's COM1 port: an x86 I/O port, written with `out`.
struct PortUart implements SerialOut {
    var port: uint16;

    func writeByte(b: uint8): void {
        asm { "out dx, al" in: "dx" = this.port, "al" = b clobbers: [] }
    }

    func name(): string {
        return "com1"
    }
}

# A ring buffer in memory, for when no UART is wired up.
struct MemoryLog implements SerialOut {
    var written: uint32;

    func writeByte(b: uint8): void {
        this.written = this.written + 1u32
    }

    func name(): string {
        return "memlog"
    }
}
```

*Listing 8-1: An interface with two implementations*

There is no separate `impl` block. Writing the implementation the Rust or Swift way fails at the type name:

```loup,does_not_compile
@mode(freestanding)

interface SerialOut {
    func writeByte(b: uint8): void;
}

struct Uart {
    var port: uint16;
}

impl Uart: SerialOut {
    func writeByte(b: uint8): void { }
}
```

```text
error[E0003]: expected `;`
  --> src/main.lpfree:11:6
   |
11 | impl Uart: SerialOut {
   |      ^^^^
   |

error[E0001]: expected expression
  --> src/main.lpfree:13:1
   |
13 | }
   | ^
   |
```

Move the methods into the struct and add `implements SerialOut` to its header, as in Listing 8-1.

The Language Reference requires every interface method to be implemented. The 1.25 checker does not report a missing method yet, so a struct that declares `implements SerialOut` without a `name()` method passes `loup check`; review implementations against their interface.

## Programming against an interface

A parameter, local or field of interface type accepts any implementation; assigning a struct to an interface-typed binding is the upcast:

```loup
#~ @mode(freestanding)
#~
#~ interface SerialOut {
#~     func writeByte(b: uint8): void;
#~     func name(): string;
#~ }
#~
#~ struct PortUart implements SerialOut {
#~     var port: uint16;
#~     func writeByte(b: uint8): void {
#~         asm { "out dx, al" in: "dx" = this.port, "al" = b clobbers: [] }
#~     }
#~     func name(): string { return "com1" }
#~ }
#~
#~ struct MemoryLog implements SerialOut {
#~     var written: uint32;
#~     func writeByte(b: uint8): void { this.written = this.written + 1u32 }
#~     func name(): string { return "memlog" }
#~ }
#~
func writeStr(out: SerialOut, s: string): void {
    for (c in s) {
        out.writeByte(c as uint8)
    }
}

func banner(useUart: bool): void {
    if (useUart) {
        let uart: SerialOut = PortUart(port = 0x3F8u16)
        writeStr(uart, "Loup kernel booting\n")
    } else {
        writeStr(MemoryLog(written = 0u32), "Loup kernel booting\n")
    }
}
```

*Listing 8-2: Dispatching through an interface-typed value*

`out.writeByte(...)` compiles to a branch on the receiver's type id between `PortUart.writeByte` and `MemoryLog.writeByte`. Adding a third implementation adds a third branch; nothing is registered at run time.

### No default methods in freestanding

In the hosted modes an interface method can have a default body that every implementation inherits. Freestanding leaves default bodies out (§3-3.4.3), and a call that relies on one fails:

```loup,does_not_compile
@mode(freestanding)

interface SerialOut {
    func writeByte(b: uint8): void;

    # A default method, written once for every implementation.
    func writeStr(s: string): void {
        for (c in s) {
            this.writeByte(c as uint8)
        }
    }
}

struct DebugPort implements SerialOut {
    var port: uint16;
    func writeByte(b: uint8): void {
        asm { "out dx, al" in: "dx" = this.port, "al" = b clobbers: [] }
    }
}

func banner(out: DebugPort): void {
    out.writeStr("boot\n")
}
```

```text
error[E0001]: type 'DebugPort' has no method 'writeStr'
  --> src/main.lpfree:22:5
   |
22 |     out.writeStr("boot\n")
   |     ^^^^^^^^^^^^^^^^^^^^^^
   |
```

Write the shared behaviour as a free function over the interface, as `writeStr(out: SerialOut, s: string)` in Listing 8-2 does. It is just as reusable and makes the dispatch visible.

## Generic functions and types

Type parameters go in square brackets after the name. Calls usually infer them; you can always write them:

```loup
#~ @mode(freestanding)
#~
struct Pair[T] {
    var first: T;
    var second: T;
}

func swapPair[T](p: Pair[T]): Pair[T] {
    return Pair(first = p.second, second = p.first)
}

enum Either[L, R] {
    Left(value: L),
    Right(value: R),
}

func isLeft[L, R](e: Either[L, R]): bool {
    return match e {
        Either.Left(v) => true,
        Either.Right(v) => false,
    }
}

func demo(): uint32 {
    let p = swapPair[uint32](Pair(first = 1u32, second = 2u32))   # explicit
    let q = swapPair(Pair(first = 3u16, second = 4u16))          # inferred
    return p.first + (q.first as uint32)
}
```

*Listing 8-3: A generic struct, function and enum*

Each distinct instantiation — `swapPair[uint32]`, `swapPair[uint16]` — is compiled separately, so generic code costs the same as hand-written code for each type.

### Bounds

A bound restricts a type parameter to types that implement an interface, which is what lets the body call the interface's methods. The Language Reference allows the bound inline, `[L: Logger]`, or in a `where` clause after the return type. In 1.25 the checker only uses a `where` bound when it looks up a method:

```loup,does_not_compile
@mode(freestanding)

interface Logger {
    func log(level: uint8, msg: string): void;
}

func warn[L: Logger](logger: L, msg: string): void {
    logger.log(2u8, msg)
}
```

```text
error[E0001]: type 'T0' has no method 'log'
 --> src/main.lpfree:8:5
  |
8 |     logger.log(2u8, msg)
  |     ^^^^^^^^^^^^^^^^^^^^
  |
```

`T0` is the checker's internal name for the type parameter `L`: with the inline bound, it does not know that `L` has a `log` method. Write the bound as a `where` clause:

```loup
#~ @mode(freestanding)
#~
interface Logger {
    func log(level: uint8, msg: string): void;
}

struct CountingLogger implements Logger {
    var lines: uint32;
    func log(level: uint8, msg: string): void {
        this.lines = this.lines + 1u32
    }
}

func warn[L](logger: L, msg: string): void where L: Logger {
    logger.log(2u8, msg)
}

func smaller[T](a: T, b: T): T where T: Comparable[T] {
    if (a < b) {
        return a
    }
    return b
}

func demo(): uint32 {
    warn(CountingLogger(lines = 0u32), "fan speed low")
    return smaller(3u32, 4u32)
}
```

*Listing 8-4: Bounds written as `where` clauses*

A generic bound and an interface-typed parameter (Listing 8-2) both work. The generic version gives one specialized copy per logger type; the interface version gives one function with a dispatch branch. Prefer the interface parameter unless the call is on a hot path.

### Phantom type parameters and explicit type arguments

A type parameter does not have to appear in any value. A *phantom* parameter tags a value with a type the compiler then checks — a capability for a `uint64` resource is not interchangeable with one for a page. The type argument cannot be inferred, so you write it:

```loup
#~ @mode(freestanding)
#~
struct Cap[T] {
    var id: uint64;
}

struct PageFrame { var number: uint64; }

func mintCap[T](id: uint64): Cap[T] {
    return Cap(id = id)
}

func pageCap(): uint64 {
    let c = mintCap[PageFrame](7u64)       # Cap[PageFrame]
    return c.id
}
```

*Listing 8-5: Explicit type arguments for a phantom-typed API*

The same bracket form instantiates a static method (`Type[T].method(...)`) and the layout queries `size_of[T]()` you met in chapter 5.

### Compile-time sizes

Generic types cannot take a number as a parameter. A C++ or Rust habit — `RingBuf<T, N>` — does not parse:

```loup,does_not_compile
@mode(freestanding)

struct RingBuf[T, const N: uint32] {
    var head: uint32;
    var count: uint32;
}
```

```text
error[E0001]: `const` is a reserved word (§1-3.4) and cannot name a type parameter
 --> src/main.lpfree:3:19
  |
3 | struct RingBuf[T, const N: uint32] {
  |                   ^^^^^
  |

error[E0001]: expected RBracket, found Identifier
 --> src/main.lpfree:3:25
  |
3 | struct RingBuf[T, const N: uint32] {
  |                         ^
  |

error[E0002]: expected `{`
 --> src/main.lpfree:3:25
  |
3 | struct RingBuf[T, const N: uint32] {
  |                         ^
  |

error[E0003]: expected `;`
 --> src/main.lpfree:3:26
  |
3 | struct RingBuf[T, const N: uint32] {
  |                          ^
  |

error[E0001]: expected expression
 --> src/main.lpfree:6:1
  |
6 | }
  | ^
  |
```

Fix the capacity with a `const` and use it in the `buffer` type, which accepts any compile-time expression (§3-3.17b):

```loup
#~ @mode(freestanding)
#~
const RX_SLOTS = 64

struct RxRing {
    var bytes: buffer[uint8, RX_SLOTS];
    var head: int;
    var count: int;

    func isFull(): bool {
        return this.count == RX_SLOTS
    }
}

func pushByte(edits ring: RxRing, b: uint8): bool {
    if (ring.isFull()) {
        return false
    }
    ring.bytes[(ring.head + ring.count) % RX_SLOTS] = b
    ring.count = ring.count + 1
    return true
}
```

*Listing 8-6: A fixed-capacity ring sized by a constant*

One more limitation in 1.25: a generic *struct* cannot hold a `buffer` of its own type parameter (`buffer[T, 16]` inside `struct Ring[T]` reports `E_STRUCT_CLASS_FIELD` at each instantiation, even with a `T: ValueType` bound). Give the buffer a concrete element type, as above.

## Generic interfaces

Interfaces take type parameters too. A source of values of some type:

```loup
#~ @mode(freestanding)
#~
interface Source[T] {
    func next(): Option[T];
}

# Hands out frame numbers from a fixed range.
struct FrameRange implements Source[uint64] {
    var current: uint64;
    var limit: uint64;

    func next(): Option[uint64] {
        if (this.current >= this.limit) {
            return None
        }
        let frame = this.current
        this.current = this.current + 1u64
        return Some(frame)
    }
}

func countFrames(edits frames: FrameRange): uint64 {
    var n: uint64 = 0
    while (true) {
        match frames.next() {
            Some(f) => { n = n + 1u64 }
            None => { return n }
        }
    }
    return n
}
```

*Listing 8-7: A generic interface implemented for one element type*

## Lifetime parameters

Types and functions can also be parameterized by a **lifetime**, written `lifetime ~name` in the bracket list. A borrowed field or parameter labelled `borrow[~name]` is then tied to that region, and the compiler checks that the value does not outlive what it borrows from:

```loup
#~ @mode(freestanding)
#~
# A view of a frame's header that borrows the frame's text.
struct HeaderView[lifetime ~frame] {
    borrow[~frame] data: string;
    var start: int;
}

func header[lifetime ~frame](borrow[~frame] packet: string): HeaderView[~frame] {
    return HeaderView(data = packet, start = 0)
}
```

*Listing 8-8: A lifetime-parameterized view*

The labels are erased after checking; they cost nothing at run time. Chapter 11 explains regions and what the 1.25 checker enforces.

## What freestanding leaves out

- **Default interface methods** — not available (shown above).
- **`dynamic class`** — rejected with `E_FREESTANDING_DYNAMIC`; it needs a runtime type table.
- **Reflection** — `core.reflect` and `core.inspect` cannot be imported in freestanding (chapter 20). Every question about types is answered at compile time.
- **`Any`** — the Language Reference excludes `Any` from freestanding union types and generic code that would need a runtime type witness (§3-3.4.3, §1-4.5). The 1.25 checker does not reject an `Any` return type yet; do not use `Any` in freestanding code — use an enum or a union of concrete types.

## Summary

- Interfaces are declared with `;`-terminated method signatures and implemented with `implements` on a struct or class; there is no `impl` block.
- Calls through an interface-typed value use static type-id dispatch; generics are monomorphized. Neither needs a runtime or an allocator.
- Default interface methods are not available in freestanding; write a free function over the interface instead.
- Write bounds as `where` clauses in 1.25; the inline form `[L: Logger]` does not give the body the interface's methods yet.
- Explicit type arguments `f[T](…)` instantiate phantom-typed APIs; there are no `const N` type parameters — size buffers with constants.
- Lifetime parameters (`[lifetime ~a]`, `borrow[~a]`) are checked and erased.
- `dynamic class`, reflection and `Any` do not belong in freestanding code.

Next: error handling with `Result`.
