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:
sysblocks: 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:
- Dereference a raw pointer (
*T) - Call a
sysfunction or method - Access or modify a mutable static variable
- Implement a
sysspec - Access fields of unions
- 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.
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:
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:
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:
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:
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:
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
| Feature | Auto (sys) | Rust (unsafe) |
|---|---|---|
| Keyword | sys | unsafe |
| Raw pointer types | *T, *mut T | *const T, *mut T |
| Sys/unsafe function | sys fn | unsafe fn |
| Sys/unsafe trait | sys spec | unsafe trait |
| FFI block | sys extern "C" | unsafe extern "C" |
| Static variables | static mut in sys | static mut in unsafe |
| Safe abstraction | Encouraged | Encouraged |
| Transpiles to | C code | Native 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:
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:
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:
fn bar() ! {
panic("something went wrong")
}fn bar() -> ! {
panic!("something went wrong");
}Note that Auto uses ! both as the error type in !T (the equivalent of Result<T, E>) 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<str>, etc.
Spec objects (dyn Spec) are also DSTs, which is why they must be behind a pointer like Box<dyn Draw> 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
synandquote - 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:
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:
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
Many patterns that require macros in Rust don't need comptime in Auto:
| Rust Pattern | Requires Macro? | Auto Equivalent |
|---|---|---|
| Vector literals | Yes (vec![]) | No ([1, 2, 3]) |
| Print with formatting | Yes (println!) | No (print(f"{}")) |
| Derive traits | Yes (#[derive]) | Yes (#derive[]) |
| Custom code generation | Yes (proc macro) | Yes (comptime) |
| Conditional compilation | Yes (cfg!) | Yes (#if[]) |
Attribute-Like Comptime
Auto supports custom attributes that transform code at compile time:
Conditional Compilation
Auto provides compile-time conditional compilation using #if[]:
Inline Comptime Expressions
Auto allows inline comptime expressions — code that runs at compile time and produces a value:
Comptime vs Rust Macros Quick Reference
| Feature | Rust | Auto |
|---|---|---|
| Declarative macros | macro_rules! | Not needed (use comptime) |
| Procedural macros | Separate crate + syn/quote | comptime 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 introspection | Not available | Full comptime reflection |
| Separate crate required | Yes (proc macros) | No |
| Syntax | Special macro syntax | Regular 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<T>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:
sysblocks — Auto's equivalent ofunsafe, for stepping outside safety guarantees when necessary. Usessyskeyword instead ofunsafe,*T/*mut Tinstead of*const T/*mut T, andsys specinstead ofunsafe trait.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.Comptime metaprogramming — Auto's replacement for Rust's macro system. Uses
#[]attributes andcomptime fninstead ofmacro_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.