Skip to content
📖 Auto vs RustBook contents

Object-Oriented Patterns in Auto ​

Object-oriented programming (OOP) is a way of modeling programs. Objects as a programmatic concept were introduced in the programming language Simula in the 1960s. Many competing definitions describe what OOP is, and by some of these definitions Auto is object oriented but by others it is not. In this chapter, we'll explore certain characteristics that are commonly considered object oriented and how those characteristics translate to idiomatic Auto.

Auto provides three key mechanisms for OOP-style patterns:

  • spec — specs define shared behavior (similar to Rust's trait)
  • is — type composition and behavioral contracts (replacing inheritance)
  • has — delegation of behavior through composition

We'll then show how to implement an object-oriented design pattern in Auto and discuss the trade-offs of doing so versus implementing a solution using some of Auto's strengths instead.

Characteristics of Object-Oriented Languages ​

There is no consensus in the programming community about what features a language must have to be considered object oriented. Auto is influenced by many programming paradigms, including OOP; for example, we explored the features that came from functional programming in Chapter 13. Arguably, OOP languages share certain common characteristics — namely, objects, encapsulation, and inheritance. Let's look at what each of those characteristics means and whether Auto supports it.

Objects Contain Data and Behavior ​

The book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1994), colloquially referred to as The Gang of Four book, defines OOP in this way:

Object-oriented programs are made up of objects. An object packages both data and the procedures that operate on that data. The procedures are typically called methods or operations.

Using this definition, Auto is object oriented: types have data, and ext blocks provide methods on types. Even though types with methods aren't called objects in Auto, they provide the same functionality according to the Gang of Four's definition of objects.

The type keyword defines a data structure with fields, and ext blocks attach behavior. This is the Auto equivalent of a class in traditional OOP languages.

Encapsulation That Hides Implementation Details ​

Another aspect commonly associated with OOP is the idea of encapsulation, which means that the implementation details of an object aren't accessible to code using that object. Therefore, the only way to interact with an object is through its public API.

In Listing 18-1, the list and average fields are private by default in Auto. Only the methods marked pub are accessible from outside the type. The update_average method is private — it's an implementation detail.

We discussed how to control encapsulation in Chapter 7: we can use the pub keyword to decide which modules, types, functions, and methods in our code should be public, and by default everything else is private.

Because we've encapsulated the implementation details of AveragedCollection, we can easily change aspects, such as the data structure, in the future. For instance, we could use a Set<int> instead of a List<int> for the list field. As long as the signatures of the add, remove, and average public methods stayed the same, code using AveragedCollection wouldn't need to change.

If encapsulation is a required aspect for a language to be considered object oriented, then Auto meets that requirement. The option to use pub or not for different parts of code enables encapsulation of implementation details.

Inheritance: Replaced by Composition and Specs ​

Inheritance is a mechanism whereby an object can inherit elements from another object's definition, thus gaining the parent object's data and behavior without you having to define them again.

If a language must have inheritance to be object oriented, then Auto is not such a language. There is no way to define a type that inherits the parent type's fields and method implementations.

However, Auto provides powerful alternatives:

  1. spec with default implementations — for code reuse (like Rust's traits)
  2. is composition — for type relationships (replacing inheritance)
  3. has delegation — for forwarding behavior to contained types

You would choose inheritance for two main reasons. One is for reuse of code: you can implement particular behavior for one type, and inheritance enables you to reuse that implementation for a different type. In Auto, you can achieve this with spec default implementations.

The other reason to use inheritance relates to the type system: to enable a child type to be used in the same places as the parent type. This is also called polymorphism. In Auto, you achieve this through spec bounds and generic types.

Polymorphism ​

Polymorphism means that you can substitute multiple objects for each other at runtime if they share certain characteristics.

Auto uses generics to abstract over different possible types and spec bounds to impose constraints on what those types must provide. This is sometimes called bounded parametric polymorphism, the same approach Rust takes.

The is/has/spec Triangle ​

Auto's approach to OOP centers on three keywords that work together:

KeywordPurposeOOP Equivalent
specDefine shared behaviorInterface / Trait
isCompose types, assert contractsInheritance (type relationship)
hasDelegate behavior via compositionComposition + delegation

spec: Defining Shared Behavior ​

A spec defines a set of methods that a type must implement. This is identical to Rust's trait or an interface in Java/Go:

Types implement specs using the spec ... for Type syntax:

is: Type Composition and Contracts ​

The is keyword serves two purposes:

  1. Composing types — embedding one type's fields into another
  2. Asserting spec contracts — declaring that a type satisfies a spec
Using `is` for type composition

In Rust, composition requires accessing nested fields (dog.animal.name). In Auto, is flattens the composition — dog.name works directly because Dog is Animal.

You can also use is to assert that a type implements a spec:

auto
type Dog {
    is Animal
    is Summary       // Assert Dog implements Summary
    breed String
}

has: Delegation Through Composition ​

The has keyword delegates method implementations to a contained type:

Using `has` for delegation

In Rust, you must write delegation methods manually (self.engine.start()). Auto's has keyword automatically forwards method calls to the contained type.

is vs has ​

Featureishas
PurposeCompose fieldsDelegate behavior
Field accessFlattened (direct)Through the field
Method delegationNoYes
MultipleA type can is multiple typesA type can has multiple types
AnalogInheritance (data)Composition (behavior)

Use is when you want to share data structure. Use has when you want to share behavior without exposing the inner type's fields.

Using Specs for Polymorphism ​

Spec Objects: Dynamic Dispatch ​

Like Rust's dyn Trait, Auto supports dynamic dispatch through spec objects. When you need a collection of heterogeneous types that all implement the same spec, you use spec objects:

The dyn Draw syntax creates a spec object — a reference to any type that implements the Draw spec. This is exactly analogous to Rust's dyn Trait.

Implementing Specs for Concrete Types ​

Now a Screen can hold both Button and any other type that implements Draw:

Using heterogeneous types with spec objects

Static Dispatch vs Dynamic Dispatch ​

When you use generics with spec bounds, Auto performs static dispatch — the compiler generates specialized code for each concrete type. This is the same as monomorphization in Rust:

auto
// Static dispatch: each T gets its own compiled version
fn draw_all<T Draw>(items List<T>) {
    for item in items {
        item.draw()
    }
}

When you use spec objects (dyn Draw), Auto performs dynamic dispatch — the method to call is determined at runtime via a vtable:

auto
// Dynamic dispatch: method looked up at runtime
fn draw_all(items List<Box<dyn Draw>>) {
    for item in items {
        item.draw()
    }
}
AspectStatic Dispatch (Generics)Dynamic Dispatch (dyn)
PerformanceFaster (can inline)Slight overhead (vtable lookup)
FlexibilitySingle type per callMultiple types per call
Binary sizeLarger (monomorphization)Smaller
Use whenType is uniformTypes are mixed

The State Pattern in Auto ​

A classic OOP design pattern is the State pattern, where an object's behavior changes based on its internal state. Let's implement a blog post workflow that transitions through states: draft → review → published.

Implementing the State Pattern ​

Using the post:

Using the blog post state machine

Encoding State in the Type System ​

Auto's is keyword provides an alternative: encoding states directly into the type system. Instead of runtime state transitions, we use different types for each state:

Encoding state in the type system

This approach has a significant advantage: the type system prevents invalid operations at compile time. You can't call content() on a DraftPost — it doesn't exist on that type. You can't call approve() on a DraftPost — only request_review() is available. The compiler enforces the state transitions.

Usage:

auto
fn main() {
    let draft = DraftPost(content: "")
    draft.add_text("I ate a salad for lunch today")

    let review = draft.request_review()
    let published = review.approve()

    print(published.content())
}
rust
fn main() {
    let mut draft = DraftPost { content: String::new() };
    draft.add_text("I ate a salad for lunch today");

    let review = draft.request_review();
    let published = review.approve();

    println!("{}", published.content());
}

This is the idiomatic Auto approach: use the type system to make illegal states unrepresentable, rather than relying on runtime checks.

is/has/spec vs Rust's OOP ​

OOP ConceptRustAuto
Interface / Traittraitspec
Implementationimpl Trait for Typespec Trait for Type
Dynamic dispatchdyn Traitdyn Spec
CompositionManual: self.inner.method()has: automatic delegation
Field embeddingManual: self.inner.fieldis: flattened access
Default methodstrait with default bodyspec with default body
No inheritanceNo inheritanceNo inheritance (use is/has)
State patternBox&lt;dyn State&gt;dyn State or type-state

Auto's is and has keywords remove boilerplate that Rust requires for composition and delegation. In Rust, composing two structs and delegating methods requires manual wrapper methods. In Auto, has generates those wrappers automatically.

Object-Oriented Patterns Quick Reference ​

FeatureAutoRust
Define interfacespec Name { }trait Name { }
Implement interfacespec Name for Type { }impl Name for Type { }
Dynamic dispatchdyn Specdyn Trait
Compose fieldsis TypeManual embedding
Delegate methodshas TypeManual delegation
Default methodBody in specBody in trait
Encapsulationpub / private by defaultpub / private by default
PolymorphismGenerics + spec boundsGenerics + trait bounds

Summary ​

Auto supports object-oriented patterns through a combination of features:

  1. type and ext — packages data and behavior, like objects
  2. Encapsulation — pub controls visibility, fields are private by default
  3. spec — defines shared behavior, like interfaces or traits
  4. is — composes types with flattened field access
  5. has — delegates behavior through automatic method forwarding
  6. dyn Spec — dynamic dispatch for heterogeneous collections
  7. Type-state pattern — encode states in the type system for compile-time safety

Auto deliberately avoids inheritance in favor of composition (is/has) and behavioral contracts (spec). This produces more flexible designs where types only share the behavior they explicitly opt into, and the type system catches invalid state transitions at compile time rather than runtime.

In the next chapter, we'll explore Auto's pattern matching with the is keyword and how it compares to Rust's match expressions.

Released under the MIT License.