OOP Reshaped
If you've programmed in Java, Python, C#, or TypeScript, you're used to a certain idea of what "object-oriented programming" means: classes, inheritance, interfaces, and methods. Auto takes a different path. It keeps the useful ideas from OOP — encapsulation, polymorphism, code reuse — but replaces the implementation with five keywords that are simpler, safer, and more explicit.
This is the most important chapter in the book. Auto's OOP model is its single biggest departure from mainstream languages, and understanding it is the key to writing idiomatic Auto code.
In this chapter, we'll cover:
- OOP in Auto — why Auto's model differs from class-based OOP
typeas Data Container — fields +extmethods, the foundationis— Inheritance — single inheritance with method overridehas— Composition with Auto-Delegation — the magic keywordspec— Behavioral Contracts — like interface or traitas— Implementing Specs — polymorphism through specsext— Post-Hoc Extension — adding methods to any type- Orphan Rule Override —
extwithasfor external types - Putting It All Together — a combined example using every keyword
OOP in Auto
Traditional OOP uses a single concept — the class — to do three jobs: hold data, define behavior, and express relationships between types. A Dog extends Animal means Dog gets Animal's fields, Animal's methods, and can be used wherever Animal is expected. This is convenient, but it creates tight coupling and the fragile base class problem.
Auto splits these three jobs across five keywords:
| Keyword | Job | What It Replaces |
|---|---|---|
type | Hold data | class (data part) |
ext | Attach behavior | class (methods) / impl |
is | Single inheritance | extends |
has | Composition + delegation | Manual forwarding |
spec / as | Behavioral contracts | interface / trait |
These five keywords form what we call the golden triangle of Auto's OOP:
typeandextgive you data + behavior (objects)isandhasgive you two kinds of type composition (relationships)specandasgive you polymorphism (contracts)
The result is a system where data is separate from behavior, inheritance is explicit and single, composition is automatic, and polymorphism is opt-in rather than implicit. Let's see how each piece works.
type as Data Container
In traditional OOP, a class bundles fields and methods together. In Auto, a type defines only the data. You attach behavior separately using ext.
This prints:
5The type block defines two fields: x and y. The ext Point block attaches three methods: a constructor new, and distance which computes the Euclidean distance between two points.
This separation of data and behavior has a practical benefit: you can add methods to a type from anywhere in your codebase. The ext block doesn't have to be in the same file as the type definition. This is different from a class, where all methods must be defined inside the class body.
Notice how each target language maps this pattern:
- Rust maps directly —
structfortype,implforext. The syntax is nearly identical. - Python uses
@dataclassfor the data and methods on the class. Thenewstatic method is idiomatic but not required in Python. - C uses a
typedef structfor the data and separate functions prefixed with the type name. Theselfparameter is explicit as a pointer. - TypeScript uses a
classwith a constructor. Thestatic newmethod is idiomatic for factory-style construction.
is — Inheritance
The is keyword gives Auto single inheritance. A child type inherits all fields from its parent and can override methods.
This prints:
Rex barks!Dog is Animal means Dog inherits the name field from Animal. The Dog type has both name and breed. When constructing a Dog, you provide values for all fields — both inherited and own.
The ext Dog block defines a new speak method that overrides the inherited one. When you call d.speak(), Auto calls the Dog version, not the Animal version. This is the standard override behavior you'd expect from inheritance.
How this maps across languages:
- Rust has no built-in inheritance. The fields from Animal are copied into Dog's struct. The
speakmethod on Dog replaces Animal's version. - Python uses class inheritance (
Dog(Animal)). Method override works naturally. - C uses embedded structs — Dog contains an
Animal basefield. Method calls on Dog accessself->base.name. - TypeScript uses
extendsfor class inheritance, withsuper()to call the parent constructor.
Auto's is is intentionally limited to single inheritance. A type can only is one other type. If you need to combine behavior from multiple sources, use has (composition) and spec (interfaces) instead.
has — Composition with Auto-Delegation
The has keyword is Auto's secret weapon. It embeds one type inside another and automatically forwards method calls to the embedded type. You never have to write boilerplate delegation methods.
This prints:
Tesla
Engine (400hp) startedIn Auto, Car has Engine means Car contains an Engine and automatically forwards any Engine method that Car doesn't define itself. When you call c.start(), Auto looks for a start method on Car, doesn't find one, and delegates to Engine.start().
Compare what you'd have to write in each target language:
- Rust requires manual delegation —
impl Car { fn start(&self) { self.engine.start(); } }. Auto generates this for you. - Python also requires manual delegation — a
startmethod on Car that callsself.engine.start(). - C requires a
Car_startfunction that callsEngine_start(&self->engine). - TypeScript requires a
startmethod on Car that callsthis.engine.start().
In every target language except Auto, composition requires boilerplate. Auto's has eliminates that boilerplate entirely. You declare the relationship once, and the compiler generates the forwarding code.
This is why has is the "magic keyword." It gives you the power of composition without the pain. You can has multiple types in a single type declaration, and each one contributes its methods to the container.
spec — Behavioral Contracts
A spec defines a set of methods that a type must implement. It's Auto's equivalent of an interface in Java/TypeScript or a trait in Rust.
A spec only declares method signatures — it has no implementation. It says "any type that wants to be Drawable must have a draw method." The spec itself doesn't know or care which types will implement it.
Notice how each language expresses this concept:
- Rust uses
trait— the closest equivalent.trait Drawable { fn draw(&self); }is a direct translation. - Python uses
ABC(Abstract Base Class) with@abstractmethod. Any class that extendsDrawablemust implementdraw. - C uses a vtable struct — a table of function pointers. This is the low-level mechanism that all the other languages use internally.
- TypeScript uses
interface— the most direct equivalent to Auto'sspec.
The spec is just the contract. To make a type fulfill that contract, you use the as keyword — which we'll cover next. But first, let's see how a type provides the implementation.
In Listing 8-4, the Circle type provides a draw method. In a real program, the compiler would check that Circle's method signature matches the spec's. If Circle had fn draw(&self, scale float) instead, the compiler would reject it — the signatures don't match.
as — Implementing Specs
The as keyword connects a type to a spec. It tells the compiler: "this type implements this spec." Once connected, the type can be used anywhere the spec is expected.
This prints:
Drawing circle with radius 5
Drawing square with side 3The render function accepts any Drawable — it doesn't care whether it's a Circle, a Square, or any future type that implements the spec. This is polymorphism: writing code that works with behavior rather than specific types.
In Auto, the compiler infers that Circle and Square implement Drawable because they both have a draw method with the matching signature. You can also be explicit with ext Circle as Drawable { ... } — we'll see that pattern in Listing 8-7.
How polymorphism maps across languages:
- Rust uses trait bounds:
fn render<T: Drawable>(d: &T). This is static dispatch — the compiler generates a specialized version ofrenderfor each type. Rust also supports dynamic dispatch withdyn Drawable. - Python uses duck typing. The
renderfunction accepts any object with adrawmethod. No explicit interface declaration is needed. - C uses vtables — a struct of function pointers that each type populates. This is manual dynamic dispatch.
- TypeScript uses
interface. Therenderfunction accepts anyDrawable, and TypeScript checks at compile time that the type implements the interface.
The key difference between is and spec/as: is is for data inheritance (getting fields from a parent type). spec/as is for behavioral polymorphism (working with types that share a behavior, regardless of their data structure).
ext — Post-Hoc Extension
The ext keyword lets you add methods to any type — including types you didn't define. You don't need to modify the original type definition. You don't need access to its source code. You just write an ext block, and the new methods appear on the type.
This prints:
HELLO!ext String adds a shout method to the built-in String type. After this declaration, every string value in your program has a .shout() method. You didn't modify String. You didn't subclass it. You just... extended it.
This is fundamentally different from how classes work. In a class-based language, you can't add methods to an existing class without modifying its source code (or using ugly monkey-patching tricks). In Auto, extension is a first-class feature.
How this maps across languages:
- Rust doesn't support extending
&strdirectly, so the transpiler emits a free functionstring_shout. This is the idiomatic Rust approach. - Python monkey-patches the built-in
strclass by assigning the method directly. This works at runtime but is considered a hack. - C uses a free function that takes a string and writes to an output buffer. This is the standard C approach.
- TypeScript uses
String.prototypeto add the method, with adeclare globalblock to provide type information.
Auto's ext is the cleanest approach: it looks like a method on the type, the compiler checks it at compile time, and it works without modifying the original type.
Orphan Rule Override
There's a subtlety with ext. If you define a type in your module, you can ext it all you want. But what if you want to implement a spec defined in another module for a type defined in yet another module? In Rust, this is blocked by the orphan rule — you can't implement a foreign trait for a foreign type.
Auto handles this differently. When you write ext TypeName as SpecName, you're explicitly telling the compiler that this extension block implements the given spec. This works even for types defined elsewhere.
This prints:
Contents of hello.txtext File as Reader does two things: it extends File with a read method, and it declares that File implements the Reader spec. The as part is the orphan rule override — it explicitly connects a type to a spec, even if they were defined in different modules.
Without as, ext File { fn read(...) { ... } } would just add a method to File. It wouldn't make File usable as a Reader in function arguments. The as keyword is what creates the spec-type connection.
This pattern maps cleanly to all target languages:
- Rust uses
impl Reader for File { ... }— the standard trait implementation syntax. - Python has File inherit from Reader (ABC) and implement
read. - C uses a standalone function. The spec relationship is implicit.
- TypeScript uses
implements Readeron the class declaration.
Putting It All Together
Now let's see all five keywords working together in a single example. This is the pattern you'll use most often in real Auto programs.
This prints:
Engine ignited: 500hp
BigRig started
BigRig stopped
Payload: 10 tonsLet's trace through every keyword in this example:
spec Vehicle— defines the contract: anything that is a Vehicle must havestart()andstop()methods.type Engine— defines the data: a singlepowerfield.ext Engine— attaches behavior: theignite()method.type Car has Engine— Car has an Engine. This means Car gets all of Engine's public methods through automatic delegation. When Car callsself.ignite(), it's forwarded toEngine.ignite().ext Car as Vehicle— Car implements the Vehicle spec. Theaskeyword makes the spec-type connection explicit. Now Car can be used anywhere a Vehicle is expected.type Truck is Car— Truck is a Car. Truck inherits Car's fields (model) and Engine's fields (power, viahas). Truck also inherits all methods —start,stop, andignite.
When we construct Truck { model: "BigRig", Engine { power: 500 }, payload: 10.0 }, we provide values for every field: the inherited model, the composed Engine, and Truck's own payload.
When we call t.start(), Auto looks up the method chain: Truck doesn't define start, so it goes to Car (via is), finds start there, and calls it. Inside Car.start(), the call to self.ignite() is delegated to Engine (via has).
This is the full power of Auto's OOP model in one example: spec for contracts, has for delegation, is for inheritance, ext for behavior, and as for connecting them.
Summary
Auto's OOP model uses five keywords where traditional languages use one (class). The trade-off is more explicit code in exchange for clearer relationships and fewer surprises.
Here's how Auto compares to class-based OOP:
| Concept | Class-Based (Java/C#/Python) | Auto |
|---|---|---|
| Define data | Fields in class | type |
| Attach methods | Methods in class | ext |
| Inheritance | extends / : | is |
| Composition | Manual delegation | has (auto-delegation) |
| Interface | interface / implements | spec / as |
| Add methods to existing type | Not possible (or monkey-patching) | ext (anywhere) |
| Multiple inheritance | Varies (often restricted) | has multiple, is single |
| Polymorphism | Subtyping | Spec bounds |
| Encapsulation | public / private | pub / default private |
The golden triangle:
type+ext= data + behavior (replaces class)is+has= inheritance + composition (replaces extends and manual delegation)spec+as= behavioral contracts (replaces interface/trait)
You've now seen every piece of Auto's OOP model. In the next chapter, we'll explore how Auto handles modules and project organization — how to structure large programs using the keywords you've learned.