Functions and Control Flow
You now know how to store data in variables and transform it with operators. But a program that's just a list of statements is like a recipe with no sections — it works, but it's hard to reuse and hard to read. Functions let you name a block of code so you can call it from anywhere. Control flow lets you choose which blocks run and repeat them when needed. Together, they turn a flat script into a structured program.
In this chapter, we'll cover:
- Defining and calling functions with
fn - Returning values from functions (implicitly and explicitly)
- Conditional branching with
if/else if/else - Looping with a condition using
for cond { ... } - Counted loops with
for i in 0..n { ... } - Iterating over collections with
for item in list { ... } - Controlling loops with
breakandcontinue - Returning early from functions
Defining Functions
A function is a named, reusable block of code. You define it once and call it as many times as you like. In Auto, you use the fn keyword to define a function:
fn greet(name String) {
print("Hello, " + name + "!")
}This creates a function called greet that takes one parameter — name — with the type String. The type annotation goes after the parameter name, with no colon. The function body is wrapped in curly braces.
To call a function, write its name followed by arguments in parentheses:
greet("Alice")This runs the code inside greet with name set to "Alice".
A few things to notice:
- Parameters have type hints. The caller must pass a value of the correct type.
fn greet(name String)meansnamemust be aString. - Return types are inferred. You don't write
-> voidor-> String. The compiler figures out what the function returns by examining the body. - No parentheses around the type.
name String, notname: Stringorname: String.
Let's see functions in action.
Look at how each target language handles string concatenation inside the function:
- Rust uses
println!("Hello, {}!", name)with a format string — more efficient than+onStringvalues. - Python keeps it simple:
"Hello, " + name + "!". Python's+works directly on strings. - C uses
printf("Hello, %s!\n", name)with a format specifier. - TypeScript uses the same
+concatenation as Auto, since JavaScript handles it natively.
Every language has its own way to combine strings, but the function structure — name, parameter, body — is universal.
Return Values
Functions don't just do things — they can also produce values. In Auto, a function returns the value of its last expression. No return keyword needed:
fn add(a int, b int) {
a + b
}This function evaluates a + b and returns the result. The last expression in the function body is the return value. This is an expression-based design, inspired by languages like Rust.
You store the result in a variable, or pass it directly to another function:
var result = add(3, 4) // result is 7
print(add(10, 20)) // prints 30The key difference across languages is how they handle the return:
- Auto and Rust both use expression-based bodies — the last expression is the return value, no
returnkeyword needed. Rust does require an explicit return type annotation (-> i64), which the transpiler adds. - Python, C, and TypeScript all require the
returnkeyword. The transpiler inserts it automatically.
This is one of Auto's design choices: expression-based functions reduce visual noise while keeping the semantics clear. The compiler always knows what a function returns by looking at the last expression.
Conditional Branching
Programs need to make decisions. The if keyword lets you run a block of code only when a condition is true:
if n > 0 {
print("positive")
}You can chain conditions with else if and provide a fallback with else:
if n > 0 {
print("positive")
} else if n < 0 {
print("negative")
} else {
print("zero")
}Auto evaluates the conditions from top to bottom. The first one that is true runs its block, and the rest are skipped. If none match, the else block runs.
The condition does not need parentheses. The curly braces are required.
The structure is nearly identical across all five languages. The main differences are syntactic:
- Python uses
elifinstead ofelse if, and colons + indentation instead of curly braces. - C requires parentheses around every condition and braces around every block.
- Rust and TypeScript look almost exactly like Auto — parentheses are optional in Rust but required in TypeScript.
Looping with a Condition
Auto uses for for all loops. When you write for followed by a condition, it behaves like a while loop — it repeats as long as the condition is true:
for count < 5 {
print(count)
count = count + 1
}This reads naturally: "for as long as count is less than 5, keep going." Auto uses for instead of while as a deliberate design choice — one keyword for all loops keeps the language small.
When transpiled, the target languages use their native while keyword:
- Rust:
while count < 5 { ... } - Python:
while count < 5: ... - C:
while (count < 5) { ... } - TypeScript:
while (count < 5) { ... }
This loop prints 0, 1, 2, 3, 4 — one number per line. Notice the pattern:
- Initialize a variable before the loop:
var count = 0 - Check a condition at the top of each iteration:
count < 5 - Update the variable inside the loop body:
count = count + 1
If you forget step 3, the loop runs forever. This is a common bug, which is why counted loops (next section) are preferred when you know the number of iterations in advance.
Counted Loops
When you know exactly how many times to repeat, use a counted loop with the .. range operator:
for i in 0..5 {
print(i)
}This means "for each value i from 0 up to (but not including) 5, run the body." It prints 0, 1, 2, 3, 4.
The range 0..5 is exclusive on the right — it includes 0 but not 5. This matches Rust's range syntax. If you want 1 through 10, write 1..11.
The loop variable i is created automatically for each iteration. You don't declare it with var.
Each target language expresses the same counted loop differently:
- Rust uses the same
0..5range syntax as Auto. The transpiler copies it directly. - Python converts
0..5torange(0, 5)— Python's built-in function for generating integer sequences. - C expands the range into a classic
forloop:for (long long i = 0; i < 5; i++). The transpiler calculates the start, end, and step from the range. - TypeScript generates a similar classic
forloop:for (let i = 0; i < 5; i++).
Iterating Collections
You can also use for to iterate over the elements of a list (or any collection):
var fruits = ["apple", "banana", "cherry"]
for fruit in fruits {
print(fruit)
}Each iteration, the variable fruit takes on the next value from the list. This is the most readable way to process every item in a collection.
Each language iterates differently:
- Rust iterates by reference with
for fruit in &fruits, borrowing each element without taking ownership. - Python uses
for fruit in fruits— clean and direct, the closest to Auto's syntax. - C falls back to an index-based loop:
for (int i = 0; i < fruits_len; i++). C has no built-in collection iterator, so the transpiler generates the index variable and length check automatically. - TypeScript uses
for...of, which iterates over values (as opposed tofor...in, which iterates over keys).
break and continue
Two keywords give you finer control inside loops:
breakimmediately exits the loop. No more iterations run.continueskips the rest of the current iteration and jumps to the next one.
for i < 10 {
if i == 3 {
continue // skip printing 3
}
if i == 7 {
break // stop the loop entirely
}
print(i)
}This program prints: 1, 2, 4, 5, 6.
Here's what happens step by step:
istarts at 0, becomes 1 after the increment. Not 3, not 7. Prints1.ibecomes 2. Prints2.ibecomes 3. Hitscontinue. Skipsprint. Goes back to the top.ibecomes 4. Prints4.ibecomes 5. Prints5.ibecomes 6. Prints6.ibecomes 7. Hitsbreak. Loop ends immediately.
break and continue work in all four loop forms — for cond, for i in range, and for item in collection. They are universal loop controls.
Conditional Returns
Functions can return different values depending on a condition. In Auto, because functions are expression-based, you can write a conditional that evaluates to a value:
fn is_even(n int) {
if n % 2 == 0 {
true
} else {
false
}
}Each branch of the if is an expression. The function returns whichever branch is taken — true or false. This works because the last expression in the function body is the return value, and the if/else itself is an expression.
This prints:
true
falseThe transpiler handles the boolean return differently in each language:
- Auto and Rust both treat
if/elseas an expression. Noreturnkeyword needed in either language (though the Rust output adds the-> boolreturn type). - Python needs
return Trueandreturn False— Python'sifis a statement, not an expression, soreturnis mandatory. - C needs
return trueandreturn false, and prints booleans via a ternary operator sinceprintfhas no%boolspecifier. - TypeScript needs
return trueandreturn false, and uses===for the strict equality check.
This pattern — an if/else where each branch is a value — is common in Auto. You'll see it everywhere, from simple checks like is_even to more complex logic that chooses between calculations.
Summary
In this chapter, you learned how to structure programs with functions and control flow:
- Functions are defined with
fn, take typed parameters, and return the value of their last expression. Noreturnkeyword needed in Auto. - Return values are inferred by the compiler. Expression-based bodies keep the code concise.
- Conditional branching uses
if,else if, andelse. Conditions don't need parentheses. Braces are required. - Conditional loops use
for cond { ... }— Auto's equivalent ofwhile. The transpiler outputswhilein every target language. - Counted loops use
for i in 0..n { ... }with an exclusive range. The transpiler converts to each language's native syntax. - Collection iteration uses
for item in list { ... }to process each element. breakandcontinuegive you fine-grained loop control — exit early or skip an iteration.- Conditional returns let
if/elsebranches produce the function's return value directly.
With functions and control flow, you can now write programs that make decisions, repeat work, and organize code into reusable pieces. In the next chapter, we'll look at how to group related data together using structs.