DEV Community

Cover image for Rust from the beginning, functions
Chris Noring for Microsoft Azure

Posted on

Rust from the beginning, functions

in this part of the Rust from the beginning series, we will look at functions and how they can help us create reusable part of code.

Series:

  • Your first program
  • Variables
  • Functions, you are here
  • Rust projects with Cargo
  • Control flow
  • IO, read and write from the console
  • Error handling
  • Working with files Advanced
  • Ownership & Borrowing
  • Testing

Resources

Functions what are they and why do I need them

A function is a named reference containing at least one and often many statements. Some of the benefits of functions are:

  • reusability & clean up. When you create a function, you often do so, because you find there are pieces of code that repeats itself. By creating a function, you can use it elsewhere and replace it with the repeating pattern. Imagine the following taking place in your code:
   let mut account_balance = 100.20;
   let stock_sales = 50;
   let mut actual_deposit = 50 * 0.51;
   account_balance += actual_deposit;
   // some other lines of code
   let stock_sales = 100;
   actual_deposit = 100 * 0.51;
   account_balance += actual_deposit;
Enter fullscreen mode Exit fullscreen mode

Here you see that selling stocks is about first off selling it, secondly it's about giving a certain percentage to the tax office. Lastly, apply the proceeds to the account balance. That's a behavior repeating itself in the code, albeit the values are slightly different.

A simpler version of the above code can be achieved by creating a function get_stock_procceed() that hides some of the behavior as well:

   let mut account_balance = 100.20;
   account_balance = get_stock_proceed(50);
   // some other lines of code
   account_balance = get_stock_proceed(100);
Enter fullscreen mode Exit fullscreen mode
  • readability. Another thing adding a function will achieve is readability, a function is named after what it does, adding or multiplying something is not as expressive as saying "getting the proceeds" of something. Also, the fewer lines there is, the easier it is to read, (to a point).

A first function

Oki then, we hopefully understand a bit more about the what and the whys of functions, how do we create them? We create a function using the fn keyword, a name for the function, parenthesis and curly braces.

Here's an example:

fn say_hi() {
  println!("Hi");
}
Enter fullscreen mode Exit fullscreen mode

To invoke it, you refer to it by name and add open and close parenthesis:

say_hi();
Enter fullscreen mode Exit fullscreen mode

Returning a value

The say_hi() function above doesn't return anything. Often, you want a function to have that capability. The reason is that you want the function to perform some sort of calculation like adding two numbers, or product maybe a random number.

There are two ways to return a value:

  • Using a return keyword. The idea is that you signal to function that at this point you are ready to return the value and exit the function, like so:
   fn get_message(temperature_in_celsius:i32) -> &'static str {
      if temperature_in_celsius < 0 {
        return "Freezing";
      } 
      return "Ok temp";
   }
Enter fullscreen mode Exit fullscreen mode

In this code, the code has two different execution paths depending on what value I provide to the function, at this point, I need a return statement to explicitly say, this is where I need to exit the function.

  • the last line. The other way to return a value from a function is to place what you want on the last line of function like so:
   fn produce_number() -> i32 {
     let number = 3;
     let another_number = 5;
     number + another_number;
   }
Enter fullscreen mode Exit fullscreen mode

The last line of the function is what's being returned, to receive the returned value, invoke the function and assign it like so:

   let value = produce_number();
   println!("{}", value);
Enter fullscreen mode Exit fullscreen mode

or use the value straight away like so:

   println!("{}", produce_number());
Enter fullscreen mode Exit fullscreen mode

You've seen something so far, an arrow -> right after the parenthesis of the function, what is that? That's the return type of the function. It's needed if the function returns something. For the produce_number() function the return type is i32, and we write it like so: -> i32. For an divide() function below we make the return type f32 as we mean to deal with decimals:

fn divide() -> f32 {
  5 as f32 / 2 as f32
}
Enter fullscreen mode Exit fullscreen mode

So what's going on here, f32 but also as f32? Let's back it up, and start with a simpler version of this function:

fn divide2() -> i32 {
  5 / 2
}
Enter fullscreen mode Exit fullscreen mode

Were you to run this, you would get 2 as response, which is probably not what you want? However, because you made the return type i32, you get a whole number and cut out the decimal. Let's change it to decimal then:

fn divide() -> f32 {
  5 / 2
}
Enter fullscreen mode Exit fullscreen mode

At this point, you get a compilation error:

  --> app.rs:21:3
   |
20 | fn divide() -> f32 {
   |                 --- expected `f32` because of return type
21 |   5 / 2
   |   ^^^^^ expected `f32`, found integer
Enter fullscreen mode Exit fullscreen mode

It states that 5 and to are integers, we need to change that. We an either write them as 5.0 and 2.0 or use the operator as and turn them into floating points. We went with the latter, 5 as f32 and that's how we got there. This is very static function, we can't do much with it, it would be neat if we could pass it any value to do the calculation for us, for that, we need parameters.

Adding parameters

A parameter is something we send into a function. To send something into a function, we need a placeholder, a parameter that catches the value we pass in, like so:

fn divide(placeholder:f32, second_placeholder: f32) {
  placeholder / second_placeholder;
}
Enter fullscreen mode Exit fullscreen mode

Now we have removed 5 and 2, adding the parameters placeholder and second_placeholder. When we now invoke the function, we can send it values:

println!("{}", divide(5,2)); // 2.5
println!("{}", divide(10,2)); // 5
Enter fullscreen mode Exit fullscreen mode

Exercise - build a calculator

In this exercise, you will build a calculator that can handle addition, subtraction, multiplication and division.

  1. Create a file main.rs and add the following code:
   fn add(first_number: i32, second_number: i32) -> i32 {
     first_number + second_number
   }

   fn main() {
     let sum = add(2,2);
     let quotient = divide(10 as f32, 2 as f32);
     let product = multiply(2, 5);
     println!("{}", sum); // 8
   }
Enter fullscreen mode Exit fullscreen mode

Can you add what's missing? Give it some minutes and try to implement divide() and multiply(). The full solution can be found in the next section.

Full solution - calculator

fn add(first_number: i32, second_number: i32) -> i32 {
  first_number + second_number
}

fn divide(first_number: f32, second_number: f32) -> f32 {
  first_number / second_number
}

fn multiply(first_number: i32, second_number: i32) -> i32 {
  first_number * second_number;
}

fn main() {
  let sum = add(2,2);
  let quotient = divide(10 as f32, 2 as f32);
  let product = multiply(2, 5);
  println!("{}", sum);
  println!("{:.2}", quotient);
  println!("{}", product);
}
Enter fullscreen mode Exit fullscreen mode

Extra credit

See if you can implement a subtract() function as well.

Summary

In this tutorial, you learned about functions, why you have them and how to create them yourself. Hopefully, you feel empowered to create your own functions.

Discussion (1)

Collapse
lexiebkm profile image
Alexander B.K.

The interesting thing with Rust that I have found is Ownership for memory management, in comparison to how C/C++ handles memory using stack or heap.
Although, Rust is not pure OOP, I feel like at the same home when learning it in parallel with C/C++ and Go aka Golang, because it provides Struct and Pointer.