Functions in Rust
Get familiar with Rust’s syntax by programming the first function.
We'll cover the following
Now, let’s familiarize ourselves with Rust’s essential syntax by writing a set of functions using the Rust compiler. This initial step will provide a foundation for understanding the language's basic semantics.
Declaring functions in Rust
Let’s get started with the main()
function.
fn main() {println!("Hello, world!");}
This is a normal function, which, if you have experience with programming, is a conventional one that is written for every programming language when starting off. It includes a simple println!
macro, which prints the string "Hello, world!”
to the terminal.
Note: In Rust, Macros are a way to define and reuse patterns of code at compile time. Macros are invoked using the exclamation mark (
!
). We can also use the exclamation mark to distinguish a macro from a regular function.
Let’s create another function called add_five
. This function will take an integer argument and, as the name suggests, add five to it. The function will look like this:
fn add_five(num: u32) -> u32 {let add_num = 5;let new_num = num + add_num;return new_num;}
Now, let’s take a moment to see what’s going on here by breaking the function definition into chunks:
Line 1: This line contains information about the function definition. We need to look out for a few key components:
Whenever we need to define a function, we’ll use the
fn
keyword. This is followed by the name of the function.Inside the round brackets, after the function name, we denote the arguments that the function will take. In this case, the
add_five()
function takes one argument of typeu32
which we'll callnum
.We use a right arrow (
->
) to specify the type that the function will return. In this case, theadd_five()
function will return an unsigned integer of 32 bits, denoted byu32
.
Lines 2–4: These lines contain the function body, which is enclosed using curly brackets.
We create a new variable named
add_num
and assign it the value5
.In the next line, we'll add the two numbers and store the value in
new_num
.We then finally return
new_num
.
Note: In Rust, if we don't specify variable types explicitly, the compiler will often infer the types based on the context and usage of the variables. Rust's type inference system is powerful and helps ensure type safety without requiring explicit type annotations in many cases. It is also a good practice to specify types.
Another way of returning values from a function in Rust can be as follows:
fn add_five (num: u32) -> u32 {num + 5}
This time around, we do not create a variable to store the value, 5
. Instead, we add 5
as a constant to num
and returning it. In Rust, the function will return the value returned by the last statement in the function even if we do not use the return
keyword provided that the statement doesn’t have a semi-colon (;
) at the end.
Now, let’s have a look at how we can use add_five()
in the main()
function:
fn add_five(num: u32) -> u32 {let add_num = 5;let new_num = num + add_num;return new_num;}fn main() {let x: u32 = 5;let y = add_five(x);println!("{}", y);}
In the main()
function above,
Line 8: We declare a variable,
x
, and assign it a value of5
.Line 9: We then pass the variable,
x
, to theadd_five()
function that we created, and the value returned by the function is stored in the variable,y
.Line 10: We then print
y
using theprintln!
macro.
Note: We can't call the
add_five()
function without themain()
function because when the compiler runs a Rust program, it runs the code inside themain()
function. Hence it is important to include the functions that we want to call inside themain()
function.
Variable declarations in Rust
Let’s talk a little about how variables are declared in Rust. As we saw above, we used the let
keyword to declare our variables. In JavaScript or TypeScript, we use the let
keyword as opposed to the const
keyword, which is used to declare constant variables that cannot be changed after initialization. However, in Rust, we use only the let
keyword to declare variables.
An interesting fact: All declared variables in Rust are immutable by default. This means that when they’re declared, their values cannot be altered unless specified otherwise. To declare variables with a mutable value, we have to use let
with the mut
keyword.
Let’s have a short look at how we can achieve this:
fn main() {let x: u32 = 10;x += 10;println!("{}", x);}
Notice that when we try to change the value of x
, we get an error stating that we cannot change the value because the variable is immutable. Let’s see what happens when we use the mut
keyword:
fn main() {let mut x: u32 = 10;x += 10;println!("{}", &x);}
As expected, we didn’t get any errors because we declared x
to be a mutable variable.
Handling unused variables
Rust is an extremely memory-safe language. This means that even before the program is compiled, Rust will give errors on where the code might go wrong. If we were to use Rust in an IDE such as Visual Studio Code, we’d get all sorts of useful information, such as warnings for unused variables and warnings for another function taking ownership of a variable whose ownership belongs to a function in a different scope.
In case of unused variables, Rust will give us warnings, like so:
fn main() {let x: u32 = 50;println!("No variables being used.");}
We can see even though the code runs and we get our output, Rust gives us a warning that the variable we declared is not being used. We can deal with that by prepending an underscore before the variable name, as shown below:
fn main() {let _x: u32 = 50;println!("No variables being used.")}
Generally, it is a good practice to not declare variables in the first place if they’re not to be used but in case it is absolutely necessary, Rust provides the users with the mechanism, as discussed above.