# Chapter 7: Structs

A struct is a value type for plain data: copied on assignment, compared field by field, and never reference-counted. This chapter covers declaring and constructing structs, mutable fields, methods and operators, `with` copies, generic structs, and the rule that keeps structs pure values: no class-typed fields.

## Declaring and constructing a struct

Struct fields are written `name: Type;` and are immutable unless declared `var`. A field may have a default value. The compiler generates a constructor that takes every field by name:

```loup
@mode(host.native)

struct RetryPolicy {
    attempts: int = 3;
    delayMs: int = 100;
    var backoff: float = 2.0;
}

func main(): void {
    let standard = RetryPolicy();
    let patient = RetryPolicy(attempts = 10, delayMs = 500);
    println(standard);
    println(patient);
    println(patient.attempts);
}
```

*Listing 7-1: A struct with defaults and its generated constructor*

```text
RetryPolicy(attempts: 3, delayMs: 100, backoff: 2.0)
RetryPolicy(attempts: 10, delayMs: 500, backoff: 2.0)
10
```

Arguments must be named. A positional call, natural if you come from C or Rust tuple structs, is rejected, and the error lists every field it could not match:

```loup,does_not_compile
@mode(host.native)

struct Point {
    x: float;
    y: float;
}

func main(): void {
    let p = Point(1.0, 2.0);
    println(p);
}
```

```text
main.lphc:9:13: error[E0001]: struct 'Point' constructor requires named arguments: Point(field1=value1, ...)
main.lphc:9:13: error[E0001]: missing required field 'x' in 'Point' constructor call
main.lphc:9:13: error[E0001]: missing required field 'y' in 'Point' constructor call
```

Write `Point(x = 1.0, y = 2.0)`. A field without a default must always be given: `Point(x = 1.0)` is "missing required field 'y'".

A struct has no `new(...)` constructor of its own. If construction needs validation, write a function that checks its input and returns the struct (or a class, chapter 9).

## Value semantics

Assigning a struct, passing it to a function, or storing it in a collection copies it. Two struct values are equal when all their fields are equal (Language Reference §1-10.3):

```loup
#~ @mode(host.native)
struct Counter {
    name: string;
    var count: int = 0;
}

func main(): void {
    var hits = Counter(name = "hits");
    var snapshot = hits;
    hits.count = hits.count + 1;
    println(hits);
    println(snapshot);
    println(snapshot == Counter(name = "hits"));
}
```

*Listing 7-2: Assignment copies a struct*

```text
Counter(name: "hits", count: 1)
Counter(name: "hits", count: 0)
true
```

Changing `hits` did not change `snapshot`. Only `var` fields can be assigned:

```loup,does_not_compile
@mode(host.native)

struct Counter {
    name: string;
    var count: int = 0;
}

func main(): void {
    var hits = Counter(name = "hits");
    hits.name = "misses";
}
```

```text
main.lphc:10:5: error[E0001]: cannot assign to immutable field 'name' of struct 'Counter'
```

To make a changed copy of an immutable field, use `with` (below).

### Changing a caller's struct

Because a struct argument is copied, a function that assigns to its parameter's fields changes only its own copy. To change the caller's struct, take it as `edits`; then the parameter is the caller's value itself (Language Reference §3-2.4.9):

```loup
#~ @mode(host.native)
#~ struct Counter {
#~     name: string;
#~     var count: int = 0;
#~ }
func record(edits c: Counter): void {
    c.count = c.count + 1;
}

func main(): void {
    var hits = Counter(name = "hits");
    record(edits: hits);
    record(edits: hits);
    println(hits.count);
}
```

*Listing 7-3: An `edits` struct parameter*

```text
2
```

## Methods and operators

Methods are declared inside the struct and read its fields through `this`. A method that "changes" a struct usually returns a new one. Operators are methods declared `operator func`. A first attempt at a money type usually looks like this:

```loup,does_not_compile
@mode(host.native)

struct Money {
    cents: int;

    pub operator func +(other: Money): Money {
        return Money(cents = this.cents + other.cents);
    }
}

func main(): void {
    let total = Money(cents = 1999) + Money(cents = 501);
    println(total.cents);
}
```

```text
main.lphc:6:5: error[E_OPERATOR_NO_INTERFACE]: E_OPERATOR_NO_INTERFACE: operator func '+' on 'Money' requires `implements Add` on the enclosing type
main.lphc:12:17: error[E_OPERATOR_MISSING]: type 'Money' does not implement 'Add' required for operator '+' (E_OPERATOR_MISSING)
```

Each operator belongs to an interface, and the type must declare it: `+` is `Add[Rhs, Result]`, with the right-hand operand type and the result type as arguments (Language Reference §1-5.1):

```loup
#~ @mode(host.native)
struct Money implements Add[Money, Money] {
    cents: int;

    pub operator func +(other: Money): Money {
        return Money(cents = this.cents + other.cents);
    }

    pub func format(): string {
        let dollars = this.cents // 100;
        let rest = this.cents % 100;
        let pad = if (rest < 10) { "0" } else { "" };
        return i"${$ dollars $}.{$ pad $}{$ rest $}";
    }
}

func main(): void {
    var total = Money(cents = 0);
    for (price in [Money(cents = 1999), Money(cents = 501)]) {
        total = total + price;
    }
    println(total.format());
}
```

*Listing 7-4: A money type with `+`*

```text
$25.00
```

Money is kept in integer cents because `decimal` is not available yet (chapter 3).

The specification also allows `pub static func` on structs (for example a `Money.zero()` factory). In Stage0 1.25.1 such a call compiles but stops `loup run` with `internal interpreter limitation: unsupported instruction: CallVirtual zero on None`, so use a module-level function or a constructor call with defaults instead.

## `with` copies

`value with (field = newValue, …)` makes a copy with some fields replaced. It works on immutable fields, which is its main use:

```loup
#~ @mode(host.native)
#~ struct RetryPolicy {
#~     attempts: int = 3;
#~     delayMs: int = 100;
#~     var backoff: float = 2.0;
#~ }
#~ func main(): void {
let base = RetryPolicy();
let aggressive = base with (attempts = 8, delayMs = 20);
println(base);
println(aggressive);
#~ }
```

*Listing 7-5: Copy with changes*

```text
RetryPolicy(attempts: 3, delayMs: 100, backoff: 2.0)
RetryPolicy(attempts: 8, delayMs: 20, backoff: 2.0)
```

A misspelled field is caught: `base with (atempts = 8)` is `E_WITH_COPY_UNKNOWN_FIELD: field 'atempts' does not exist on struct 'RetryPolicy'`. `with` on a class instance is `E_WITH_COPY_WRONG_TYPE`: only values can be copied this way.

> ⚠ **Planned.** Struct destructuring, `let Point(x = px, y = py) = p` and `Point(x = 0.0, y = 0.0) => …` in a `match` arm, is specified (Language Reference §1-10.8) but does not parse in 1.25.1. Read the fields instead, and match on their values with guards.

## Generic structs

A struct can take type parameters. They are usually inferred from the constructor arguments:

```loup
#~ @mode(host.native)
struct Pair[A, B] {
    first: A;
    second: B;
}

func main(): void {
    let labelled = Pair(first = "port", second = 8080);
    let explicit = Pair[int, bool](first = 1, second = true);
    println(labelled);
    println(explicit.second);
}
```

*Listing 7-6: A generic struct*

```text
Pair(first: "port", second: 8080)
true
```

Chapter 11 covers generics in general.

## Structs hold values, not objects

A struct may contain primitives, strings, bytes, collections, enums, tuples and other structs. It may not contain a class instance (Language Reference §1-10.3.1, the pure value type invariant):

```loup,does_not_compile
@mode(host.native)

class Connection {
    pub let host: string;
    new(host: string) { this.host = host; }
}

struct Session {
    id: int;
    conn: Connection;
}

func main(): void {
    let s = Session(id = 1, conn = Connection("db"));
}
```

```text
main.lphc:8:1: error[E_STRUCT_CLASS_FIELD]: E_STRUCT_CLASS_FIELD: field 'conn' in struct 'Session' has class type 'Connection'. Migration: (a) convert struct to class, (b) replace field with a value type, (c) use structures.* with a class, or (d) add T: ValueType constraint
```

If a struct could hold a class reference, copying the struct would silently share the object, and a "copy" could be changed through someone else's reference. The rule keeps "struct" meaning "independent value". The message lists the ways out; the usual one is (a): when something holds a connection, it is an object with identity, so make it a class:

```loup
#~ @mode(host.native)
class Connection {
    pub let host: string;
    new(host: string) { this.host = host; }
}

class Session {
    pub let id: int;
    pub let conn: Connection;
    new(id: int, conn: Connection) {
        this.id = id;
        this.conn = conn;
    }
}

func main(): void {
    let s = Session(1, Connection("db.local"));
    println(i"session {$ s.id $} on {$ s.conn.host $}");
}
```

*Listing 7-7: A class may hold another class*

```text
session 1 on db.local
```

`host.native` adds one exception: a struct may hold a `unique` value, which makes the struct itself move-only. Chapter 17 covers it.

## Layout for C

C libraries expect structs laid out by the C rules: fields in declaration order with natural alignment. The `@repr(C)` and `@align(n)` annotations request that layout (Language Reference §3-3.10, §3-3.11).

> ⚠ **Planned.** The feature matrix (Language Reference §1-2.6) lists `@repr` and `@align` for freestanding code only, while the annotation table describes `@repr` as "Freestanding/native". Stage0 1.25.1 accepts both annotations in a `host.native` file, but there is no way to hand such a struct to C code from a native program yet: the FFI library (`native.ffi`) is not shipped. Chapter 19 covers what native FFI exists today; *The Loup Freestanding Book* covers struct layout in depth.

## Summary

- Struct fields are `name: Type;`, immutable unless `var`, optionally with defaults; the generated constructor takes named arguments only.
- Structs are copied on assignment and compared field by field. Pass `edits` to change a caller's struct.
- Methods read fields through `this`; operators are `operator func` plus `implements Add[…]` and friends. Static methods on structs fail under `loup run` in 1.25.1.
- `value with (field = v)` makes a modified copy. Struct destructuring is ⚠ planned.
- A struct may not contain a class instance (`E_STRUCT_CLASS_FIELD`); make the container a class instead.
- `@repr(C)` and `@align(n)` are accepted but not usable for native interop yet.
- Chapter 8 covers enums and pattern matching.
