Skip to content
📖 Auto vs RustBook contents

Advanced Features ​

By now, you've learned the most commonly used parts of the Auto programming language. Before we do one more project in Chapter 21, we'll look at a few aspects of the language you might run into every once in a while but may not use every day. You can use this chapter as a reference for when you encounter any unknowns. The features covered here are useful in very specific situations. Although you might not reach for them often, we want to make sure you have a grasp of all the features Auto has to offer.

In this chapter, we'll cover:

  • sys blocks: Auto's equivalent of unsafe Rust, for when you need to step outside the language's safety guarantees
  • Advanced types: Type aliases, the never type, and dynamically sized types
  • Comptime metaprogramming: Auto's compile-time code generation with #[], replacing Rust's macro system

sys Blocks ​

All the code we've discussed so far has had Auto's memory safety guarantees enforced at compile time through AutoFree. However, Auto has a second language hidden inside it that doesn't enforce these memory safety guarantees: it's called sys mode and works just like regular Auto but gives you extra capabilities. It uses the sys keyword instead of Rust's unsafe.

Sys mode exists because, by nature, static analysis is conservative. When the compiler tries to determine whether or not code upholds the guarantees, it's better for it to reject some valid programs than to accept some invalid programs. In these cases, you can use sys code to tell the compiler, "Trust me, I know what I'm doing."

Another reason Auto has a sys mode is that the underlying computer hardware is inherently unsafe. If Auto didn't let you do unsafe operations, you couldn't do certain tasks. Auto needs to allow you to do low-level systems programming, such as directly interacting with the operating system or even writing your own operating system.

Sys Superpowers ​

To switch to sys mode, use the sys keyword and then start a new block that holds the system-level code. You can take several actions in sys mode that you can't in safe Auto, which we call sys superpowers. Those superpowers include the ability to:

  1. Dereference a raw pointer (*T)
  2. Call a sys function or method
  3. Access or modify a mutable static variable
  4. Implement a sys spec
  5. Access fields of unions
  6. Call C functions through FFI

It's important to understand that sys doesn't turn off AutoFree or disable any of Auto's other safety checks. The sys keyword only gives you access to these features that are then not checked by the compiler for memory safety.

In addition, sys does not mean the code inside the block is necessarily dangerous. The intent is that as the programmer, you'll ensure that the code inside a sys block will access memory in a valid way.

To isolate sys code as much as possible, it's best to enclose such code within a safe abstraction and provide a safe API.

Dereferencing Raw Pointers ​

Sys mode has two raw pointer types similar to references: *T (immutable raw pointer) and *mut T (mutable raw pointer). These correspond to Rust's *const T and *mut T.

Creating and dereferencing raw pointers

Notice that we can create raw pointers in safe code; we just can't dereference raw pointers outside a sys block. Creating a pointer does no harm; it's only when we try to access the value that it points at that we might end up dealing with an invalid value.

Unlike regular references, raw pointers:

  • Are allowed to ignore borrowing rules by having both immutable and mutable pointers to the same location
  • Aren't guaranteed to point to valid memory
  • Are allowed to be null
  • Don't implement any automatic cleanup

Calling Sys Functions ​

The second type of operation you can perform in a sys block is calling sys functions. Sys functions have an extra sys before the rest of the definition:

Calling a sys function

We must call the dangerous function within a sys block. If we try to call it without the sys block, we'll get an error.

Creating a Safe Abstraction over Sys Code ​

Just because a function contains sys code doesn't mean we need to mark the entire function as sys. In fact, wrapping sys code in a safe function is a common abstraction. As an example, let's implement split_at_mut:

A safe abstraction using sys internally

Note that we don't need to mark split_at_mut as sys, and we can call this function from safe Auto. We've created a safe abstraction to the sys code with an implementation that uses sys code in a safe way.

Foreign Function Interface (FFI) ​

Sometimes your Auto code might need to interact with code written in another language. Since Auto transpiles to C, it has natural FFI support:

Declaring and calling an extern C function

Within the sys extern "C" block, we list the names and signatures of external functions from C. The "C" part defines the application binary interface (ABI) the external function uses.

Because Auto transpiles to C, FFI with C libraries is straightforward. No additional bridge code or wrapper generation is needed for calling C functions.

Accessing or Modifying Mutable Static Variables ​

Global variables in Auto are called static variables. Accessing and modifying mutable static variables requires sys:

Reading from or writing to a mutable static variable

With mutable data that is globally accessible, it's difficult to ensure that there are no data races, which is why Auto considers mutable static variables to require sys. Where possible, it's preferable to use the concurrency techniques we discussed in Chapter 16 (Actors) so that the compiler checks that data access is done safely.

Implementing a Sys Spec ​

We can use sys to implement a sys spec. A spec is sys when at least one of its methods has some invariant that the compiler can't verify:

Defining and implementing a sys spec

By using sys spec ... for Type, we're promising that we'll uphold the invariants that the compiler can't verify.

sys vs unsafe Quick Reference ​

FeatureAuto (sys)Rust (unsafe)
Keywordsysunsafe
Raw pointer types*T, *mut T*const T, *mut T
Sys/unsafe functionsys fnunsafe fn
Sys/unsafe traitsys specunsafe trait
FFI blocksys extern "C"unsafe extern "C"
Static variablesstatic mut in sysstatic mut in unsafe
Safe abstractionEncouragedEncouraged
Transpiles toC codeNative code

Advanced Types ​

Type Aliases ​

Auto provides the ability to declare a type alias to give an existing type another name, just like Rust. For example, we can create the alias Kilometers to int:

Creating a type alias

The alias Kilometers is a synonym for int; unlike a newtype pattern, Kilometers is not a separate, new type. Values of type Kilometers will be treated the same as values of type int.

The main use case for type aliases is reducing repetition. For example, a lengthy type like Box<dyn Fn() + Send> can be aliased:

auto
type Thunk = Box<dyn Fn() + Send>

fn takes_long_type(f Thunk) {
    // ...
}

fn returns_long_type() Thunk {
    // ...
}

The Never Type ​

Auto has a special type named ! that's known as the never type because it stands in the place of the return type when a function will never return. This is the same concept as Rust's never type:

auto
fn bar() ! {
    panic("something went wrong")
}
rust
fn bar() -> ! {
    panic!("something went wrong");
}

Note that Auto uses ! both as the error type in !T (the equivalent of Result&lt;T, E&gt;) and as the never type. The compiler distinguishes between them based on context: !T is an error-propagating result type, while standalone ! as a return type means the function never returns.

The never type is useful with panic(), return, break, and continue — all of these produce values of type ! that can be coerced into any other type.

Dynamically Sized Types ​

Auto has dynamically sized types (DSTs), similar to Rust. The most common DST is str (not String). We can't create a variable of type str directly because the compiler doesn't know how much space to allocate. Instead, we always use DSTs behind a pointer: &str, Box&lt;str&gt;, etc.

Spec objects (dyn Spec) are also DSTs, which is why they must be behind a pointer like Box&lt;dyn Draw&gt; or &dyn Draw.

Comptime Metaprogramming ​

Rust uses macros (macro_rules! and procedural macros) for metaprogramming — writing code that writes code. Auto replaces this entire system with comptime: code that runs at compile time. This is inspired by Zig's comptime and is more powerful and easier to understand than Rust's macro system.

Why Comptime Instead of Macros? ​

Macros in Rust have several downsides:

  • macro_rules! uses a complex pattern-matching syntax that's different from normal Rust code
  • Procedural macros require separate crates and dependencies like syn and quote
  • Macro errors are often hard to read
  • Macros can't easily introspect types

Auto's comptime system solves these problems:

  • Comptime code uses regular Auto syntax — no special macro language
  • No separate crates needed
  • Error messages are clear because they come from normal Auto code
  • Full type introspection at compile time

The #[] Attribute System ​

Auto uses #[] attributes for compile-time code generation. These serve the same role as Rust's #[derive], #[proc_macro], and other attributes:

Using built-in derive attributes

The #derive[...] attribute automatically generates spec implementations for your types. This is the equivalent of Rust's #[derive(...)].

Writing Custom Comptime Functions ​

Auto lets you write comptime functions — regular Auto functions that execute at compile time and generate code. This replaces both declarative and procedural macros:

A comptime function generating code

In Rust, procedural macros require a separate crate, the syn and quote crates, and complex token manipulation. In Auto, a comptime function is just a regular function marked with comptime that returns a string of code or directly manipulates the AST.

Comptime vs Rust Macros ​

Comptime list creation vs vec! macro

Many patterns that require macros in Rust don't need comptime in Auto:

Rust PatternRequires Macro?Auto Equivalent
Vector literalsYes (vec![])No ([1, 2, 3])
Print with formattingYes (println!)No (print(f"{}"))
Derive traitsYes (#[derive])Yes (#derive[])
Custom code generationYes (proc macro)Yes (comptime)
Conditional compilationYes (cfg!)Yes (#if[])

Attribute-Like Comptime ​

Auto supports custom attributes that transform code at compile time:

Custom attribute-like comptime

Conditional Compilation ​

Auto provides compile-time conditional compilation using #if[]:

Conditional compilation

Inline Comptime Expressions ​

Auto allows inline comptime expressions — code that runs at compile time and produces a value:

Inline comptime expressions

Comptime vs Rust Macros Quick Reference ​

FeatureRustAuto
Declarative macrosmacro_rules!Not needed (use comptime)
Procedural macrosSeparate crate + syn/quotecomptime fn
Derive macros#[derive(Trait)]#derive[Trait]
Attribute macros#[proc_macro_attribute]#attr[...] + comptime fn
Function-like macros#[proc_macro]comptime fn
Conditional compilation#[cfg(...)]#if[...]
Type introspectionNot availableFull comptime reflection
Separate crate requiredYes (proc macros)No
SyntaxSpecial macro syntaxRegular Auto syntax

sys and Comptime Guidelines ​

When to Use sys ​

Use sys blocks when you need to:

  • Interface with C libraries through FFI
  • Implement low-level data structures (like List&lt;T&gt; internals)
  • Do pointer arithmetic
  • Access hardware directly

Always wrap sys code in safe abstractions. Keep sys blocks as small as possible. Document safety invariants with /// SAFETY: comments.

When to Use Comptime ​

Use comptime when you need to:

  • Generate repetitive code (like spec implementations)
  • Implement domain-specific languages within Auto
  • Perform compile-time validation
  • Reduce boilerplate

Prefer regular functions and generics over comptime when possible. Comptime adds complexity; use it only when the alternatives are worse.

Summary ​

This chapter covered Auto's advanced features:

  1. sys blocks — Auto's equivalent of unsafe, for stepping outside safety guarantees when necessary. Uses sys keyword instead of unsafe, *T/*mut T instead of *const T/*mut T, and sys spec instead of unsafe trait.

  2. Advanced types — Type aliases for reducing repetition, the never type ! for functions that don't return, and dynamically sized types that must be used behind pointers.

  3. Comptime metaprogramming — Auto's replacement for Rust's macro system. Uses #[] attributes and comptime fn instead of macro_rules! and procedural macros. No separate crates needed, regular Auto syntax throughout.

The key difference from Rust is that Auto replaces the complex macro ecosystem with a unified comptime system. Where Rust has three kinds of macros each with their own syntax and crate requirements, Auto has a single comptime mechanism that uses familiar function syntax. And where Rust uses unsafe, Auto uses sys — a simpler keyword for the same concept of opting out of safety guarantees.

In the next chapter, we'll put everything we've discussed throughout the book into practice and build a final project: a web server using Auto's Actor model.

Released under the MIT License.