# Chapter 9: Classes and Objects

A struct (Chapter 7) is a value: two `Point(x = 3, y = 4)` are the same point, and assigning one copies it. A class describes an object with identity: two shopping carts holding the same items are still two carts, and assigning a cart to a second name gives you a second name for the *same* cart.

This chapter covers declaring classes, constructors, the `modifier`/`safe` rules for changing an object's state, inheritance, runtime type checks, static members, the ownership verbs on class parameters, cleanup with `del`, `weak` fields, and generic classes. The normative text is in the Language Reference §1-8 (classes) and §3-2.4.9–§3-2.4.13 (ownership verbs, `modifier`, `safe`, `synchronized`).

**How class instances are managed.** Every Loup mode presents ARC semantics for class instances: an instance is shared by reference and released when its last strong reference goes away (Language Reference §1-8.1.1). That holds for `web.compiled` and `web.scripted` too. On the Wasm target, instances are host-managed references, not Wasm GC objects; Wasm GC is a possible future implementation strategy, not what Loup 1.25 emits (Language Reference §3-1). If you have read older Loup material that said "web code is garbage-collected, so cycles are free", that is not how Loup works today; see "Reference cycles and `weak`" below.

## Declaring a class

```loup
@mode(web.compiled)

class Counter {
    pub var count: int;

    new(initial: int) {
        this.count = initial;
    }

    pub modifier func increment(): void {
        this.count = this.count + 1;
    }

    pub func get(): int {
        return this.count;
    }
}

func main(): void {
    let clicks = Counter(0);
    safe {
        clicks.increment();
        clicks.increment();
    }
    println(clicks.get());

    let alias = clicks;          # a second name for the same object
    safe { alias.increment(); }
    println(clicks.get());
    println(clicks);
}
```

*Listing 9-1: A class with a constructor, a mutating method, and a read-only method*

```text
2
3
Counter(count: 3)
```

The pieces:

- `class Counter { … }` declares the class. With no `:` clause it extends `Any`, the root of every class hierarchy.
- `pub var count: int;` is a field. Every class field is declared with `let` (assigned once, in the constructor) or `var` (reassignable). Fields are private to the class unless marked `pub`.
- `new(initial: int) { … }` is the constructor. It has no return type, and a class has at most one. Inside it, `this` is the object being built. Write `this.count`, not a bare `count`.
- `pub modifier func increment()` writes a `var` field, so it must be declared `modifier`. Calling a `modifier` method on an ordinary class instance must happen inside `safe { … }` or a `safe func`. The rule and its reasons are below.
- `pub func get()` only reads, so it needs neither.
- `let alias = clicks;` copies the reference, not the object. `alias.increment()` changes the object both names point at. Compare Chapter 7, where `let b = a;` on a struct made an independent copy.
- `println(clicks)` renders the object structurally: the class name and each field.

### Fields need `let` or `var`

If you come from TypeScript, a bare `count: int;` looks like a field declaration. In Loup it is an error:

```loup,does_not_compile
@mode(web.compiled)

class Counter {
    count: int;
}

func main(): void {
    println("never runs");
}
```

```text
main.lpweb:4:5: error[E0001]: E_BARE_FIELD: class field `count` must be declared `let` or `var`; write `var count: ...` (mutable) or `let count: ...` (immutable)
```

The compiler wants you to say whether the field can change after construction. Pick `var` for a counter:

```loup
@mode(web.compiled)

class Counter {
    pub var count: int = 0;
}

func main(): void {
    println(Counter().count);
}
```

```text
0
```

A field with a default value (`= 0`) does not need to be set in a constructor. This class has no `new` at all, so Loup generates a constructor for it (see "Constructors" below).

### Fields are private unless `pub`

A field without `pub` can be read and written only inside the class's own methods and those of its subclasses (ruling FPRIV-1). A function in the same file cannot reach it:

```loup,does_not_compile
@mode(web.compiled)

class Account {
    var balance: int = 0;
}

func main(): void {
    let a = Account();
    println(a.balance);
}
```

```text
main.lpweb:9:13: error[E0001]: member 'balance' of class 'Account' is private
```

Either mark the field `pub`, or keep it private and expose a method. The second is usually better for state you want to protect:

```loup
@mode(web.compiled)

class Account {
    var balance: int = 0;

    pub func current(): int {
        return this.balance;
    }
}

func main(): void {
    let a = Account();
    println(a.current());
}
```

```text
0
```

Methods and module-level declarations without `pub` are private to the module (file) instead; there is no `protected`, `pub(package)` or `pub(module)` (Language Reference §1-8.2).

## Constructors

A class with an explicit `new(...)` is built by calling the class name with that constructor's arguments, positionally or by name: `Counter(0)` or `Counter(initial = 0)`.

A class with no `new` gets a generated constructor that takes each field **by name**, including inherited fields (ruling IMPCTOR-1). Fields with a default are optional:

```loup
@mode(web.compiled)

class Account {
    pub let owner: string;
    pub var balance: int = 0;
}

class Savings : Account {
    pub let rate: float = 0.02;
}

func main(): void {
    let checking = Account(owner = "ann");
    let savings = Savings(owner = "bo", balance = 100);
    println(checking);
    println(savings);
}
```

*Listing 9-2: Generated constructors fill fields by name, inherited fields included*

```text
Account(owner: "ann", balance: 0)
Savings(owner: "bo", balance: 100, rate: 0.02)
```

Leaving out a field that has no default is an error, because the object would be incomplete:

```loup,does_not_compile
@mode(web.compiled)

class Account {
    pub let owner: string;
    pub var balance: int = 0;
}

func main(): void {
    let a = Account(balance = 5);
    println(a);
}
```

```text
main.lpweb:9:13: error[E0001]: missing required field 'owner' in 'Account' constructor call
```

Pass `owner = "…"`, or give the field a default.

## Changing an object's state: `modifier` and `safe`

Because class instances are shared by reference, a method that changes a field changes it for every holder of that reference. Loup makes such methods visible in two places: on the declaration (`modifier`) and at the call (`safe`). The rules are in the Language Reference §3-2.4.11 and §3-2.4.12.

First, a method that writes a `var` field must say so:

```loup,does_not_compile
@mode(web.compiled)

class Counter {
    pub var count: int = 0;

    pub func increment(): void {
        this.count = this.count + 1;
    }
}

func main(): void {
    let c = Counter();
    c.increment();
    println(c.count);
}
```

```text
main.lpweb:7:9: error[E_MODIFIER_REQUIRED]: E_MODIFIER_REQUIRED: writing to `var` field 'count' of `self` in method 'increment' of class 'Counter' requires the method to be declared as `modifier`
```

Add `modifier`, and the compiler moves on to the call site:

```loup,does_not_compile
@mode(web.compiled)

class Counter {
    pub var count: int = 0;

    pub modifier func increment(): void {
        this.count = this.count + 1;
    }
}

func main(): void {
    let c = Counter();
    c.increment();
    println(c.count);
}
```

```text
main.lpweb:13:5: error[E_SAFE_CONTEXT_REQUIRED]: E_SAFE_CONTEXT_REQUIRED: `modifier` method 'increment' called on ARC receiver of type 'Counter' outside `safe {}` or `safe func` context — wrap the call in `safe { ... }` or mark the enclosing function as `safe func`
```

A `safe` context is where Loup checks that a mutation does not happen through a reference somebody else is also using. You provide one in either of two ways:

```loup
@mode(web.compiled)

class Counter {
    pub var count: int = 0;

    pub modifier func increment(): void {
        this.count = this.count + 1;
    }
}

safe func incrementTwice(edits c: Counter): void {
    c.increment();
    c.increment();
}

func main(): void {
    let c = Counter();
    safe { c.increment(); }          # 1. a safe block around the call
    incrementTwice(edits: c);        # 2. a safe func, which takes the requirement on itself
    println(c.count);
}
```

*Listing 9-3: Calling a `modifier` method from a `safe` block and from a `safe func`*

```text
3
```

The same rule covers assigning a `pub var` field from outside the class: `c.count = 5;` must also be inside `safe { … }`. Stage0's message for that case calls the field a "`modifier` method", but the fix is the same.

> **Not checked yet.** Inside a `safe` context, the Language Reference also forbids calling a `modifier` while another alias of the receiver is live (`E_SAFE_ALIAS_MUTABLE`). Loup 1.25 does not check that yet: in Listing 9-1, `alias.increment()` compiles even though `clicks` is still in scope. Treat the rule as a design constraint you follow by hand.

## Inheritance

A class extends at most one parent class, written after a colon. The subclass constructor calls the parent's constructor with `super(...)`, and a method that replaces a parent method is marked `override`:

```loup
@mode(web.compiled)

class Shape {
    pub let name: string;

    new(name: string) {
        this.name = name;
    }

    pub func area(): float {
        return 0.0;
    }

    pub func describe(): string {
        return i"{$ this.name $} with area {$ this.area() $}";
    }
}

class Circle : Shape {
    pub let radius: float;

    new(radius: float) {
        super("circle");
        this.radius = radius;
    }

    pub override func area(): float {
        return 3.14159 * this.radius * this.radius;
    }
}

class Rect : Shape {
    pub let w: float;
    pub let h: float;

    new(w: float, h: float) {
        super("rect");
        this.w = w;
        this.h = h;
    }

    pub override func area(): float {
        return this.w * this.h;
    }
}

func main(): void {
    let shapes: list[Shape] = [Circle(1.0), Rect(2.0, 3.0)];
    for (s in shapes) {
        println(s.describe());
    }
}
```

*Listing 9-4: A small shape hierarchy with virtual dispatch*

```text
circle with area 3.14159
rect with area 6
```

`describe()` is written once, in `Shape`, and calls `this.area()`. Dispatch is virtual: each object runs its own class's `area()`, even through a variable of type `Shape`.

Leave out `override` and the compiler refuses, because an accidental name clash with a parent method is a common source of bugs:

```loup,does_not_compile
@mode(web.compiled)

class Shape {
    pub func area(): float {
        return 0.0;
    }
}

class Square : Shape {
    pub let side: float = 1.0;

    pub func area(): float {
        return this.side * this.side;
    }
}

func main(): void {
    println(Square().area());
}
```

```text
main.lpweb:12:5: error[E_OVERRIDE_REQUIRED]: method `area` on class `Square` shadows a parent method on `Shape` and must be marked `override` (E_OVERRIDE_REQUIRED)
```

Writing `pub override func area()` fixes it. The reverse mistake, `override` on a method with nothing to override, is `E_OVERRIDE_NO_TARGET`.

Two more inheritance tools:

- `super.method(args)` calls the parent's version from inside an override.
- `final class` cannot be extended (`E_FINAL_EXTEND`), and `final func` cannot be overridden.

```loup
@mode(web.compiled)

class Logger {
    pub func format(msg: string): string {
        return "[log] " + msg;
    }
}

class TimedLogger : Logger {
    pub override func format(msg: string): string {
        return "12:00 " + super.format(msg);
    }
}

func main(): void {
    let log: Logger = TimedLogger();
    println(log.format("cart saved"));
}
```

```text
12:00 [log] cart saved
```

> ⚠ **Planned.** The Language Reference (§1-8.3) lets an `abstract class` declare body-less methods (`abstract func area(): float;`) that subclasses implement without `override`. Stage0 1.25.1 rejects the body-less declaration ("method 'area' may not return a value on all code paths"). Until it lands, give the base method a body as in Listing 9-4, or describe the contract with an interface (Chapter 10).

## Checking an object's runtime type: `is` and `as?`

`x is T` asks whether `x`'s runtime class is `T` or a subclass of it. The Language Reference (§1-4.7) says the compiler then narrows `x` to `T` inside the `if`. Loup 1.25 does not narrow yet, so the TypeScript-style code below fails:

```loup,does_not_compile
@mode(web.compiled)

func label(s: Shape): string {
    if (s is Circle) {
        return i"circle, r = {$ s.radius $}";
    }
    return s.name;
}

func main(): void {
    println(label(Circle(2.5)));
}
#~
#~ class Shape {
#~     pub let name: string;
#~     new(name: string) { this.name = name; }
#~ }
#~ class Circle : Shape {
#~     pub let radius: float;
#~     new(radius: float) { super("circle"); this.radius = radius; }
#~ }
```

```text
main.lpweb:5:33: error[E0001]: type 'Shape' has no member 'radius'
```

Inside the `if`, `s` still has the static type `Shape`. Convert it explicitly with `as?`, which gives `Some(value)` when the runtime type matches and `None` otherwise:

```loup
@mode(web.compiled)

class Shape {
    pub let name: string;
    new(name: string) { this.name = name; }
}

class Circle : Shape {
    pub let radius: float;
    new(radius: float) { super("circle"); this.radius = radius; }
}

class Rect : Shape {
    new() { super("rect"); }
}

func label(s: Shape): string {
    match s as? Circle {
        Some(c) => { return i"circle, r = {$ c.radius $}"; },
        None => { return s.name; },
    }
}

func main(): void {
    println(label(Circle(2.5)));
    println(label(Rect()));
    println(Rect() is Shape);
}
```

*Listing 9-5: `as?` turns a runtime type check into an `Option`*

```text
circle, r = 2.5
rect
true
```

Two cautions for Loup 1.25:

- The Language Reference treats `instanceof` as a synonym of `is`. In Stage0 1.25.1, `instanceof` in an expression crashes the parser (a Rust panic, exit status 101). Write `is`.
- `x as T` (without `?`) is specified to throw `TypeError` on a mismatch. Stage0 does not check: the cast succeeds and the program fails later, at the first field access, with an internal interpreter error. Use `as?` and handle `None`.

## Static members

`static` fields and methods belong to the class rather than to an instance. A common use is a named factory; another is a per-class counter:

```loup
@mode(web.compiled)

class Ticket {
    static var issued: int = 0;
    pub let number: int;

    new() {
        Ticket.issued = Ticket.issued + 1;
        this.number = Ticket.issued;
    }

    pub static func count(): int {
        return Ticket.issued;
    }
}

class Temperature {
    pub static let FREEZING_K: float = 273.15;
    pub let kelvin: float;

    new(kelvin: float) {
        this.kelvin = kelvin;
    }

    pub static func fromCelsius(c: float): Temperature {
        return Temperature(c + Temperature.FREEZING_K);
    }

    pub func celsius(): float {
        return this.kelvin - Temperature.FREEZING_K;
    }
}

func main(): void {
    let a = Ticket();
    let b = Ticket();
    println(i"tickets {$ a.number $} and {$ b.number $}, {$ Ticket.count() $} issued");

    let room = Temperature.fromCelsius(21.5);
    println(room.kelvin);
}
```

*Listing 9-6: A static counter and a static factory method*

```text
tickets 1 and 2, 2 issued
294.65
```

Static members are reached through the class name: `Ticket.count()`, `Temperature.FREEZING_K`.

## Ownership verbs on class parameters

A parameter's verb says what a function may do with an argument (Language Reference §3-2.4.9). The same four verbs apply to every type; with classes they matter most, because the argument is a reference to an object the caller still holds.

| Verb | The function may | Caller writes |
|---|---|---|
| `reads` (the default when no verb is written) | read the object | `f(cart)` |
| `edits` | change the object | `f(edits: cart)` |
| `takes` | keep or consume it; the caller may not use it afterwards | `f(takes: cart)` |
| `shares` | keep another reference to it | `f(shares: cart)` |

```loup
@mode(web.compiled)

class Cart {
    pub var items: list[string] = [];
    pub var total: int = 0;

    pub modifier func add(name: string, cents: int): void {
        this.items.push(name);
        this.total = this.total + cents;
    }

    pub func count(): int {
        return this.items.len();
    }
}

func summary(cart: Cart): string {               # reads, implicitly
    return i"{$ cart.count() $} items, {$ cart.total $} cents";
}

safe func addGiftWrap(edits cart: Cart): void {
    cart.add("gift wrap", 300);
}

func checkout(takes cart: Cart): int {
    return cart.total;
}

func main(): void {
    let cart = Cart();
    safe {
        cart.add("book", 1500);
        cart.add("pen", 200);
    }
    println(summary(cart));
    addGiftWrap(edits: cart);
    println(summary(cart));
    let paid = checkout(takes: cart);
    println(i"paid {$ paid $}");
}
```

*Listing 9-7: `reads`, `edits` and `takes` on a class parameter*

```text
2 items, 1700 cents
3 items, 2000 cents
paid 2000
```

Each verb has a mistake that goes with it. The listings below use the `Cart` class from Listing 9-7 (hidden at the end of each listing).

**Changing an object you only `reads`.** A parameter with no verb is read-only:

```loup,does_not_compile
@mode(web.compiled)

func applyCoupon(cart: Cart): void {
    safe { cart.add("coupon", -100); }
}

func main(): void {
    let cart = Cart();
    applyCoupon(cart);
    println(cart.total);
}
#~
#~ class Cart {
#~     pub var items: list[string] = [];
#~     pub var total: int = 0;
#~     pub modifier func add(name: string, cents: int): void {
#~         this.items.push(name);
#~         this.total = this.total + cents;
#~     }
#~ }
```

```text
main.lpweb:4:12: error[E_READS_MUTATION]: E_READS_MUTATION: parameter 'cart' is a `reads` borrow (a parameter without a verb is read-only, §3-2.4.9) and cannot be mutated (`modifier` method `add` called on it); declare it `edits` and pass `edits: …` to change the caller's value, or copy it into a local first
```

The message tells you the fix: declare the parameter `edits cart: Cart` and call `applyCoupon(edits: cart)`.

**Forgetting the acknowledgement at the call site.** With `edits` declared, a plain `addGiftWrap(cart)` is refused. The caller has to write the verb too, so that a reader of `main` can see which calls change `cart`:

```loup,does_not_compile
@mode(web.compiled)

safe func addGiftWrap(edits cart: Cart): void {
    cart.add("gift wrap", 300);
}

func main(): void {
    let cart = Cart();
    addGiftWrap(cart);
    println(cart.total);
}
#~
#~ class Cart {
#~     pub var items: list[string] = [];
#~     pub var total: int = 0;
#~     pub modifier func add(name: string, cents: int): void {
#~         this.items.push(name);
#~         this.total = this.total + cents;
#~     }
#~ }
```

```text
main.lpweb:9:17: error[E_VERB_REQUIRED]: E_VERB_REQUIRED: parameter 'cart' requires 'edits' verb acknowledgment at call site
```

Write `addGiftWrap(edits: cart);`.

**`edits` without `safe func`.** The Language Reference (§3-2.4.12) says a function with an `edits` parameter is implicitly a `safe func`. Stage0 1.25.1 does not apply that rule yet, so leaving off `safe` fails at the first `modifier` call inside:

```loup,does_not_compile
@mode(web.compiled)

func addGiftWrap(edits cart: Cart): void {
    cart.add("gift wrap", 300);
}

func main(): void {
    let cart = Cart();
    addGiftWrap(edits: cart);
    println(cart.total);
}
#~
#~ class Cart {
#~     pub var items: list[string] = [];
#~     pub var total: int = 0;
#~     pub modifier func add(name: string, cents: int): void {
#~         this.items.push(name);
#~         this.total = this.total + cents;
#~     }
#~ }
```

```text
main.lpweb:4:5: error[E_SAFE_CONTEXT_REQUIRED]: E_SAFE_CONTEXT_REQUIRED: `modifier` method 'add' called on ARC receiver of type 'Cart' outside `safe {}` or `safe func` context — wrap the call in `safe { ... }` or mark the enclosing function as `safe func`
```

Write `safe func addGiftWrap(...)`, as in Listing 9-7. That spelling is also correct under the specified rule, so it will keep working.

**Using an object after `takes`.** `takes` hands the object over. The caller's binding is consumed, and using it again is an error:

```loup,does_not_compile
@mode(web.compiled)

func checkout(takes cart: Cart): int {
    return cart.total;
}

func main(): void {
    let cart = Cart();
    let paid = checkout(takes: cart);
    println(paid);
    println(cart.total);
}
#~
#~ class Cart {
#~     pub var total: int = 0;
#~ }
```

```text
main.lpweb:11:13: error[E_USE_AFTER_TAKE]: E_USE_AFTER_TAKE: binding 'cart' was consumed by a `takes` parameter and cannot be used again
```

Read everything you need from the cart before handing it to `checkout`, or have `checkout` return what you need afterwards (here, it already returns the total).

## `synchronized` classes

A `synchronized class` guards every instance with a runtime lock, so its `modifier` methods can be called without a `safe` context (Language Reference §3-2.4.13). In the browser, Loup code runs on one thread, so the lock matters only for code that is shared with multi-threaded host builds. It is shown here because you will meet it in libraries:

```loup
@mode(web.compiled)

synchronized class HitCounter {
    var hits: int = 0;

    pub modifier func hit(): void {
        this.hits = this.hits + 1;
    }

    pub modifier func total(): int {     # even a read of a `var` field takes the lock
        return this.hits;
    }
}

func main(): void {
    let page = HitCounter();
    page.hit();                      # no safe {} needed: the lock provides exclusivity
    page.hit();
    println(page.total());
}
```

```text
2
```

Inside a `synchronized` class, every method that touches a `var` field, even only to read it, must be a `modifier`, so that the access happens under the lock. A plain `func total()` here is rejected with `E_MODIFIER_REQUIRED` ("reading `var` field 'hits' … is unsynchronized"). For the same reason, keep the mutable fields private: the compiler warns (`E_PUB_VAR_ON_SYNCHRONIZED`) about a `pub var`, because reading or writing it from outside would bypass the lock.

## `unique` values in web code

`unique` marks a value with exactly one owner: assigning it moves it (Language Reference §3-2.4.7). The Language Reference allows both `unique let` bindings and whole `unique class` declarations in every mode, but Stage0 1.25.1 accepts `unique class` only in `host.native` and `freestanding`:

```loup,does_not_compile
@mode(web.compiled)

unique class Session {
    pub let user: string;
    new(user: string) { this.user = user; }
}

func main(): void {
    unique let s = Session("ann");
    println(s.user);
}
```

```text
main.lpweb:3:1: error[E_UNIQUE_CLASS_ARC_BINDING]: E_UNIQUE_CLASS_ARC_BINDING: `unique class 'Session'` is only valid in `@mode(native)` or `@mode(freestanding)`
```

(The message still spells the native mode by its retired name; the current name is `host.native`.) In web code, declare an ordinary class and put `unique` on the binding instead:

```loup
@mode(web.compiled)

class Session {
    pub let user: string;
    new(user: string) { this.user = user; }
}

func main(): void {
    unique let s = Session("ann");
    let current = s;                 # moves the session; `s` is no longer usable
    println(current.user);
}
```

```text
ann
```

Reading `s.user` after the move would be `E_USE_AFTER_MOVE`. Chapter 20 returns to moves and borrowing.

## Cleanup: `del` and `finalize()`

A class that holds something that must be released (a connection, a subscription, a timer) declares a `finalize()` method. Declaring it makes the class `Finalizable`. `del x` runs `finalize()` at that point and ends the binding (Language Reference §1-6.11, §1-8.9):

```loup
@mode(web.compiled)

class Subscription {
    pub let topic: string;

    new(topic: string) {
        this.topic = topic;
        println(i"subscribed to {$ topic $}");
    }

    pub func finalize(): void {
        println(i"unsubscribed from {$ this.topic $}");
    }
}

func main(): void {
    let prices = Subscription("prices");
    println("rendering prices");
    del prices;
    println("done");
}
```

*Listing 9-8: `del` runs `finalize()` deterministically*

```text
subscribed to prices
rendering prices
unsubscribed from prices
done
```

After `del`, the name is gone:

```loup,does_not_compile
@mode(web.compiled)

class Subscription {
    pub let topic: string;
    new(topic: string) { this.topic = topic; }
    pub func finalize(): void { println("unsubscribed"); }
}

func main(): void {
    let prices = Subscription("prices");
    del prices;
    println(prices.topic);
}
```

```text
main.lpweb:12:13: error[E_USE_AFTER_DEL]: E_USE_AFTER_DEL: use of binding 'prices' after `del` — the binding is uninitialized once it has been passed to `del` (§1-6.11)
```

Under the specified ARC rules, `finalize()` also runs when the last strong reference goes away, for example when a function that created the object returns. **On the Loup 1.25 interpreter it does not**: an object that goes out of scope without `del` is never finalized. Call `del` for any cleanup that must happen. (A `with` block, Chapter 5, also gives scope-bound cleanup, but only for types that implement `Scoped[T]`.)

## Reference cycles and `weak`

Under ARC semantics, two objects that hold strong references to each other keep each other alive. A tree whose children point back to their parent is the usual example. The specified fix is to make the back-pointer `weak`: it does not keep its target alive, and reads as `None` once the target is released (Language Reference §1-8).

```loup
@mode(web.compiled)

class TreeNode {
    pub let label: string;
    pub var children: list[TreeNode] = [];
    pub weak var parent: TreeNode?;

    new(label: string) {
        this.label = label;
    }
}

func main(): void {
    let menu = TreeNode("menu");
    let item = TreeNode("settings");
    safe {
        item.parent = Some(menu);
        menu.children.push(item);
    }
    println(menu.children.len());
    match item.parent {
        Some(p) => println(i"parent: {$ p.label $}"),
        None => println("parent released"),
    }
}
```

*Listing 9-9: A `weak` back-pointer from child to parent*

```text
1
parent: menu
```

The `weak` field has an optional type (`TreeNode?`) and is assigned `Some(menu)`.

What works and what does not in web modes today:

- `weak` fields compile and run in `web.compiled` and `web.scripted`, on the interpreter and in a `--target browser` Wasm build. The Language Reference disagrees with itself here: the mode matrix (§1-2.6) lists `weak` for both web modes, while §3-1.2 says the Wasm target has no zeroing side table for it. Follow the matrix, but note the next point.
- **On the interpreter, a `weak` field is not cleared** when its target's last strong reference goes away; it behaves like a strong reference. Write `weak` where the design calls for it, and do not rely on reading `None`.
- Closure capture lists (`func() [weak self] { … }`) are refused in web modes: "capture lists are only allowed in @compiled files targeting --target native".

> ⚠ **Planned.** `arc let` / `arc var` (advisory pinning, Language Reference §1-8) are in the mode matrix for web modes but do not parse in Loup 1.25 (`error[E0003]: expected ';'`).

## Generic classes

A class can take type parameters in square brackets, like the built-in `list[T]` and `dict[K, V]`:

```loup
@mode(web.compiled)

class Stack[T] {
    var items: list[T] = [];

    pub modifier func push(value: T): void {
        this.items.push(value);
    }

    pub modifier func pop(): Option[T] {
        if (this.items.len() == 0) {
            return None;
        }
        return Some(this.items.pop());
    }

    pub func size(): int {
        return this.items.len();
    }
}

func main(): void {
    let undo: Stack[string] = Stack();
    safe {
        undo.push("type 'a'");
        undo.push("type 'b'");
        undo.push("delete line");
    }
    println(undo.size());
    safe {
        println(undo.pop().unwrap_or("nothing to undo"));
        println(undo.pop().unwrap_or("nothing to undo"));
    }
    println(undo.size());

    let empty: Stack[int] = Stack();
    safe { println(empty.pop().unwrap_or(-1)); }
}
```

*Listing 9-10: A generic undo stack*

```text
3
delete line
type 'b'
1
-1
```

Give the binding its full type (`let undo: Stack[string] = Stack();`). In Stage0 1.25.1, explicit type arguments on the constructor call (`Stack[string]()`) are not applied: the call type-checks against an unresolved parameter and fails with messages such as `expected 'T0', found 'string'`. Chapter 11 covers type parameters and their bounds.

## A worked example: an observable store

Here is a class you might write in a web app: a store that holds a value and notifies listeners when it changes. It combines a generic class, a list of function values, `modifier` methods and a closure that updates an outer variable, which must declare it `nonlocal` (closures are covered in Chapter 4).

```loup
@mode(web.compiled)

class Store[T] {
    var value: T;
    var listeners: list[fn(T): void] = [];

    new(initial: T) {
        this.value = initial;
    }

    pub func get(): T {
        return this.value;
    }

    pub modifier func subscribe(listener: fn(T): void): void {
        this.listeners.push(listener);
    }

    pub modifier func set(next: T): void {
        this.value = next;
        for (listener in this.listeners) {
            listener(next);
        }
    }
}

func main(): void {
    let cartSize: Store[int] = Store(0);
    var renders = 0;
    safe {
        cartSize.subscribe(func(n: int): void {
            println(i"badge shows {$ n $}");
        });
        cartSize.subscribe(func(n: int): void {
            nonlocal renders;
            renders = renders + 1;
        });
        cartSize.set(1);
        cartSize.set(3);
    }
    println(i"current: {$ cartSize.get() $}, renders: {$ renders $}");
}
```

*Listing 9-11: A generic observable store*

```text
badge shows 1
badge shows 3
current: 3, renders: 2
```

The store has identity (every part of the app that holds it sees the same value), its listener list is changed in place, and every change goes through a `modifier` method. That is the profile of a class rather than a struct. The reactive `signal` type planned for browser UI (Chapter 16) follows the same pattern.

## Try it yourself

1. Add a `pub modifier func remove(name: string, cents: int)` to `Cart` and a `safe func` that applies a discount through an `edits` parameter. Then call it without `edits:` and read the diagnostic.
2. Give `Shape` a `perimeter()` method and override it in `Circle` and `Rect`. Remove one `override` keyword and read the error.
3. Add an `unsubscribe` to `Store` that removes the most recently added listener, and check with `renders` that it stops being called.

## Summary

- A class has identity; assigning or passing an instance shares the same object. Every mode, the web modes included, presents ARC semantics for class instances.
- Fields are declared with `let` or `var` (a bare field is `E_BARE_FIELD`) and are private to the class unless `pub`.
- `new(...)` is the constructor; without one, Loup generates a constructor that takes the fields by name.
- A method that writes a field is a `modifier`, and calling it on an ordinary instance requires `safe { … }` or a `safe func`.
- Single inheritance with `: Parent`, `super(...)`, `super.method()`, and mandatory `override`. Body-less `abstract` methods are planned.
- Use `is` and `as?` for runtime type checks. Loup 1.25 does not narrow after `is`, and `instanceof` crashes the Stage0 parser.
- Parameter verbs: `reads` by default, `edits` and `takes` acknowledged at the call site. Stage0 needs an explicit `safe func` for a function that mutates through `edits`.
- `del` runs `finalize()` deterministically; the interpreter does not finalize objects that simply go out of scope.
- `weak` fields compile in web modes but are not cleared on the interpreter; capture lists and `arc let` are not available.
- Generic classes take `[T]`; annotate the binding (`let s: Stack[int] = Stack();`).
