Skip to content
📖 The Auto Programming LanguageBook contents

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
  • type as Data Container — fields + ext methods, the foundation
  • is — Inheritance — single inheritance with method override
  • has — Composition with Auto-Delegation — the magic keyword
  • spec — Behavioral Contracts — like interface or trait
  • as — Implementing Specs — polymorphism through specs
  • ext — Post-Hoc Extension — adding methods to any type
  • Orphan Rule Override — ext with as for 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:

KeywordJobWhat It Replaces
typeHold dataclass (data part)
extAttach behaviorclass (methods) / impl
isSingle inheritanceextends
hasComposition + delegationManual forwarding
spec / asBehavioral contractsinterface / trait

These five keywords form what we call the golden triangle of Auto's OOP:

  • type and ext give you data + behavior (objects)
  • is and has give you two kinds of type composition (relationships)
  • spec and as give 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.

A `type` with fields and `ext` methods

This prints:

5

The 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 — struct for type, impl for ext. The syntax is nearly identical.
  • Python uses @dataclass for the data and methods on the class. The new static method is idiomatic but not required in Python.
  • C uses a typedef struct for the data and separate functions prefixed with the type name. The self parameter is explicit as a pointer.
  • TypeScript uses a class with a constructor. The static new method 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.

Single inheritance with `is` and method override

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 speak method on Dog replaces Animal's version.
  • Python uses class inheritance (Dog(Animal)). Method override works naturally.
  • C uses embedded structs — Dog contains an Animal base field. Method calls on Dog access self->base.name.
  • TypeScript uses extends for class inheritance, with super() 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.

Composition with `has` and automatic method delegation

This prints:

Tesla
Engine (400hp) started

In 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 start method on Car that calls self.engine.start().
  • C requires a Car_start function that calls Engine_start(&self->engine).
  • TypeScript requires a start method on Car that calls this.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.

Defining a `spec` as a behavioral contract

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 extends Drawable must implement draw.
  • 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's spec.

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.

Implementing a spec with `as` for polymorphism

This prints:

Drawing circle with radius 5
Drawing square with side 3

The 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 of render for each type. Rust also supports dynamic dispatch with dyn Drawable.
  • Python uses duck typing. The render function accepts any object with a draw method. 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. The render function accepts any Drawable, 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.

Adding methods to a built-in type with `ext`

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 &str directly, so the transpiler emits a free function string_shout. This is the idiomatic Rust approach.
  • Python monkey-patches the built-in str class 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.prototype to add the method, with a declare global block 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.

Implementing a spec for a type with `ext ... as`

This prints:

Contents of hello.txt

ext 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 Reader on 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.

All five OOP keywords in one program

This prints:

Engine ignited: 500hp
BigRig started
BigRig stopped
Payload: 10 tons

Let's trace through every keyword in this example:

  1. spec Vehicle — defines the contract: anything that is a Vehicle must have start() and stop() methods.

  2. type Engine — defines the data: a single power field.

  3. ext Engine — attaches behavior: the ignite() method.

  4. type Car has Engine — Car has an Engine. This means Car gets all of Engine's public methods through automatic delegation. When Car calls self.ignite(), it's forwarded to Engine.ignite().

  5. ext Car as Vehicle — Car implements the Vehicle spec. The as keyword makes the spec-type connection explicit. Now Car can be used anywhere a Vehicle is expected.

  6. type Truck is Car — Truck is a Car. Truck inherits Car's fields (model) and Engine's fields (power, via has). Truck also inherits all methods — start, stop, and ignite.

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:

ConceptClass-Based (Java/C#/Python)Auto
Define dataFields in classtype
Attach methodsMethods in classext
Inheritanceextends / :is
CompositionManual delegationhas (auto-delegation)
Interfaceinterface / implementsspec / as
Add methods to existing typeNot possible (or monkey-patching)ext (anywhere)
Multiple inheritanceVaries (often restricted)has multiple, is single
PolymorphismSubtypingSpec bounds
Encapsulationpublic / privatepub / 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.

Released under the MIT License.