Introduction to Rust Programming πŸ¦€

Rust Basics πŸ¦€

Rust Basics Series #5: Functions in the Rust Programming Language

In this chapter of the Rust Basics series, learn to use functions and return values from them with the help of examples.

Like any modern programming language, Rust too has functions.

The function that you are already familiar with is the main function. This function gets called when the program is launched.

But what about other functions? In this article, you'll learn to use functions in Rust programs.

The basic syntax of a function

You may already know this based on how we declare the main function, but let's look at the syntax of declaring a function nonetheless.

// declaring the function
fn function_name() {
    <statement(s)>;
}

// calling the function
function_name();

Let's look at a simple function that prints the string "Hi there!" to the standard output.

fn main() {
    greet();
}

fn greet() {
    println!("Hi there!");
}
πŸ“‹
Unlike C, it does not matter if you call the function before declaring or defining it. As long as the said function is declared somewhere, Rust will handle it.

And as expected, it has the following output:

Hi there!

That was simple. Let's take it to the next level. Let's create functions that accept parameter(s) and return value(s). Neither are mutually exclusive or inclusive.

Accepting parameters with functions

The syntax for a function that accepts the parameter is as follows:

// declaring the function
fn function_name(variable_name: type) {
    <statement(s)>;
}

// calling the function
function_name(value);

You can think of the function parameters as a tuple that is passed to the function. It can accept parameters of multiple data types and as many as you wish. So, you are not restricted to accepting parameters of the same type.

Unlike some languages, Rust does not have default arguments. Populating all parameters when calling the function is compulsory.

Example: Famished function

Let's look at a program to understand this better.

fn main() {
    food(2, 4);
}

fn food(theplas: i32, rotis: i32) {
    println!(
        "I am hungry... I need {} theplas and {} rotis!",
        theplas, rotis
    );
}

On line 5, I declare a function called food. This function takes in 2 parameters: theplas and rotis (Names of Indian food items). I then print the contents of these variables.

From the main function, I call the food function with parameters '2' and '4'. This means that theplas gets assigned the value '2' and rotis get assigned the value '4'.

Let's look at the program output:

I am hungry... I need 2 theplas and 4 rotis!

And now I'm actually hungry... πŸ˜‹

Returning values from a function

Just as a function can accept values in the form of parameters, a function can also return one or more values. The syntax for such a function is as follows:

// declaring the function
fn function_name() -> data_type {
    <statement(s)>;
}

// calling the function
let x = function_name();

The function can return a value using either the return keyword or by using an expression instead of a statement.

Wait! Expression what?

Before you go further: Statements vs Expressions

It may not fit in the flow of the Rust function examples but you should understand the difference between statements and expressions in Rust and other programming languages.

A statement is a line of code that ends with a semi-colon and does not evaluate to some value. An expression, on the other hand, is a line of code that does not end with a semi-colon and evaluates to some value.

Let's understand that with an example:

fn main() {
    let a = 873;
    let b = {
        // statement
        println!("Assigning some value to a...");

        // expression
        a * 10
    };

    println!("a: {a}");
}

On line 3, I open a code block, inside which I have a statement and an expression. The comments highlight which one is which.

The code on the 5th line does not evaluate to a value and hence needs to be ended with a semi-colon. This is a statement.

The code on the 8th line evaluates to a value. It is b * 10 which is 873 * 10 and it evaluates to 8730. Since this line does not end with a semi-colon, this is an expression.

πŸ“‹
An expression is a handy way of returning something from a code block. Hence, it is an alternate to the return keyword when a value is returned.
The expressions are not only used to "return" a value from a function. As you just saw, the value of `b * 10` was "returned" from the inner scope to the outer scope and it was assigned to the variable `b`. A simple scope isn't a function and the value of the expression was still "returned".

Example: Buying rusted fruits

Let's understand how a function returns a value using a demonstration.

fn main() {
    println!(
        "If I buy 2 Kilograms of apples from a fruit vendor, I have to pay {} rupees to them.",
        retail_price(2.0)
    );
    println!(
        "But, if I buy 30 Kilograms of apples from a fruit vendor, I have to pay {} rupees to them.",
        wholesale_price(30.0)
    );
}

fn retail_price(weight: f64) -> f64 {
    return weight * 500.0;
}

fn wholesale_price(weight: f64) -> f64 {
    weight * 400.0
}

Above I have two functions: retail_price and wholesale_price. Both functions accept one parameter and store the value inside the weight variable. This variable is of type f64 and the function signature denotes that an f64 value is ultimately returned by the function.

Both of these functions multiply the weight of apples purchased by a number. This number represents the current price per Kilogram for apples. Since wholesale purchasers have big orders, logistics are easier in a way, the price can be eased a bit.

Other than the price per Kilogram, the functions have one more difference. That is, the retail_price function returns the product using the return keyword. Whereas, the wholesale_price function returns the product using an expression.

If I buy 2 Kilograms of apples from a fruit vendor, I have to pay 1000 rupees to them.
But, if I buy 30 Kilograms of apples from a fruit vendor, I have to pay 12000 rupees to them.

The output shows that both methods of returning a value from a function work as intended.

Returning multiple values

You can have a function that returns multiple values of different types. You have many options, but returning a tuple is the easiest.

Following is an example:

fn main() {
    let (maths, english, science, sanskrit) = tuple_func();

    println!("Marks obtained in Maths: {maths}");
    println!("Marks obtained in English: {english}");
    println!("Marks obtained in Science: {science}");
    println!("Marks obtained in Sanskrit: {sanskrit}");
}

fn tuple_func() -> (f64, f64, f64, f64) {
    // return marks for a student
    let maths = 84.50;
    let english = 85.00;
    let science = 75.00;
    let sanskrit = 67.25;

    (maths, english, science, sanskrit)
}

The tuple_func returns four f64 values, enclosed in a tuple. These values are the marks obtained by a student in four subjects (out of 100).

When the function is called, this tuple is returned. I can either print the values using tuple_name.0 scheme, but I thought it best to destruct the tuple first. That will ease the confusion of which value is which. And I print the marks using the variables that contain values from the destructured tuple.

Following is the output I get:

Marks obtained in Maths: 84.5
Marks obtained in English: 85
Marks obtained in Science: 75
Marks obtained in Sanskrit: 67.25

Conclusion

This article covers functions in the Rust programming language. The "types" of functions are covered here:

  • Functions that do not accept any parameter(s) nor return value(s)
  • Functions that accept one or more parameters
  • Functions that return one or more values back to the caller

You know what comes next? Conditional statements, aka if-else in Rest.

Rust Basics Series #6: Using If Else
You can control the flow of your program by using conditional statements. Learn to use if-else in Rust.

Stay tuned and enjoy learning Rust with It's FOSS.