Functions

Functions abstract a bunch of instructions into named entities, which can be invoked later by other code and help manage complexity. We already used a function in our greet.rs program, that is, the main function. Let's look at how we can define another one:

// functions.rs

fn add(a: u64, b: u64) -> u64 {
a + b
}

fn main() {
let a: u64 = 17;
let b = 3;
let result = add(a, b);
println!("Result {}", result);
}

In the preceding code, we created a new function named add. The fn keyword is used to create functions followed by its name, add, its parameters inside parentheses a and b, and the function body inside {} braces. The parameters have their type on the right, after the colon :. Return types in functions are specified using a ->, followed by the type, u64, which can be omitted if the function has nothing to return. Functions also have types. The type of our add function is denoted as fn(u64, u64) -> u64. They can also be stored in variables and passed to other functions.

If you look at the body of add, we don't need a return keyword to return a + b as in other languages. The last expression is returned automatically. However, we do have the return keyword available for early returns. Functions are basically expressions that return a value, which is a () (Unit) type by default, akin to the void return type in C/C++. They can also be declared within other functions. The use case for that is when you have a functionality within a function (say, foo) that is hard to reason as a sequence of statements. In this case, one can extract those lines in a local function, bar, which is then defined within the parent function, foo.

In main, we declared two variables, a and b, using the let keyword. As is the case with b, we can even omit specifying the type as Rust is able to infer types of variables in most cases by examining your code. This is also the case with the result, which is a u64 value. This feature helps prevent type signature clutter and improves the readability of code, especially when your types are nested inside several other types that have long names.

Rust's type inference is based on the Hindly Milner type system. It's a set of rules and algorithms that enable type inference in a programming language. It's an efficient type inference method that performs in linear time, making it practical to type check large programs.

We can also have functions that modify their arguments. Consider the following code:

// function_mut.rs

fn increase_by(mut val: u32, how_much: u32) {
val += how_much;
println!("You made {} points", val);
}

fn main() {
let score = 2048;
increase_by(score, 30);
}

We declare a  score variable with 2048 as the value, and call the increase_by function, passing score and the value 30 as the second argument. In the increase_by function, we have specified the first parameter as mut val, indicating that the parameter should be taken as mutable, which allows the variable to be mutated from inside the function. Our increase_by function modifies the val binding and prints the value. Following is the output when running the program:

$ rustc function_mut.rs 
$ ./function_mut
You made 2078 points

Next, let's look at closures.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset