Functions in Rust

Functions in Rust is a block of statements which helps the user to perform repeated tasks. For example, the user wants to perform an operation which takes 5 lines of code. Now when the user wants to perform the same operation in different places, then he/she group this 5 lines of code and the user can call this group whenever required, rather than writing the 5 lines again and again. And this group is called a function.

Why use a function

Below are some major points about why we should practice using functions?

  • A function can reduce the number of codes, which saves memory.
  • A function can be called anywhere in the program.
  • A function can reduce the chances of errors.
  • A function helps in easy modification of a program.
  • A function enhances program readability.
  • A function enhances coding style and speed.

How to declare a Function in Rust

A function in rust can be declared using the keyword fn and then mentioning the function name. The syntax for functions in Rust:

fn function_name()   // This is a function declaration.
{
    // Commands/Statements can be written here !           
}
  • fn is the keyword used for declaring a function in Rust.
  • main() is the function name.
  • The scope of a function starts with an open brace { and closed by the closing brace } . Inside these two braces, the program statement resides.

An example program for functions in Rust:

fn main()   // This is a function declaration.
{
    println!("Hello Chercher.tech!");            
}

Types of Functions in Rust

There are basically two different types of functions. They are:

  • In-built Functions: In-built functions are those function, which is present inside the library package of Rust itself. This kind of functions are designed to do a specific task and can be called anytime to perform the task without declaring it inside the program.
  • User-defined Functions: User-defined functions are those functions which are not present inside the library, but are written by the user to perform a specific task. The writing the definition of these kinds of function is required inside the program itself, in which it is going to be called.
Program using an in-built function
fn main() {
    println!("Hello, world!");
}

In the program above, println! is the in-built function.

Program output:

program-using-inbuilt-function

Program using an user-defined function
fn main() {
    println!("Hello, world!");
    my_function();
}
fn my_function() {
    println!("Hello World!");
}

In the above program, we have created an user-defined function called my_function() using the keyword fn .

Program Output:

program-using-inbuilt-and-userdefined-function

Function with Parameter

A parameter is a value inside a function declaration which is the local variable for the function and we can carry out operations using this/these variable. A function can accept:

  • No parameters.
    fn my_function() {
        // some code 
    }​
  • One or more parameters.
    fn my_function(parameter_name: datatype) {
        // some code 
    }
    fn my_function2(parameter_name1: datatype1, parameter_name2: datatype2, parameter_name3: datatype3) {
        // some code 
    }​



There can be different types of parameters accepted by a function. Such as integers, characters strings, etc. Below we shall see how integer, character, and strings can be used as a parameter.

Integer as a parameter

A value which is of integer datatype can also be used as a parameter, as shown in the program.

fn main()
{
print_number(5);
}
fn print_number(number: i32) {
    println!("The value is is: {}", number);
}

In the above program, print_number() function accepts a parameter called number and it is an i32 type.

Program Output :

integer-parameter

Char as a parameter

A value which is of character datatype can also be used as a parameter, as shown in the program.

fn main()
{
print_character('a');
}
fn print_character(value: char) {
    println!("The character value is: {}", value);
}

In the above program, the function print_character() accepts a parameter of char type.

Program Output :

character-parameter

String as a parameter

A value which is of string datatype can be used as a parameter, as shown in the program.

fn main() {
   my_name("Fitas");
}
fn my_name(name: &str) {
    println!("My name is  {} ", name );
}

In the above program, the function print_string() accepts a parameter name and it is of str type. Here, the string Fitas is passed as an argument to the function my_name() .

So, when the function my_name() is called inside the main() function, then the string is printed.

Program Output :

string-parameter

Return Type & Return Value

  • Return value : When the user calls a function, the function has the capability to send back a value. This value could be of any type and this value is called a return value of a function.
  • Return Type : Return Type is the datatype of the value that the function is going to return when it is called. And usually, the return type is mentioned using (arrow) -> followed by the datatype.

Functions can also be classified based on whether the function returns a value or not to the function in which it is called or invoked.

Function with no return type.

Normally all the functions return a value, sometimes there can also be a function which will not have any return type, such functions return void as the return value. To return a void value, the user does not have to mention anything at the end of the function.

fn main() {
    println!("Hello, world!");
    my_function();
}
fn my_function() {
    println!("This is just a use of a function");
}

The output of the above program is below:

function-no-return

In the above output, we see Hello, world! is printed as it is inside the main() function. But the next text This is just a use of a function is printed when the function my_function() is executed, we have called my_function() inside the main function. my_function() does not return any value.

Function with a Return Value

A Function directly returns a value to the code which calls them. We can return a value which was stored in a variable earlier or new value which formed at return time. The type of the return value is declared after an arrow -> .

In the below code, i32 is the return type, which means this function is going to return an integer value.

fn ten() -> i32 
Note: In Rust, the return value of the function is the value of the variable in the final expression inside the function.

We can return early from a function, using the keyword return by specifying a value. But in default, almost all functions, in all cases will return the last expression implicitly.

Now, inside the main() function we have the statement:

let x = ten();

This statement means, that the variable x which is declared using the keyword let is assigned with the return value of the function ten().

Inside the function, we have a value 10. Now, surprisingly, the value is just mentioned and does not even contain any semicolon ; at the end of the line. It means, that the line containing 10 not only works as an expression but also act as a return value.

fn ten() -> i32 {
    10
}
fn main() {
    let x = ten();
    println!("The value of x is: {}", x);
}

The value 10 is printed as the output, this is because the variable x is initialized with 10 which is the return value of the function ten().

function-using-arrow

The return value from a function can be used for different operations. We can store the returned value inside a variable and can use it as we want.

In this program, we have defined a function called square(x: i32) . The function contains a variable x as its parameter. And we have also defined an arrow to declare the type of value it will return. (-> i32)

x*x is at the end of the function square(), and it is the return value of the function.

The program below to find the square of a number.

fn main() {
    let x = square(5);
    println!("The square of x is: {}", x);
}
fn square(x: i32) -> i32 {
    x*x
}

The output of the program :

function-using-operations

In our above examples, we have only seen the functions which return an integer type. Now let us see how can we return a string from a function.

Function Returning String

Not only integer but also strings can be returned using a function.

In the below program, the function hello_string() is the function which returns a string. Inside the function, we have parameter as &str which is a pointer variable pointing to a memory address which we storing our string. _: is used to signify, that it is a private pointer variable.

And then we mentioned the return type, which is a string type in our case. This is done using the symbol -> after the function name. The string hello chercher.tech is returned using the keyword

fn main() {
    println!("message: {}", hello_string(""));
}
fn hello_string(_: &str) -> &str {
    return "hello chercher.tech";
}

Program Output:

string-return-function

Diverging Functions

Diverging Functions in Rust are used for crashing the current execution of a thread. This type of function does not return anything but has a characteristic of printing a string and crashing the thread at the end of its execution.

Syntax for diverging function :

fn diverge_function_name() -> ! {
  // some code
}

The exclamatory sign ! after the arrow(->) means a diverging function.

panic! is one of the diverging functions which can crash the current thread, and it prints a given text to the console.

Since panic!() function will crash the current execution of the thread, therefore, it will not return anything.

fn main()
{
	diverge_function();	
}
fn diverge_function() -> ! {
   panic!("This function does not return anything, but crashes the current thread execution");
}

Program Output :

diverging-function-example

Function Pointers

What is a pointer

A pointer is nothing but a variable which stores the memory location of another variable. For example, if we write let a= &d , it means that a is holding the memory address of d.

What is a function pointer

There can be also variable bindings which can point to a function. A function pointer can be declared in this way:

let p: fn(i32) ->i32

Here, is the variable binding, which points to a function, which takes an i32 datatype as its argument and returns a value which is of i32 datatype.

In the below program, we have declared a user-defined function called add_one() which takes an i32 type parameter and has an i32 return type. Inside the main function, we have declared a variable f and assigned it the function add_one().

fn add_one(i: i32) -> i32 {
    i + 1
}
fn main()
{
let f: fn(i32) -> i32 = add_one;
let _f = add_one;
let value = f(5);
println!("{}", value);
}

In the first assignment: let f: fn(i32) -> i32 = add_one; , the assignment is done without type inferencing.

Note : Type inferencing is the feature by which the compiler look at the invocation of each method and corresponding declaration to determine the type of arguments.

And in the second assignment: let _f = add_one; , the assignment is done with type inference.

Now, the variable f will work the same as that of the function add_one() itself. Therefore, we directly pass the value 5 and the returned result is assigned to the variable value.

Program Output :

function-pointer

Function as a Parameter

So far, it is shown how different datatypes- such as integer, characters, strings, etc can be used as parameters inside a function.

Similarly, a function itself can be used as a parameter for a different function. The syntax is as follows:

function_name(variable: &Generic_function -> datatype) -> datatype

Here, f is assigned a generic function called Fn(i32), which is a pure function and holds an i32 type of parameter inside it and returns an i32 datatype. Fn(i32) is assigned to f and is written as a parameter inside the function function() which again itself returns a i32 type of data.

fn my_function(value: i32, f: &Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}
fn multiply(value: i32) -> i32 {
    value * value}
fn main() {
    my_function(5, &multiply);
}

In the above program, inside the function my-function(), there are two parameters. One is the value which is an integer type parameter and the other function Fn(i32) is a pure as well as a generic function.

The function multiply() has a parameter value , which is of i32 datatype and returns a value of i32 type.

Now, when the address of the function multiply() is passed as a parameter inside the function my_function() , it takes the data 5 and invokes the function f which in turn uses the function multiply() and produces the return value x*x.

The Output of the Program

function-as-parameter

Common Errors in Rust Functions

When there are variables in a statement and there is no semicolon ; at the end of the line, it creates confusion that, whether it will create an error. But the fact is that adding a semicolon ; in the end of the statement, will itself create an error.

We shall discuss with an example, why this error is created. Let us consider the program below.

fn main() {
    let x = square(5);

    println!("The square of x is: {}", x);
}

fn square(x: i32) -> i32 {
    x*x;
}

Now when the program is compiled below errors are produced by the compiler.

error[E0308]: mismatched types
 --> main.rs:7:22
 fn square(x: i32) -> i32 {
 ------            ^^^ expected i32, found () 
  this function's body doesn't return
   x*x;
 - help: consider removing this semicolon
= note: expected type `i32`
  found type `()`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.

Now let us analyze, why this error occurred and what does this error mean?

Since a semicolon ; is added at the end of the line x*x therefore, now this statement does not return any value. So, it displays that i32 was expected, but the function is returning NULL which is indicated by an empty tuple.

Rust generates an error message consider removing this semicolon. This will help the programmer to some extent to correct the error.

Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions