Skip to content
📖 Auto vs RustBook contents

Generic Types, Specs, and Lifetimes ​

Every programming language has tools for effectively handling the duplication of concepts. In Auto, one such tool is generics: abstract stand-ins for concrete types or other properties. We can express the behavior of generics or how they relate to other generics without knowing what will be in their place when compiling and running the code.

Functions can take parameters of some generic type, instead of a concrete type like int or String, in the same way they take parameters with unknown values to run the same code on multiple concrete values. In fact, we already used generics in earlier chapters: ?T (Option), !T (Result), List<T>, and Map<K, V> are all generic types.

In this chapter, we'll explore how to define your own types, functions, and methods with generics. Then we'll learn about specs (Auto's equivalent of Rust traits) for defining shared behavior. Finally, we'll briefly discuss lifetimes — and how Auto's compiler handles them automatically, eliminating the need for explicit lifetime annotations.

Generic Data Types ​

In Function Definitions ​

When defining a function that uses generics, we place the generics in the signature where we would usually specify the data types. This makes our code more flexible while preventing duplication.

A generic function to find the largest value in a list

We read fn largest<T>(list []T) T as "the function largest is generic over some type T." The function takes a list of T values and returns a T. By convention, type parameter names are short, often just one letter, using UpperCamelCase — T (short for "type") is the default choice.

The syntax is the same as Rust's: type parameters go inside angle brackets <> between the function name and the parameter list.

In Type Definitions ​

We can define types with generic type parameters in their fields:

A `Point<T>` type that holds `x` and `y` values of type `T`

Because we've used only one generic type T, both x and y must be the same type. If you need different types for each field, use multiple type parameters:

A `Point<T, U>` with two type parameters

In Method Definitions (ext) ​

We can define methods on generic types using ext:

Implementing a method on `Point<T>` with `ext`

Note that we declare T after ext and before Point&lt;T&gt;, just like Rust's impl&lt;T&gt; Point&lt;T&gt;. This tells the compiler that the method is defined for any concrete type that T might be.

Performance of Code Using Generics ​

Using generics has zero runtime cost. Auto (like Rust) uses monomorphization: the compiler generates specific code for each concrete type used with a generic. So Point&lt;int&gt; and Point&lt;f64&gt; become separate, optimized types at compile time. There is no runtime overhead for using generics.

Defining Shared Behavior with Specs ​

A spec (short for "specification") defines the functionality a particular type has and can share with other types. Specs are Auto's equivalent of Rust's traits (also known as interfaces in other languages).

Defining a Spec ​

A type's behavior consists of the methods we can call on that type. Spec definitions group method signatures together to define a set of behaviors:

auto
spec Summary {
    fn summarize(self) String
}

This declares a spec named Summary with one required method: summarize, which takes self and returns a String. Any type that implements this spec must provide its own summarize method.

Implementing a Spec on a Type ​

In Auto, you implement a spec using as:

Defining a spec and implementing it on two types

Key differences from Rust:

FeatureAutoRust
Define spec/traitspec Summary { ... }trait Summary { ... }
Implement on typetype Foo as Summary { ... }impl Summary for Foo { ... }
Method bodyIn separate ext blockInside impl block

The as keyword on the type declaration means "this type conforms to the spec." The actual method implementations go in the ext block.

Using Specs as Parameters ​

You can use a spec as a parameter type to accept any type that implements it:

Using a spec as a function parameter

In Auto, fn notify(item Summary) accepts any value whose type implements the Summary spec. This is equivalent to Rust's impl Trait syntax: fn notify(item: impl Summary).

Note how Auto's syntax is simpler — you don't need impl or Box&lt;dyn Trait&gt; to pass spec-conforming values. The compiler handles the dispatch automatically.

Generic Specs ​

Specs can have their own type parameters, just like types:

A generic spec with a type parameter

The generic spec Container&lt;T&gt; defines a contract that any implementing type must provide get and set methods for its inner type T. The Box&lt;T&gt; type implements Container&lt;T&gt;, and the ext block provides the method bodies.

Spec vs Trait Quick Reference ​

AutoRustMeaning
spec Foo { ... }trait Foo { ... }Define a specification
type Bar as Foo { ... }impl Foo for Bar { ... }Implement spec on type
fn foo(x SomeSpec)fn foo(x: impl SomeSpec)Spec as parameter
spec Container&lt;T&gt;trait Container&lt;T&gt;Generic spec
ext Type&lt;T&gt; { ... }impl&lt;T&gt; Type&lt;T&gt; { ... }Generic method extension

Lifetimes: Automatic in Auto ​

In Rust, lifetimes are generic annotations that tell the compiler how long references should be valid. They're necessary because Rust needs explicit information about the relationships between references.

Auto takes a different approach: lifetimes are fully automatic. The compiler tracks reference validity internally and ensures memory safety without requiring you to write any lifetime annotations.

No Lifetime Annotations Needed ​

In Rust, a function that returns the longer of two string slices requires explicit lifetime annotations:

rust
// Rust requires explicit lifetimes
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

In Auto, the same function needs no annotations at all:

Auto infers lifetimes automatically — no annotations needed

Auto's compiler uses ownership and borrowing rules (.view, .mut, .move) to track reference validity at compile time, eliminating the need for explicit lifetime parameters in most cases.

What This Means for You ​

  • No 'a annotations in function signatures
  • No lifetime parameters on struct definitions
  • No &lt;'a&gt; declarations in ext blocks
  • The compiler still prevents dangling references and use-after-free bugs

If you're coming from Rust, you can think of it this way: Auto's compiler applies Rust's lifetime elision rules aggressively, plus additional inference, so you almost never need to write lifetimes explicitly.

Summary ​

Auto provides the same generic programming capabilities as Rust, with a cleaner syntax:

ConceptAutoRust
Generic functionfn foo&lt;T&gt;(x T)fn foo&lt;T&gt;(x: T)
Generic typetype Point&lt;T&gt; { x T }struct Point&lt;T&gt; { x: T }
Multiple type paramstype Pair&lt;T, U&gt;struct Pair&lt;T, U&gt;
Generic methodsext Point&lt;T&gt; { ... }impl&lt;T&gt; Point&lt;T&gt; { ... }
Spec (trait)spec Foo { ... }trait Foo { ... }
Implement spectype Bar as Foo { ... }impl Foo for Bar { ... }
Spec as parameterfn foo(x SomeSpec)fn foo(x: impl SomeSpec)
Generic specspec Storage&lt;T&gt;trait Storage&lt;T&gt;
LifetimesAutomaticExplicit 'a annotations

The most notable simplification is Auto's automatic lifetime inference. Where Rust requires &lt;'a&gt; annotations on function signatures and struct definitions, Auto's compiler handles this internally, letting you focus on your logic rather than lifetime management.

In the next chapter, we'll discuss automated testing.

Released under the MIT License.