Functions
The starting point of every Rust program is itself a function fn called the main() function, which can be further subdivided into separate functions for reuse of code or better code organization. Rust doesn't care in which order these functions are defined, but it is nice to put the function main() at the start of the code to get a better overview. Rust has incorporated many features of traditional functional languages; we will see examples of that in Chapter 5, Higher Order Functions and Error-Handling.
Let's start with a basic function example:
// from Chapter 3/code/functions.rs fn main() { let hero1 = "Pac Man"; let hero2 = "Riddick"; greet(hero2); greet_both(hero1, hero2); } fn greet(name: &str) { println!("Hi mighty {}, what brings you here?", name); } fn greet_both(name1: &str, name2: &str) { greet(name1); greet(name2); }
This prints out the following output:
Hi mighty Riddick, what brings you here?Hi mighty Pac Man, what brings you here?Hi mighty Riddick, what brings you here?
Like variables, functions have variable snake_case names that must be unique, and their parameters (which have to be typed) are separated by commas, as in this example:
name1: &str, name2: &str
It looks like a binding, but without the let binding. Forcing a type to the parameters was an excellent design decision as it documents the function for use by its caller code and allows type inference inside the function. The type here is &str because strings are stored on the heap (see section The stack and the heap in Chapter 2, Using Variables and Types).
The functions above don't return anything useful (in fact, they return the unit value ()), but, if we want a function to actually return a value, its type must be specified after an arrow ->, as in this example:
fn increment_power(power: i32) -> i32 { println!("My power is going to increase:"); power + 1 } fn main() { let power = increment_power(1); // function is called println!(" I am now at power level: {}", power);}
When executed, it prints an output like the following:
My power is going to increase: I am now at power level: 2
The return value of a function is the value of its last expression. Note that in order to return a value, the final expression must not end with a semicolon. What happens when you do end it with a semicolon? Try it out: in this case the unit value () is returned, and the compiler gives you the following error:
error: not all control paths return a value
We could have written the statement return power + 1 as the last line, but that is not idiomatic code. If we wanted to return a value from the function before the last code line, we would have to write return value; as in the following:
if power < 100 { return 999 }
If this was the last line in the function, you would write it as follows:
if power < 100 { 999 }
A function can return only one value, but this isn't much of a limitation. If we have for example, three values, a, b, and c, to return, make one tuple (a, b, c) with them and return that. We will examine tuples in more detail in the next chapter.
A function that never returns is called a diverging function and it has return type !.
For example:
fn diverges() -> ! { panic!("This function never returns!"); }
It can be used as any type, for example to isolate exception handling, like in this example.
A function can be recursive; that means that the function calls itself, like in the following example:
// from Chapter 3/code/fib_procedural.rs fn main() { let ans = fib(10); println!("{}", ans); } fn fib(x: i64) -> i64 { if x == 0 || x == 1 { return x; } fib(x - 1) + fib(x - 2) }
Make sure that the recursion stops by including a base case, in this example when the function is called for x equal to 1 and 0.
Functions have a type, for example, the type of the function increment_power from the previous code snippet like:
Fn(i32) -> i32
The fn function in general denotes a function type.
In Rust, you can also write a function inside another function (called a nested function), contrary to C or Java. This should only be used for small helper functions needed locally.
As an exercise, try the following:
Knowing that if can be an expression, simplify the following function:
fn verbose(x: i32) -> &'static str { let result: &'static str; if x < 10 { result = "less than 10"; } else { result = "10 or more"; } return result; }
See code in Chapter 3\exercises\ifreturn.rs.
The static in and static str variables are a so-called lifetime indication, needed here to indicate how long the function's return value will exist. The static lifetime is the longest possible lifetime, such an object stays alive throughout the entire application, and so it is available in all of its code. We'll discuss this in detail in the section Lifetimes in Chapter 6, Using Variables and Types.
What is wrong with this function that returns the absolute of a given number in the variable x?
fn abs(x: i32) -> u32 { if x > 0 { x } else { -x }
}
Correct and test it (see the code in Chapter 3/exercises/absolute.rs).