Deep Dive into Rust Functions, Variable Scope, and Closures

Functions are reusable blocks of code that redact a appointed task. For example if we want to make a program to add two numbers, after that we can create a Rust function to add numbers. So now, we do reuse this same function whensoever we add two numbers.

Making a function in Rust helps partition our code into smaller blocks and makes our code look cleaner and easier to Feel.

Not only in Rust Program, however functions are also one of the core building blocks of any programming language.

Define a Function in Rust Programming

In Rust Program, we can use the fn keyword to identify a function. The syntax of a function is below-

fn function_name(arguments) {
    // code
}

Now Let’s see an example

fn greet() {
    // code
}

Below-

  • fn - keyword are used to create a function in Rust Program
  • greet() - name of function
  • // code - function body
  • { } - start and finish of the function body

So now let’s entire the greet() function to print “Hello, World!”.

// define a function
fn greet() {
    println!("Hello, World!");
}

fn main() {

}

When you run this code, you will not get any output. that is because here you are just identify a function. To conduct a function, you need to call it.

Calling a Function in Rust Programming

We can use the name of the function and parenthesis () to call a function.

// call a function
greet();

Now Let’s complete the upon example now.

// define a function
fn greet() {
    println!("Hello, World!");
}

fn main() {
    // function call
    greet();
}

Output :

Hello, World!

Below, we have maked a greet() function that prints “Hello, World!” on the system screen. Advertising that we are calling the function from inside main().

main() Function in Rust Program

If we look gingerly, you can see the syntax of main() beauty similar to a function.

fn main() {
    // function call
    greet();
}

In Rust program, main() is also a function informed as a built-in function that has a individual meaning. this is the entry point (start) of each Rust program.

Example: Function to Add Two Numbers in Rust Program

// function to add two numbers
fn add() {
    let a = 5;
    let b = 10;

    let sum = a + b;

    println!("Sum of a and b = {}", sum);
}

fn main() {
    // function call
    add();
}

Output :

Sum of a and b = 15

In the upon example, you have maked a function named add(). And the function adds two numbers and prints the sum.

Below how the program works-

Screenshot-2024-01-11-214503

Function Parameters in Rust Programming

Now from the definition, you know that a function will be reusable. But, the add() function in our previous example can only used to perform the addition of 5 and 10.

// function to add two numbers
fn add() {
    let a = 5;
    let b = 10;

    let sum = a + b;

    println!("Sum of a and b = {}", sum);
}

that function is not progressive to be reused.

Below, how can create a function with parameters

// function with parameters
fn add(a: i32, b: i32) {
    let sum = a + b;

    println!("Sum of a and b = {}", sum);
}

Below,

  • a and b are function parameter
  • i32 is the data types of parameter

For call this function, you should provide some value by during the function call.

add(2, 11);

Below, 2 and 11 are as known as function contention this are passed to the add function.

Example: Function Parameters In Rust

// define an add function that takes in two parameters
fn add(a: i32, b: i32) {
    let sum = a + b;
    
    println!("Sum of a and b = {}", sum);
}

fn main() {
    // call add function with arguments
    add(2, 11);
}

Output :

Sum of a and b = 13

Return Value in Rust with Function

In the above example, we received the sum of two numbers and printed the result under the function. Nevertheless, you can also back the result from the function and you can use it anywhere in our program.

Now see here’s how we can make a function in Rust that returns a value.

// define an add function that takes in two parameters with a return type
fn add(a: i32, b: i32) -> i32 {
    let sum = a + b;

    // return a value from the function
    return sum;
}

Above, -> i32 before the opening wavy bracket { indicate the function’s return type. In this reason, function will return an i32 value.

Example: Function with Return Value In Rust

// define an add function that takes in two parameters with a return type
fn add(a: i32, b: i32) -> i32 {
    let sum = a + b;

    // return a value from the function
    return sum;
}

fn main() {
    // function call
    let sum = add(3, 5);

    println!("Sum of a and b = {}", sum);
}

Output :

Sum of a and b = 8

Now see below, how the program works-

Screenshot-2024-01-11-220708

The above example , when we attain the return statement in the add function, that it returns the sum variable. In The returned value is stored in the sum variable under main().

Variable Scope In Rust

In System programming, a variable’s scope defines the region in what the variable is available for use. For example-

fn main() {
    // this variable has scope inside the main function block
    let age = 31;
    …
}

Here the age variable has scope under the body {...} of the main() function

Working of Variable Scope in Rust Programming

Now let’s see how variable scope works with an example-

fn main() {
    // scope of outer_var variable is inside the main function code block
    let outer_var = 100;
    
    // start of the inner code block
    {
        // scope of inner_var variable is only inside this new code block
        let inner_var = 200;
        println!("inner_var = {}", inner_var);
    }
    // end of the inner code block
    
    println!("inner_var = {}", inner_var);
    println!("outer_var = {}", outer_var);
}

Below, if you try to print the inner_var outside of the inner code block, but the program fails to compile, and you encounter an error.

Output :

error[E0425]: cannot find value `inner_var` in this scope
  --> src/main.rs:13:32
   |
13 |     println!("inner_var = {}", inner_var);
   |                                ^^^^^^^^^ help: a local variable with a similar name exists: `outer_var`

But to fix this Issue we can following below-

fn main() {
 // scope of outer_var variable is inside the main function code block
 let outer_var = 100;
 
 // start of the inner code block
 {
     // scope of inner_var variable is only inside this new code block
     let inner_var = 200;
     println!("inner_var = {}", inner_var);
     println!("outer_var inside inner block = {}", outer_var);
 }
 // end of the inner code block
 
 println!("outer_var = {}", outer_var);
}

Output :

inner_var = 200
outer_var inside inner block = 100
outer_var = 100

We can removed the println!("inner_var = {}", inner_var); from the outer code block and the program now works as prospective.

Additionally, you can access the outer_var under the inner code block because this scope is in the main() function.

Please see how variable scope works in the above program.

Screenshot-2024-01-11-222203

Variable Shadowing in Rust Programming

In Rust Programming, when a variable declared within a individual scope has the same name as a variable expressed in the outer scope, that is known as variable shadowing.

We can use the equivalent variable name in several scope blocks in the same program.

Now let’s take a look at an example-

fn main() {
    let random = 100;

    // start of the inner block
    {
        println!("random variable before shadowing in inner block = {}", random);

        // this declaration shadows the outer random variable
        let random = "abc";

        println!("random after shadowing in inner block = {}", random);
    }
    // end of the inner block

    println!("random variable in outer block = {}", random);
}

Output :

random variable before shadowing in inner block = 100
random after shadowing in inner block = abc
random variable in outer block = 100

Now see, the random variable published in the outer block is shadowed in the inner block. Let’s look what that means-

let random = "abc";

Variable Freezing in Rust Programming

You can freeze a variable in Rust Programming by using shadowing and immutability. At one time a variable is frozen, after you cannot change the variable value in the inner scope.

Now See an Example-

fn main() {
    let mut age = 1;

    // start of the inner block
    {
        // shadowing by immutable age variable
        let age = age;

        // error, age variable is frozen in this scope
        age = 2;

        println!("age variable inner block = {}", age);
        // age variable goes out of scope
    }
    // end of the inner block

    // age variable is not frozen in outer block
    age = 3;

    println!("integer variable outer block = {}", age);
}

Output :

error[E0384]: cannot assign twice to immutable variable `age`
  --> src/main.rs:10:9
   |
7  |         let age = age;
   |             ---
   |             |
   |             first assignment to `age`
   |             help: consider making this binding mutable: `mut age`
...
10 |         age = 31;
   |         ^^^^^^^^ cannot assign twice to immutable variable

Please see the above example, you have imposed the mutable variable of the outer block named age to the same immutable variable in the inner scope.

fn main() {
    let mut age = 100;

    {
        let age = age;
        …
    }
    …
}

For doing this, you are shadowing the mutable age variable with an immutable variable named age.

Now the age variable freezes inside the under the block from the inner age variable is instruction to the same value as the age variable in the outer block.

In this way, you cannot change the value of age inside the inner block and visibility an error.

At one time you get out of the inner block, the value of age can be reciprocal.

Finally Let’s see at the working version of the variable freezing example below-

fn main() {
    let mut age = 100;

    {
        // shadowing by immutable age variable
        let age = age;

        println!("age variable inner block = {}", age);
        // age goes out of scope
    }

    // age variable is not frozen in this scope
    age = 3;

    println!("age variable outer block = {}", age);
}

Output :

age variable inner block = 100
age variable outer block = 3

Rust Closure In Rust program

In Rust Program, closures are functions outside of names. They are also known as innominate functions or lambdas

Defining a Closure in Rust Program

Below how we create a closure in Rust Program-

// define a closure to print a text
let print_text = || println!("Defining Closure");

In the upon example, you have created a closure that prints the text “Defining Closure”. Below-

  • print_text- variable to store of the closure
  • || - start of closure
  • println!("Defining Closure") - the body of the closure

Calling Closure In Rust

At one time a closure is defined, you need to call it irrespective of calling a function. For call a closure, you can use the variable name to which is the closure is assigned. For example-

// define a closure to print a text
let print_text = || println!("Defining Closure");

// call the closure
print_text();

here print_text() calls the closure

Example: Closure in Rust Program

fn main() {
    // closure that prints a text
    let print_text = || println!("Hello, World!");
    
    print_text(); 
}

Output :

Hello, World!

In the upon example, you have defined a closure and stored it in the print_text variable. We after that call the closure using print_text().

Rust Closure with Parameters In Rust Programming

In Rust Program, you can also permission parameters to a closure. For example-

// define closure to add 1 to an integer
let add_one = |x: i32| x + 1;

Below,

  • let add_one - it is the name of the variable to store the closure
  • |x: i32| - it’s the parameter and its types that we pass to the closure
  • x + 1; - it’s the body of the closure who returns x + 1

If yow make a closure with parameters, you need to pass the value while calling of the closure.

// call the closure with value 2
add_one(2);

Example: Rust Closure with Parameter In Rust Program

fn main() {
    // define a closure and store it in a variable
    let add_one = |x: i32| x + 1;
    
    // call closure and store the result in a variable
    let result = add_one(2);
    
    println!("Result = {}", result);
}

Output :

Result = 3

In the upon example, you have defined a closure and binded that is to the add_one variable. You after call the closure with add_one(2) and bind the return value to the result variable.

Previous
Mastering Rust Data Types: A Comprehensive Guide for Developers
Next
Exploring the Core Features of Rust Standard Library