Functions
In this chapter, we'll explore learn how to define functions, use parameters, and return values.
Functions
Functions are prevalent in Mana code. You've already seen the most important function: main, which is the entry point of many programs. You've also seen fn, which declares new functions.
Mana uses snake_case as the conventional style for function and variable names:
module main
fn main() -> void {
println("Hello, world!")
another_function()
}
fn another_function() -> void {
println("Another function.")
}Function definitions start with fn followed by a name and parentheses. The curly brackets tell the compiler where the function body begins and ends.
We can call any function we've defined by entering its name followed by parentheses. Because another_function is defined in the program, it can be called from inside main.
Note that we defined another_function after main. We could have defined it before. Mana doesn't care where you define your functions, only that they're defined somewhere in a scope visible to the caller.
Parameters
Functions can have parameters, special variables that are part of a function's signature. When a function has parameters, you provide it with concrete values called arguments.
module main
fn main() -> void {
another_function(5)
}
fn another_function(x: int) -> void {
println("The value of x is: ", x)
}The declaration of another_function has one parameter named x of type int. When we pass 5 to the function, println outputs The value of x is: 5.
In function signatures, you must declare the type of each parameter. This is a deliberate decision: requiring type annotations in function definitions means the compiler almost never needs you to use them elsewhere.
When defining multiple parameters, separate them with commas:
module main
fn main() -> void {
print_labeled_measurement(5, 'h')
}
fn print_labeled_measurement(value: int, unit_label: char) -> void {
println("The measurement is: ", value, unit_label)
}Default Parameters
Parameters can have default values:
module main
fn greet(name: string, greeting: string = "Hello") -> void {
println(greeting, ", ", name, "!")
}
fn main() -> void {
greet("Alice") // Hello, Alice!
greet("Bob", "Welcome") // Welcome, Bob!
}Named Arguments
For clarity, you can use named arguments:
module main
fn create_user(name: string, age: int, active: bool) -> void {
// ...
}
fn main() -> void {
create_user(
name: "Alice",
age: 30,
active: true
)
}Statements and Expressions
Function bodies are made up of statements optionally ending in an expression.
Statements are instructions that perform an action and don't return a value.
Expressions evaluate to a resulting value.
Creating a variable and assigning a value is a statement:
let y = 6Function definitions are also statements. Statements don't return values, so you can't assign a let statement to another variable:
let x = (let y = 6) // Error!Expressions evaluate to a value. A math operation like 5 + 6 is an expression that evaluates to 11. Expressions can be part of statements.
Calling a function is an expression. Calling a macro is an expression. A new scope block created with curly brackets is an expression:
module main
fn main() -> void {
let y = {
let x = 3
x + 1
}
println("The value of y is: ", y) // 4
}This expression:
{
let x = 3
x + 1
}evaluates to 4. The last expression in a block is implicitly its return value.
Functions with Return Values
Functions can return values. We declare their type after an arrow (->):
module main
fn five() -> int {
return 5
}
fn main() -> void {
let x = five()
println("The value of x is: ", x)
}There are no function calls, macros, or even let statements in five—just the return statement. That's a perfectly valid function in Mana.
Here's another example:
module main
fn plus_one(x: int) -> int {
return x + 1
}
fn main() -> void {
let x = plus_one(5)
println("The value of x is: ", x) // 6
}Early Returns
You can return early from a function:
fn absolute(x: int) -> int {
if x < 0 {
return -x
}
return x
}Generic Functions
Functions can work with multiple types using generics:
module main
generic<T>
fn identity(x: T) -> T {
return x
}
fn main() -> void {
let i = identity(42) // T = int
let s = identity("hello") // T = string
}We'll cover generics in depth in Chapter 10.
Lambda Expressions
For short, inline functions, use lambda expressions:
let add = |a, b| a + b
let result = add(3, 4) // 7
// With type annotations
let multiply = |x: int, y: int| -> int { x * y }
// Multi-statement lambda
let process = |x| {
let doubled = x * 2
return doubled + 1
}Lambdas can capture variables from their environment:
let factor = 10
let scale = |x| x * factor
println(scale(5)) // 50Now let's learn about Comments.