# Chapter 9: Classes and ARC

Structs (chapter 7) are values. Classes are the other half of Loup's data model: objects with identity, shared by reference, whose lifetime is managed by automatic reference counting (ARC). This chapter covers class declarations, constructors, visibility, mutation through `modifier` methods, inheritance, and what ARC does in a native program, including the places where Loup 1.25.1 does not yet behave as specified.

## Declaring a class

A class has fields, an optional constructor named `new`, and methods. Every field is declared `let` (set once, in the constructor or by its initializer) or `var` (changeable later). Fields and methods are private to the class unless marked `pub` (ruling FPRIV-1). Inside the class, members are reached through `this`:

```loup
@mode(host.native)

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

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

    pub func describe(): string {
        return i"{$ this.owner $}: {$ this.balance $}";
    }

    pub modifier func deposit(amount: int): void {
        this.balance = this.balance + amount;
    }
}

func main(): void {
    let acct = Account("ada");
    safe {
        acct.deposit(50);
        acct.deposit(25);
    }
    println(acct.describe());
}
```

*Listing 9-1: A class with a private field and a `modifier` method*

```text
ada: 75
```

`Account("ada")` calls the constructor. There is at most one `new` per class. `deposit` writes a `var` field, so it is declared `modifier`, and calling it on `acct` happens inside a `safe { }` block. Both rules are explained below and in full in chapter 15.

### Mistake: a field without `let` or `var`

Fields need a keyword. A bare `name: Type`, as in TypeScript or in a Loup struct, is rejected:

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

class Endpoint {
    host: string;
}

func main(): void {
    println(Endpoint(host = "db").host);
}
```

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

### Mistake: reading a private field

`balance` has no `pub`, so only the class itself can read it:

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

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

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

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

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

Expose what callers need through a method (`describe()` in Listing 9-1) or mark the field `pub`. Keeping `var` fields private and changing them only through `modifier` methods keeps every change to an object's state inside the class.

### Generated constructors

A class without `new` gets a constructor generated from its fields, which takes them by name; fields with initializers are optional (ruling IMPCTOR-1):

```loup
#~ @mode(host.native)
class Endpoint {
    pub let host: string;
    pub let port: int = 443;
    pub var retries: int = 3;
}

func main(): void {
    let api = Endpoint(host = "api.example.com");
    let db = Endpoint(host = "db.local", port = 5432);
    println(i"{$ api.host $}:{$ api.port $} retries={$ api.retries $}");
    println(i"{$ db.host $}:{$ db.port $}");
}
```

*Listing 9-2: A generated constructor*

```text
api.example.com:443 retries=3
db.local:5432
```

A required field left out is reported as `missing required field 'host' in 'Endpoint' constructor call`.

## Classes are references

Assigning a class instance does not copy it. Both bindings refer to the same object, and a change made through one is visible through the other:

```loup
#~ @mode(host.native)
#~ class Account {
#~     pub let owner: string;
#~     var balance: int = 0;
#~     new(owner: string) { this.owner = owner; }
#~     pub func describe(): string { return i"{$ this.owner $}: {$ this.balance $}"; }
#~     pub modifier func deposit(amount: int): void { this.balance = this.balance + amount; }
#~ }
func main(): void {
    let mine = Account("ada");
    let alias = mine;
    safe { alias.deposit(40); }
    println(mine.describe());
}
```

*Listing 9-3: Two references to one object*

```text
ada: 40
```

Compare Listing 7-2, where assigning a struct made an independent copy. Use a class when a thing has identity and several parts of the program must see the same instance (a connection, a cache, a node in a graph); use a struct for plain data.

### Changing an object: `modifier` and `safe`

Because an object can be reached through several references, Loup controls who may change it. A method that writes a `var` field must be declared `modifier` (otherwise `E_MODIFIER_REQUIRED`), and calling a `modifier` method on an ordinary reference-counted instance must happen in a *safe context*: a `safe { }` block or a `safe func`. The same applies to assigning a `pub var` field from outside the class:

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

class Account {
    pub let owner: string;
    pub var label: string = "";
    new(owner: string) { this.owner = owner; }
}

func main(): void {
    let acct = Account("ada");
    acct.label = "savings";
    println(acct.label);
}
```

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

The message treats the field write as a call to a `modifier` named after the field. Write `safe { acct.label = "savings"; }`. A function that receives an object to change takes it as `edits` and is declared `safe func`, as in chapter 15's Listing 15-4. Chapter 15 explains what the safe context is for and which parts of the check 1.25.1 implements.

## Inheritance

A class extends at most one base class, written after a colon. The subclass constructor calls the base constructor with `super(...)`, and a method that replaces a base method must say `override`:

```loup
#~ @mode(host.native)
class Animal {
    pub let name: string;
    new(name: string) { this.name = name; }
    pub func speak(): string { return "..."; }
    pub func intro(): string { return i"{$ this.name $} says {$ this.speak() $}"; }
}

class Dog : Animal {
    pub let breed: string;
    new(name: string, breed: string) {
        super(name);
        this.breed = breed;
    }
    pub override func speak(): string { return "woof"; }
}

func main(): void {
    let rex = Dog("rex", "labrador");
    println(rex.intro());
    let animals: list[Animal] = [Animal("generic"), rex];
    for (a in animals) {
        println(a.speak());
    }
}
```

*Listing 9-4: A subclass with an override*

```text
rex says woof
...
woof
```

Calls are dispatched on the object's actual class, so `intro()`, defined in `Animal`, calls `Dog`'s `speak()`. Leaving out `override` is an error, so a subclass cannot replace a base method by accident:

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

class Animal {
    pub let name: string;
    new(name: string) { this.name = name; }
    pub func speak(): string { return "..."; }
}

class Dog : Animal {
    new(name: string) { super(name); }
    pub func speak(): string { return "woof"; }
}

func main(): void {
    println(Dog("rex").speak());
}
```

```text
main.lphc:11:5: error[E_OVERRIDE_REQUIRED]: method `speak` on class `Dog` shadows a parent method on `Animal` and must be marked `override` (E_OVERRIDE_REQUIRED)
```

`super.method()` calls the base version of a method. For behaviour shared by unrelated classes, use interfaces (chapter 10).

### Static members

A `static` field or method belongs to the class rather than to an instance:

```loup
#~ @mode(host.native)
class RequestIds {
    static var next: int = 1;

    pub static func issue(): int {
        let id = RequestIds.next;
        RequestIds.next = RequestIds.next + 1;
        return id;
    }
}

func main(): void {
    println(RequestIds.issue());
    println(RequestIds.issue());
}
```

*Listing 9-5: A static counter*

```text
1
2
```

## Automatic reference counting

Every class instance carries a reference count. In a native binary, the compiler inserts the bookkeeping: storing a reference in a new binding, a field or a collection adds one; a binding going out of scope, being overwritten, or being passed to `del` removes one. When the count reaches zero, the object's `finalize()` method runs (if it has one), the references it holds in its fields are released in turn, and the memory is freed (Language Reference §1-8.1.1, §3-2.4). There is no garbage collector and no pause: an object goes away at the moment its last reference does.

Several features exist to avoid reference-count traffic where it is not needed. A parameter without a verb (`reads`) borrows the caller's reference without counting it (chapter 15); `borrow` makes that explicit (chapter 16); and a `unique` value has exactly one owner and is moved instead of counted (chapter 17).

### `finalize()` and `del`

A class that owns an outside resource can declare `func finalize(): void` to release it. `del name;` calls `finalize()` immediately and makes the binding unusable (Language Reference §1-6.11):

```loup
#~ @mode(host.native)
class TempFile {
    pub let path: string;

    new(path: string) {
        this.path = path;
        println(i"create {$ path $}");
    }

    func finalize(): void {
        println(i"delete {$ this.path $}");
    }
}

func main(): void {
    let scratch = TempFile("/tmp/report.part");
    println(i"writing {$ scratch.path $}");
    del scratch;
    println("done");
}
```

*Listing 9-6: Releasing a resource with `del`*

```text
create /tmp/report.part
writing /tmp/report.part
delete /tmp/report.part
done
```

Using the binding after `del` is caught at compile time:

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

class TempFile {
    pub let path: string;
    new(path: string) { this.path = path; }
    func finalize(): void { println(i"delete {$ this.path $}"); }
}

func main(): void {
    let scratch = TempFile("/tmp/report.part");
    del scratch;
    println(scratch.path);
}
```

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

`del` runs `finalize()` even if other references to the object exist, so make `finalize()` safe to call more than once. For a resource whose lifetime matches a block, `with` (chapter 5) is usually clearer than `del`.

**Where `loup run` differs.** The interpreter does not run `finalize()` when a reference count reaches zero; it runs it only for `del` (roadmap finding F4). In Listing 9-6, if you remove the `del`, a native binary still deletes the file when `scratch` goes out of scope at the end of `main`, but `loup run` never prints the "delete" line. Do not test cleanup that depends on scope exit with `loup run`; use `del` or `with`, which behave the same everywhere.

## Breaking cycles with `weak`

Reference counting cannot free a cycle: if a parent holds its children and each child holds its parent, every count stays above zero after the last outside reference is gone. The specified answer is a `weak` reference, which does not count and reads as `None` once its target has been freed. A `weak` field has an `Option` type, and you assign it with `Some(...)`:

```loup
#~ @mode(host.native)
class TreeNode {
    pub let label: string;
    pub var children: list[TreeNode] = [];
    pub weak var parent: TreeNode?;

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

safe func attach(edits parent: TreeNode, edits child: TreeNode): void {
    child.parent = Some(parent);
    parent.children.push(child);
}

func main(): void {
    let docs = TreeNode("docs");
    let intro = TreeNode("intro");
    attach(edits: docs, edits: intro);
    match intro.parent {
        Some(p) => println(i"{$ intro.label $} is under {$ p.label $}"),
        None => println("no parent"),
    }
}
```

*Listing 9-7: A tree with `weak` parent links*

```text
intro is under docs
```

Assigning a plain object to a `weak` field is a type error (`cannot assign 'TreeNode' to 'Option[TreeNode]'`), which differs from the Language Reference's examples; write `Some(parent)`.

**Known gap.** In Loup 1.25.1 `weak` (and `unowned`) fields are accepted and type-checked, but a native binary stores them as ordinary strong references, so a parent/child cycle like the one above is never freed (roadmap finding F2), and the interpreter never frees anything at a count of zero. In both, a weak reference never becomes `None`:

```loup,not_desired_behavior
#~ @mode(host.native)
#~ class TreeNode {
#~     pub let label: string;
#~     pub var children: list[TreeNode] = [];
#~     pub weak var parent: TreeNode?;
#~     new(label: string) { this.label = label; }
#~ }
#~ safe func attach(edits parent: TreeNode, edits child: TreeNode): void {
#~     child.parent = Some(parent);
#~     parent.children.push(child);
#~ }
func detachedLeaf(): TreeNode {
    let root = TreeNode("root");
    let leaf = TreeNode("leaf");
    attach(edits: root, edits: leaf);
    return leaf;    # nothing outside this function refers to root any more
}

func main(): void {
    let leaf = detachedLeaf();
    match leaf.parent {
        Some(p) => println(i"leaf still sees {$ p.label $}"),
        None => println("the parent was freed"),
    }
}
```

```text
leaf still sees root
```

Under the specified semantics `root` is freed when `detachedLeaf` returns and the program prints "the parent was freed". Keep declaring back-references `weak`, which states the design and will be correct when F2 is fixed, but for a long-running native program that creates and drops many such structures, also break the links yourself (clear `children` or `parent`) when you discard a structure.

Chapter 18 covers `unowned[~name]`, the specified compile-time alternative to `weak`.

## `local` and `shared` classes

A plain `class` is a `local class`: an object meant to be used by one thread. A `shared class` is meant to be passed between threads, and every field it declares must be *Shareable* (Language Reference §1-8.1.1, runtime §2-2.2). Strings and growable collections are not:

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

shared class Stats {
    pub var requests: int = 0;
    pub var paths: list[string] = [];
}

func main(): void {
    let s = Stats();
}
```

```text
main.lphc:5:5: error[E_SHARED_FIELD_NOT_SHAREABLE]: E_SHARED_FIELD_NOT_SHAREABLE: field `Stats.paths` is not Shareable (`list[T]` is a growable collection and is never Shareable (§2-2.2) — use `SharedArray[T]` or `SharedQueue[T]`) — every field of a `shared class` must satisfy `core.Shareable` (§2-2.2)
```

The specified way to create a shared instance is `shared Stats()`, which yields a `SharedRef[Stats]`. A native build accepts it, but in 1.25.1 `loup run` stops with `internal interpreter limitation: unsupported instruction: NewSharedObject`. Chapter 20 covers sharing objects between threads, including the `synchronized` classes introduced in chapter 15.

> ⚠ **Planned.** `arc let` and `arc var`, which mark a binding as explicitly reference-counted for readers and for the `--require-explicit-arc` lint, are specified (Language Reference §3-2.4.8) but do not parse in 1.25.1: `arc let n = Node(1);` is `error[E0003]: expected ';'`. A plain `let` or `var` holding a class instance is already reference-counted.

## Summary

- Class fields are declared `let` or `var` (`E_BARE_FIELD` otherwise) and are private unless `pub`; `new(...)` is the constructor, or the compiler generates one that takes the fields by name.
- Class instances are shared by reference; structs are copied.
- Methods that write `var` fields are `modifier`, and calling them (or writing a field) on an ordinary instance needs `safe { }` or a `safe func` (`E_SAFE_CONTEXT_REQUIRED`).
- `class Dog : Animal` inherits; `super(...)` calls the base constructor; replacing a method requires `override` (`E_OVERRIDE_REQUIRED`).
- Native binaries free an object and run `finalize()` when its count reaches zero; `loup run` runs `finalize()` only on `del`. `del` finalizes immediately, and a later use is `E_USE_AFTER_DEL`.
- `weak var parent: T?` is assigned with `Some(x)`. In 1.25.1 weak references are kept strong, so cycles leak; break them by hand in long-running programs.
- `shared class` fields must be Shareable; `arc let` is ⚠ planned.
- Chapter 10 covers interfaces.
